/*
 * Copyright (C) 2016 Evgeny Khryukin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include <QAction>
#include <QColorDialog>
#include <QDataStream>
#include <QDomElement>
#include <QFile>
#include <QVariant>
#include <QWidget>
//#include <QTextStream>

#include "enummessagesplugin.h"

#include "activetabaccessinghost.h"
#include "applicationinfoaccessinghost.h"
#include "optionaccessinghost.h"
#include "pluginaccessinghost.h"
#include "psiaccountcontrollinghost.h"

#include "defines.h"

static const char *  propAcc      = "em_account";
static const char *  propJid      = "em_jid";
static const QString emIdName     = "psi_em_id";
static const QString htmlimNS     = "http://www.w3.org/1999/xhtml";
static const QString xhtmlProtoNS = "http://jabber.org/protocol/xhtml-im";

EnumMessagesPlugin::EnumMessagesPlugin() :
    enabled(false), _psiOptions(nullptr), _activeTab(nullptr), _applicationInfo(nullptr), _accContrller(nullptr),
    _inColor(QColor(Qt::red)), _outColor(QColor(Qt::green)), _defaultAction(true), _options(nullptr)

{
}

QString EnumMessagesPlugin::name() const { return constPluginName; }

QWidget *EnumMessagesPlugin::options()
{
    if (!enabled) {
        return nullptr;
    }
    _options = new QWidget();
    _ui.setupUi(_options);

    _ui.hack->hide();

    connect(_ui.tb_inColor, &QToolButton::clicked, this, &EnumMessagesPlugin::getColor);
    connect(_ui.tb_outColor, &QToolButton::clicked, this, &EnumMessagesPlugin::getColor);

    restoreOptions();

    return _options;
}

bool EnumMessagesPlugin::enable()
{
    enabled = true;
    QFile f(_applicationInfo->appCurrentProfileDir(ApplicationInfoAccessingHost::DataLocation)
            + QString(constEnumsFileName));
    if (f.exists() && f.open(QFile::ReadOnly)) {
        QDataStream s(&f);
        s >> _enumsIncomming >> _jidActions;
    }

    _inColor       = _psiOptions->getPluginOption(constInColor, _inColor).value<QColor>();
    _outColor      = _psiOptions->getPluginOption(constOutColor, _outColor).value<QColor>();
    _defaultAction = _psiOptions->getPluginOption(constDefaultAction, _defaultAction).toBool();

    return true;
}

bool EnumMessagesPlugin::disable()
{
    enabled = false;
    QFile f(_applicationInfo->appCurrentProfileDir(ApplicationInfoAccessingHost::DataLocation)
            + QString(constEnumsFileName));
    if (f.open(QFile::WriteOnly | QFile::Truncate)) {
        QDataStream s(&f);
        s << _enumsIncomming << _jidActions;
    }
    return true;
}

void EnumMessagesPlugin::applyOptions()
{
    _defaultAction = _ui.rb_enabled->isChecked();
    _inColor       = _ui.tb_inColor->property("psi_color").value<QColor>();
    _outColor      = _ui.tb_outColor->property("psi_color").value<QColor>();

    _psiOptions->setPluginOption(constInColor, _inColor);
    _psiOptions->setPluginOption(constOutColor, _outColor);
    _psiOptions->setPluginOption(constDefaultAction, _defaultAction);
}

void EnumMessagesPlugin::restoreOptions()
{
    if (_defaultAction) {
        _ui.rb_enabled->setChecked(true);
    } else {
        _ui.rb_disabled->setChecked(true);
    }

    _ui.tb_inColor->setStyleSheet(QString("background-color: %1;").arg(_inColor.name()));
    _ui.tb_inColor->setProperty("psi_color", _inColor);

    _ui.tb_outColor->setStyleSheet(QString("background-color: %1;").arg(_outColor.name()));
    _ui.tb_outColor->setProperty("psi_color", _outColor);
}

void EnumMessagesPlugin::setOptionAccessingHost(OptionAccessingHost *host) { _psiOptions = host; }

void EnumMessagesPlugin::setActiveTabAccessingHost(ActiveTabAccessingHost *host) { _activeTab = host; }

void EnumMessagesPlugin::setApplicationInfoAccessingHost(ApplicationInfoAccessingHost *host)
{
    _applicationInfo = host;
}

void EnumMessagesPlugin::setPsiAccountControllingHost(PsiAccountControllingHost *host) { _accContrller = host; }

bool EnumMessagesPlugin::incomingStanza(int account, const QDomElement &stanza)
{
    if (!enabled)
        return false;

    if (stanza.tagName() == "message") {
        QString type = stanza.attribute("type");

        if (type != "chat")
            return false;

        if (stanza.firstChildElement("body").isNull())
            return false;

        if (!stanza.hasAttribute(emIdName))
            return false;

        const QString jid(stanza.attribute("from").split('/').first());

        if (!isEnabledFor(account, jid))
            return false;

        quint16 num = stanza.attribute(emIdName, "1").toUShort();

        quint16  myNum = 0;
        JidEnums jids;
        if (_enumsIncomming.contains(account)) {
            jids = _enumsIncomming.value(account);

            if (jids.contains(jid)) {
                myNum = jids.value(jid);
            }
        }

        if (num > myNum + 1) {
            QString missed;
            while (num > myNum + 1) {
                missed += QString("%1 ").arg(numToFormatedStr(myNum + 1));
                ++myNum;
            }
            _accContrller->appendSysMsg(account, jid, tr("Missed messages: %1").arg(missed));
        }

        jids.insert(jid, num);
        _enumsIncomming.insert(account, jids);

        QDomDocument doc      = stanza.ownerDocument();
        QDomElement &nonConst = const_cast<QDomElement &>(stanza);
        addMessageNum(&doc, &nonConst, num, _inColor);
    }

    return false;
}

bool EnumMessagesPlugin::outgoingStanza(int account, QDomElement &stanza)
{
    if (!enabled)
        return false;

    if (stanza.tagName() == "message") {
        QString type = stanza.attribute("type");

        if (type != "chat")
            return false;

        if (stanza.firstChildElement("body").isNull())
            return false;

        const QString jid(stanza.attribute("to").split('/').first());

        if (!isEnabledFor(account, jid))
            return false;

        quint16 num = 1;

        JidEnums jids;
        if (_enumsOutgoing.contains(account)) {
            jids = _enumsOutgoing.value(account);

            if (jids.contains(jid)) {
                num = jids.value(jid);
                ++num;
            }
        }

        jids.insert(jid, num);
        _enumsOutgoing.insert(account, jids);

        stanza.setAttribute(emIdName, num);
    }
    return false;
}

QAction *EnumMessagesPlugin::getAction(QObject *parent, int account, const QString &contact)
{
    if (!enabled)
        return nullptr;
    QIcon    icon = PSI_PLUGIN_MD("icon").value<QIcon>();
    QAction *act  = new QAction(icon, tr("Enum Messages"), parent);
    act->setCheckable(true);
    const QString jid = contact.split("/").first();
    act->setProperty("account", account);
    act->setProperty("contact", jid);
    connect(act, &QAction::triggered, this, &EnumMessagesPlugin::onActionActivated);

    act->setChecked(_defaultAction);

    if (_jidActions.contains(account)) {
        JidActions a = _jidActions.value(account);
        if (a.contains(jid)) {
            act->setChecked(a.value(jid));
        }
    }

    return act;
}

void EnumMessagesPlugin::setupChatTab(QWidget *tab, int account, const QString &contact)
{
    tab->setProperty(propAcc, account);
    tab->setProperty(propJid, contact);
    connect(tab, &QWidget::destroyed, this, &EnumMessagesPlugin::removeWidget);
}

bool EnumMessagesPlugin::appendingChatMessage(int account, const QString &contact, QString &body, QDomElement &html,
                                              bool local)
{
    if (!enabled || !local)
        return false;

    if (body.isEmpty())
        return false;

    const QString jid(contact.split('/').first());

    if (!isEnabledFor(account, jid))
        return false;

    quint16 num = 0;

    JidEnums jids;
    if (_enumsOutgoing.contains(account)) {
        jids = _enumsOutgoing.value(account);

        if (jids.contains(jid)) {
            num = jids.value(jid);
        }
    }

    if (num == 0)
        return false;

    QDomNode     bodyNode;
    QDomDocument doc = html.ownerDocument();

    //    QString s;
    //    QTextStream str(&s, QIODevice::WriteOnly);
    //    html.save(str, 2);
    //    qDebug() << s;

    if (html.isNull()) {
        html = doc.createElementNS(htmlimNS, "body");
        doc.appendChild(html);
    } else {
        bodyNode = html.firstChild();
    }
    if (bodyNode.isNull()) {
        nl2br(&html, &doc, body);
    }

    QDomElement msgNum = doc.createElement("span");
    msgNum.setAttribute("style", "color: " + _outColor.name());
    msgNum.appendChild(doc.createTextNode(QString("%1 ").arg(numToFormatedStr(num))));

    QDomNode n = html.firstChild();
    html.insertBefore(msgNum, n);

    return false;
}

void EnumMessagesPlugin::removeWidget()
{
    QWidget *w       = static_cast<QWidget *>(sender());
    int      account = w->property(propAcc).toInt();
    QString  jid     = w->property(propJid).toString();

    if (_enumsOutgoing.contains(account)) {
        JidEnums jids = _enumsOutgoing.value(account);
        if (jids.contains(jid.split('/').first())) {
            jids.remove(jid);
            _enumsOutgoing[account] = jids;
        }
    }
}

void EnumMessagesPlugin::getColor()
{
    QToolButton *button = static_cast<QToolButton *>(sender());
    QColor       c(button->property("psi_color").value<QColor>());
    c = QColorDialog::getColor(c);
    if (c.isValid()) {
        button->setProperty("psi_color", c);
        button->setStyleSheet(QString("background-color: %1").arg(c.name()));
        // HACK
        _ui.hack->toggle();
    }
}

void EnumMessagesPlugin::onActionActivated(bool checked)
{
    QAction *     act     = static_cast<QAction *>(sender());
    const int     account = act->property("account").toInt();
    const QString jid     = act->property("contact").toString();

    JidActions acts;

    if (_jidActions.contains(account)) {
        acts = _jidActions.value(account);
    }

    acts[jid]            = checked;
    _jidActions[account] = acts;
}

void EnumMessagesPlugin::addMessageNum(QDomDocument *doc, QDomElement *stanza, quint16 num, const QColor &color)
{
    bool appendBody = false;

    QDomElement body;
    QDomElement element = stanza->firstChildElement("html");
    if (element.isNull()) {
        element = doc->createElementNS(xhtmlProtoNS, "html");
    } else {
        body = element.firstChildElement("body");
    }
    if (body.isNull()) {
        body       = doc->createElementNS(htmlimNS, "body");
        appendBody = true;
    }

    QDomElement msgNum = doc->createElement("span");
    msgNum.setAttribute("style", "color: " + color.name());
    msgNum.appendChild(doc->createTextNode(QString("%1 ").arg(numToFormatedStr(num))));

    if (appendBody) {
        body.appendChild(msgNum);
        nl2br(&body, doc, stanza->firstChildElement("body").text());
    } else {
        QDomNode n = body.firstChild();
        body.insertBefore(msgNum, n);
    }

    element.appendChild(body);
    stanza->appendChild(element);
}

QString EnumMessagesPlugin::numToFormatedStr(int number) { return QString("%1").arg(number, 5, 10, QChar('0')); }

void EnumMessagesPlugin::nl2br(QDomElement *body, QDomDocument *doc, const QString &msg)
{
    const QStringList strings = msg.split("\n");
    for (const QString &str : strings) {
        body->appendChild(doc->createTextNode(str));
        body->appendChild(doc->createElement("br"));
    }
    body->removeChild(body->lastChild());
}

bool EnumMessagesPlugin::isEnabledFor(int account, const QString &jid) const
{
    bool res = _defaultAction;

    if (_jidActions.contains(account)) {
        JidActions acts = _jidActions.value(account);
        if (acts.contains(jid)) {
            res = acts.value(jid);
        }
    }

    return res;
}

QString EnumMessagesPlugin::pluginInfo()
{
    return tr("The plugin is designed to enumerate messages, adding the messages numbers in chat logs "
              "and notification of missed messages. \n"
              "Supports per contact on / off message enumeration via the buttons on the chats toolbar.");
}
