/*
 * birthdayreminderplugin.cpp - plugin
 * Copyright (C) 2009-2010  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 <QDomElement>
#include <QFileDialog>
#include <QSet>

#include "accountinfoaccessinghost.h"
#include "accountinfoaccessor.h"
#include "applicationinfoaccessinghost.h"
#include "applicationinfoaccessor.h"
#include "contactinfoaccessinghost.h"
#include "contactinfoaccessor.h"
#include "iconfactoryaccessinghost.h"
#include "iconfactoryaccessor.h"
#include "optionaccessinghost.h"
#include "optionaccessor.h"
#include "plugininfoprovider.h"
#include "popupaccessinghost.h"
#include "popupaccessor.h"
#include "psiplugin.h"
#include "soundaccessinghost.h"
#include "soundaccessor.h"
#include "stanzafilter.h"
#include "stanzasender.h"
#include "stanzasendinghost.h"

#include "ui_options.h"

#define constLastCheck "lstchck"
#define constDays "days"
#define constInterval "intrvl"
#define constTimeout "timeout"
#define constStartCheck "strtchck"
#define constCheckFromRoster "chckfrmrstr"
#define constLastUpdate "lstupdate"
#define constUpdateInterval "updtintvl"
#define constSoundFile "sndfl"

#define POPUP_OPTION_NAME "Birthday Reminder Plugin"

static const QString id      = "bdreminder_1";
static const QString dirName = "Birthdays";

class Reminder : public QObject,
                 public PsiPlugin,
                 public StanzaFilter,
                 public AccountInfoAccessor,
                 public ApplicationInfoAccessor,
                 public StanzaSender,
                 public OptionAccessor,
                 public PopupAccessor,
                 public IconFactoryAccessor,
                 public PluginInfoProvider,
                 public SoundAccessor,
                 public ContactInfoAccessor {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "com.psi-plus.Reminder" FILE "psiplugin.json")
    Q_INTERFACES(PsiPlugin StanzaFilter AccountInfoAccessor ApplicationInfoAccessor StanzaSender OptionAccessor
                     PopupAccessor IconFactoryAccessor PluginInfoProvider SoundAccessor ContactInfoAccessor)

public:
    Reminder() = default;
    virtual QString  name() const;
    virtual QWidget *options();
    virtual bool     enable();
    virtual bool     disable();
    virtual void     applyOptions();
    virtual void     restoreOptions();
    virtual bool     incomingStanza(int account, const QDomElement &xml);
    virtual bool     outgoingStanza(int account, QDomElement &xml);
    virtual void     setAccountInfoAccessingHost(AccountInfoAccessingHost *host);
    virtual void     setApplicationInfoAccessingHost(ApplicationInfoAccessingHost *host);
    virtual void     setStanzaSendingHost(StanzaSendingHost *host);
    virtual void     setOptionAccessingHost(OptionAccessingHost *host);
    virtual void     optionChanged(const QString &) { }
    virtual void     setPopupAccessingHost(PopupAccessingHost *host);
    virtual void     setIconFactoryAccessingHost(IconFactoryAccessingHost *host);
    virtual void     setSoundAccessingHost(SoundAccessingHost *host);
    virtual void     setContactInfoAccessingHost(ContactInfoAccessingHost *host);
    virtual QString  pluginInfo();

private:
    QString checkBirthdays();
    QString bdaysDir() const;

private slots:
    void updateVCard();
    bool check();
    void clearCache();
    void getSound();
    void checkSound();
    void playSound(const QString &);
    void timeoutStopUpdate();

private:
    bool                          enabled     = false;
    OptionAccessingHost          *psiOptions  = nullptr;
    AccountInfoAccessingHost     *accInfoHost = nullptr;
    ApplicationInfoAccessingHost *appInfoHost = nullptr;
    StanzaSendingHost            *stanzaHost  = nullptr;
    PopupAccessingHost           *popup       = nullptr;
    IconFactoryAccessingHost     *icoHost     = nullptr;
    SoundAccessingHost           *sound_      = nullptr;
    ContactInfoAccessingHost     *contactInfo = nullptr;

    QString lastCheck = QStringLiteral("1901010101");
    int     days_     = 5;
    int     interval  = 24;
    // int timeout = 15;
    bool    startCheck       = true;
    bool    checkFromRoster  = true;
    QString lastUpdate       = QStringLiteral("19010101");
    int     updateInterval   = 30;
    QString soundFile        = QStringLiteral("sound/reminder.wav");
    bool    updateInProgress = false;
    int     popupId          = 0;

    QPointer<QWidget> options_;
    Ui::Options       ui_;
};

QString Reminder::name() const { return "Birthday Reminder Plugin"; }

bool Reminder::enable()
{
    if (!psiOptions)
        return enabled;

    QFile file(":/reminder/birthdayreminderplugin.png");
    if (file.open(QIODevice::ReadOnly)) {
        QByteArray image = file.readAll();
        icoHost->addIcon("reminder/birthdayicon", image);
        file.close();
    } else {
        return enabled;
    }

    enabled = true;

    lastCheck       = psiOptions->getPluginOption(constLastCheck, lastCheck).toString();
    days_           = psiOptions->getPluginOption(constDays, days_).toInt();
    interval        = psiOptions->getPluginOption(constInterval, interval).toInt();
    startCheck      = psiOptions->getPluginOption(constStartCheck, startCheck).toBool();
    checkFromRoster = psiOptions->getPluginOption(constCheckFromRoster, checkFromRoster).toBool();
    updateInterval  = psiOptions->getPluginOption(constUpdateInterval, updateInterval).toInt();
    lastUpdate      = psiOptions->getPluginOption(constLastUpdate, lastUpdate).toString();
    soundFile       = psiOptions->getPluginOption(constSoundFile, QVariant(soundFile)).toString();

    int timeout = psiOptions->getPluginOption(constTimeout, QVariant(15000)).toInt() / 1000;
    popupId
        = popup->registerOption(POPUP_OPTION_NAME, timeout, QLatin1String("plugins.options.reminder.") + constTimeout);

    QDir dir(bdaysDir());
    if (!dir.exists()) {
        dir.cdUp();
        dir.mkdir(dirName);
        return enabled;
    }
    if (startCheck) {
        lastCheck = QDateTime::currentDateTime().toString("yyyyMMddhh");
        psiOptions->setPluginOption(constLastCheck, QVariant(lastCheck));
        QTimer::singleShot(4000, this, SLOT(check())); // необходимо для инициализации приложения
    }

    return enabled;
}

bool Reminder::disable()
{
    enabled = false;
    popup->unregisterOption(POPUP_OPTION_NAME);
    return true;
}

QString Reminder::bdaysDir() const
{
    static QString dir(appInfoHost->appVCardDir() + QDir::separator() + dirName);
    return dir;
}

QWidget *Reminder::options()
{
    if (!enabled)
        return nullptr;

    options_ = new QWidget();
    ui_.setupUi(options_);

    ui_.tb_get->setIcon(icoHost->getIcon("psi/browse"));
    ui_.tb_check->setIcon(icoHost->getIcon("psi/play"));

    connect(ui_.pb_update, &QPushButton::clicked, this, &Reminder::updateVCard);
    connect(ui_.pb_check, &QPushButton::clicked, this, &Reminder::check);
    connect(ui_.pb_clear_cache, &QPushButton::clicked, this, &Reminder::clearCache);
    connect(ui_.tb_check, &QPushButton::clicked, this, &Reminder::checkSound);
    connect(ui_.tb_get, &QPushButton::clicked, this, &Reminder::getSound);

    restoreOptions();

    return options_;
}

bool Reminder::incomingStanza(int account, const QDomElement &stanza)
{
    if (enabled) {
        if (stanza.tagName() == "iq" && stanza.attribute("id") == id) {
            QDomNode    VCard = stanza.firstChild();
            QDomElement BDay  = VCard.firstChildElement("BDAY");
            if (!BDay.isNull()) {
                QString Jid  = stanza.attribute("from");
                QString Nick = contactInfo->name(account, Jid);
                if (Nick == Jid)
                    Nick = VCard.firstChildElement("NICKNAME").text();
                QString Date = BDay.text();
                if (!Date.isEmpty()) {
                    Jid.replace("@", "_at_");
                    QFile file(bdaysDir() + QDir::separator() + Jid);
                    if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
                        QTextStream out(&file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                        out.setCodec("UTF-8");
#else
                        out.setEncoding(QStringConverter::Utf8);
#endif
                        out.setGenerateByteOrderMark(false);
                        out << Date << "__" << Nick << Qt::endl;
                    }
                }
            }
            return true;
        }

        if (stanza.tagName() == "presence") {
            QDateTime cur = QDateTime::currentDateTime();
            if ((lastCheck.toLong() + interval) <= cur.toString("yyyyMMddhh").toLong()) {
                lastCheck = QDateTime::currentDateTime().toString("yyyyMMddhh");
                psiOptions->setPluginOption(constLastCheck, QVariant(lastCheck));
                check();
            }
            if (updateInterval) {
                if ((lastUpdate.toLong() + updateInterval) <= cur.toString("yyyyMMdd").toLong()) {
                    lastUpdate = QDateTime::currentDateTime().toString("yyyyMMdd");
                    psiOptions->setPluginOption(constLastUpdate, QVariant(lastUpdate));
                    updateVCard();
                }
            }
        }
    }
    return false;
}

bool Reminder::outgoingStanza(int /*account*/, QDomElement & /*xml*/) { return false; }

void Reminder::setAccountInfoAccessingHost(AccountInfoAccessingHost *host) { accInfoHost = host; }

void Reminder::setApplicationInfoAccessingHost(ApplicationInfoAccessingHost *host) { appInfoHost = host; }

void Reminder::applyOptions()
{
    if (!options_)
        return;

    days_ = ui_.sb_start->value();
    psiOptions->setPluginOption(constDays, QVariant(days_));

    interval = ui_.sb_check_interval->value();
    psiOptions->setPluginOption(constInterval, QVariant(interval));

    startCheck = ui_.cb_startupcheck->isChecked();
    psiOptions->setPluginOption(constStartCheck, QVariant(startCheck));

    checkFromRoster = ui_.cb_active_accounts->isChecked();
    psiOptions->setPluginOption(constCheckFromRoster, QVariant(checkFromRoster));

    updateInterval = ui_.sb_update_interval->value();
    psiOptions->setPluginOption(constUpdateInterval, QVariant(updateInterval));

    soundFile = ui_.le_sound->text();
    psiOptions->setPluginOption(constSoundFile, QVariant(soundFile));
}

void Reminder::restoreOptions()
{
    if (!options_)
        return;

    ui_.sb_start->setValue(days_);
    ui_.sb_check_interval->setValue(interval);
    ui_.cb_startupcheck->setChecked(startCheck);
    ui_.cb_active_accounts->setChecked(checkFromRoster);
    ui_.sb_update_interval->setValue(updateInterval);
    ui_.le_sound->setText(soundFile);
}

void Reminder::setStanzaSendingHost(StanzaSendingHost *host) { stanzaHost = host; }

void Reminder::updateVCard()
{
    if (enabled && !updateInProgress) {
        updateInProgress       = true;
        const QString     path = appInfoHost->appVCardDir();
        QDir              dir(path);
        const QStringList files = dir.entryList(QDir::Files);
        for (auto filename : files) {
            QFile file(path + QDir::separator() + filename);
            if (file.open(QIODevice::ReadOnly)) {
                QTextStream in(&file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                in.setCodec("UTF-8");
#else
                in.setEncoding(QStringConverter::Utf8);
#endif
                QDomDocument doc;
                doc.setContent(in.readAll());
                QDomElement vCard = doc.documentElement();
                QDomElement BDay  = vCard.firstChildElement("BDAY");
                if (!BDay.isNull()) {
                    QString Nick = vCard.firstChildElement("NICKNAME").text();
                    QString Date = BDay.text();
                    if (!Date.isEmpty()) {
                        filename.replace("%5f", "_");
                        filename.replace("%2d", "-");
                        filename.replace("%25", "%");
                        filename.remove(".xml");
                        QFile file(bdaysDir() + QDir::separator() + filename);
                        if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
                            QTextStream out(&file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                            out.setCodec("UTF-8");
#else
                            out.setEncoding(QStringConverter::Utf8);
#endif
                            out.setGenerateByteOrderMark(false);
                            out << Date << "__" << Nick << Qt::endl;
                        }
                    }
                }
            }
        }

        int accs = -1;
        while (true) {
            QStringList Jids = accInfoHost->getRoster(++accs);
            if (!Jids.isEmpty()) {
                if (Jids.first() == "-1") {
                    break;
                } else if (accInfoHost->getStatus(accs) != "offline") {
                    QString text = "<iq type=\"get\" to=\"%1\" id=\"%2\">"
                                   "<vCard xmlns=\"vcard-temp\" /></iq>";
                    for (const QString &Jid : Jids) {
                        stanzaHost->sendStanza(accs, text.arg(Jid, id));
                    }
                }
            }
        }
        QTimer::singleShot(30000, this,
                           SLOT(timeoutStopUpdate())); // 30 секунд дольжно хватить, чтобы получить все vCard'ы
    }
}

void Reminder::timeoutStopUpdate() { updateInProgress = false; }

QString Reminder::checkBirthdays()
{
    if (!enabled)
        return QString();

    QSet<QString> Roster_;
    if (checkFromRoster) {
        int accs = -1;
        while (true) {
            QStringList Jids = accInfoHost->getRoster(++accs);
            if (!Jids.isEmpty()) {
                if (Jids.first() == "-1") {
                    break;
                } else {
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
                    Roster_ += QSet<QString>(Jids.begin(), Jids.end());
#else
                    Roster_ += Jids.toSet();
#endif
                }
            }
        }
    }

    QString           CheckResult;
    const QStringList jids = QDir(bdaysDir()).entryList(QDir::Files);
    for (auto jid : jids) {
        if (jid.contains("_at_")) {
            QFile file(bdaysDir() + QDir::separator() + jid);
            if (file.open(QIODevice::ReadOnly)) {
                QTextStream in(&file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                in.setCodec("UTF-8");
#else
                in.setEncoding(QStringConverter::Utf8);
#endif
                QString     line   = in.readLine();
                QStringList fields = line.split("__");
                QString     Date   = fields.takeFirst();
                QString     Nick   = "";
                if (!fields.isEmpty()) {
                    Nick = fields.takeFirst();
                }
                QDate Birthday = QDate::currentDate();
                if (Date.contains("-")) {
                    Birthday = QDate::fromString(Date, "yyyy-MM-dd");
                } else if (Date.contains(".")) {
                    Birthday = QDate::fromString(Date, "d.MM.yyyy");
                } else if (Date.contains("/")) {
                    Birthday = QDate::fromString(Date, "d/MM/yyyy");
                }
                QDate current = QDate::currentDate();
                if (current != Birthday) {
                    int years      = current.year() - Birthday.year();
                    Birthday       = Birthday.addYears(years);
                    int     daysTo = current.daysTo(Birthday);
                    QString days;
                    days.setNum(daysTo);
                    jid.replace("_at_", "@");
                    if (!checkFromRoster || Roster_.contains(jid)) {
                        if (daysTo == 0) {
                            CheckResult += Nick + " (" + jid + ") "
                                + tr("celebrates birthday today!"
                                     "\n");
                        } else if (daysTo <= days_ && daysTo > 0) {
                            CheckResult
                                += Nick + " (" + jid + ") " + tr("celebrates birthday in %n day(s)\n", "", daysTo);
                        } else if (daysTo == -1) {
                            CheckResult += Nick + " (" + jid + ") " + tr("celebrates birthday yesterday.\n");
                        }
                    }
                }
            }
        }
    }
    return CheckResult;
}

void Reminder::setOptionAccessingHost(OptionAccessingHost *host) { psiOptions = host; }

void Reminder::setIconFactoryAccessingHost(IconFactoryAccessingHost *host) { icoHost = host; }

void Reminder::setSoundAccessingHost(SoundAccessingHost *host) { sound_ = host; }

void Reminder::setContactInfoAccessingHost(ContactInfoAccessingHost *host) { contactInfo = host; }

bool Reminder::check()
{
    QString text = checkBirthdays();
    if (text.isEmpty())
        return false;
    text.chop(1);

    if (psiOptions->getGlobalOption("options.ui.notifications.sounds.enable").toBool())
        playSound(soundFile);

    text = text.replace("\n", "<br>");
    popup->initPopup(text, tr("Birthday Reminder"), "reminder/birthdayicon", popupId);

    return true;
}

void Reminder::clearCache()
{
    QDir              dir(bdaysDir());
    const QStringList files = dir.entryList(QDir::Files);
    for (const QString &file : files) {
        QFile File(bdaysDir() + QDir::separator() + file);
        if (File.open(QIODevice::ReadWrite)) {
            File.remove();
        }
    }
    lastUpdate = "19010101";
    psiOptions->setPluginOption(constLastUpdate, QVariant(lastUpdate));
}

void Reminder::setPopupAccessingHost(PopupAccessingHost *host) { popup = host; }

void Reminder::playSound(const QString &f) { sound_->playSound(f); }

void Reminder::getSound()
{
    QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Choose a sound file"), "", tr("Sound (*.wav)"));
    if (fileName.isEmpty())
        return;
    ui_.le_sound->setText(fileName);
}

void Reminder::checkSound() { playSound(ui_.le_sound->text()); }

QString Reminder::pluginInfo()
{
    return tr("This plugin is designed to show reminders of upcoming birthdays.\n"
              "The first time you install this plugin, you need to log on to all of your accounts, go to the plugin "
              "settings and click \"Update Birthdays\"."
              "The plugin will then collect the information about the birthdays of all the users in your roster, but "
              "when the 'Use vCards cache' option is"
              "selected, the users' vCards that are cached on your hard disk will be used. ");
}

#include "birthdayreminderplugin.moc"
