/*
 * Copyright (C) 2023, KylinSoft Co., Ltd.
 *
 * 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 3, 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 <http://www.gnu.org/licenses/>.
 *
**/
#include "datetime.h"
#include <sys/timex.h>

#define MAX_TIMEZONES 5

DateTime::DateTime()
    : mFirstLoad(true)
{
    pluginName = tr("Date");
    pluginType = DATETIME;
}

DateTime::~DateTime()
{

}

QString DateTime::plugini18nName()
{
    return pluginName;
}

int DateTime::pluginTypes()
{
    return pluginType;
}

QWidget *DateTime::pluginUi()
{
    if (mFirstLoad) {  
        datetimeUi = new DatetimeUi;
        mFirstLoad = false;
        datetimeInterface = new QDBusInterface("org.ukui.ukcc.session",
                                               "/Datetime",
                                               "org.ukui.ukcc.session.Datetime",
                                               QDBusConnection::sessionBus(), this);
        if (datetimeInterface->isValid()) { // 判断服务是否存在
            QDBusMessage message = datetimeInterface->call("ping"); // 判断dbus路径是否存在
            if (message.type() == QDBusMessage::ErrorMessage && message.errorMessage().contains("No such object path", Qt::CaseInsensitive)) {
                qWarning()<<datetimeInterface<<":"<<message.errorMessage();
            } else {
                QDBusConnection::sessionBus().connect("org.ukui.ukcc.session",
                                                      "/Datetime",
                                                      "org.ukui.ukcc.session.Datetime",
                                                      "changed",
                                                      this,
                                                      SLOT(dataChanged(QString)));
                initContent();
            }

        } else {
            qCritical() << "org.ukui.ukcc.session.Datetime DBus error:" << datetimeInterface->lastError();
        }
    } else {
#ifdef Nile
        datetimeUi->resetDateFormat();
        datetimeUi->updateDate();
#endif
    }

    return datetimeUi;
}

const QString DateTime::name() const
{

    return QStringLiteral("Date");
}

bool DateTime::isShowOnHomePage() const
{
    return true;
}

QIcon DateTime::icon() const
{
    if (QIcon::hasThemeIcon("ukui-datetime-symbolic")) {
        return QIcon::fromTheme("ukui-datetime-symbolic");
    }
    return QIcon::fromTheme("ukui-datetine-symbolic");  //主题命名错误，兼容
}

bool DateTime::isEnable() const
{
    return true;
}

void DateTime::connectToServer()
{
    QThread *NetThread = new QThread;
    MThread *NetWorker = new MThread;
    NetWorker->moveToThread(NetThread);
    connect(NetThread, &QThread::started, NetWorker, &MThread::run);
    connect(NetWorker,&MThread::keychangedsignal,this,&DateTime::keyChangedSlot);
    connect(NetThread, &QThread::finished, NetWorker, &MThread::deleteLater);
    NetThread->start();
}

void DateTime::keyChangedSlot(const QString &key)
{
    if (key == "datetime") {
        initTimer();
        initDate();
        initTimezone();
        initTimeModeSet();
        initNtpServers();
        initNtpServer();
        initOtherTimezone();
    }
}

void DateTime::dataChanged(QString key)
{
    if (toChangeKey != key) {
        if(key == "timezone") {
            initDate();
        } else if (key == "timeMode") {
            initTimeModeSet();
        } else if (key == "ntpServer") {
            initNtpServer();
        } else if (key == "otherTimezones") {
            initOtherTimezone();
        }
    }
    toChangeKey = "";
}

void DateTime::initContent()
{
    initTimer();
    initDate();
    initTimezone();
    initTimeModeSet();
    initNtpServers();
    initNtpServer();
    initOtherTimezone();
    connectUiSignals();
}

void DateTime::initTimer()
{
    if (!timer) {
        timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, [=](){
            struct timex txc = {};
            if (adjtimex(&txc) < 0 || txc.maxerror >= 16000000) {  //未能同步时间
                timerTimes++;
                if (timerTimes >= 120) {
                    datetimeUi->setSyncResultLabel(1);
                } else {
                    datetimeUi->setSyncResultLabel(0);
                }
            } else {
                timerTimes = 0;
                timer->stop();
                datetimeUi->setSyncResultLabel(2);   //同步时间成功
            }
        });
    }
}

void DateTime::initDate()
{
    QStringList list = datetimeInterface->property("timezone").toStringList();
    QTimeZone localTimezone = QTimeZone(list.at(0).toLatin1().data());
    int utcOff = (localTimezone.offsetFromUtc(QDateTime::currentDateTime()))/3600;
    QString gmData;
    if (utcOff >= 0) {
       gmData = QString("(GMT+%1:%2)").arg(utcOff, 2, 10, QLatin1Char('0')).arg(utcOff / 60, 2, 10, QLatin1Char('0'));
    } else {
       gmData = QString("(GMT%1:%2)").arg(utcOff, 3, 10, QLatin1Char('0')).arg(utcOff / 60, 2, 10, QLatin1Char('0'));
    }
    datetimeUi->setTimezoneStr(gmData + " " + list.at(1));
}

void DateTime::initTimezone()
{
    if (!timezoneMap) {
        timezoneMap = new TimeZoneChooser(datetimeUi);
        timezoneMap->setWindowModality(Qt::ApplicationModal);
        connect(timezoneMap, &TimeZoneChooser::confirmed, this, [=](QString timezone, QString title){
            if (title == tr("Change Timezone")) {
                UkccCommon::buriedSettings(name(), "Change Timezone", QString("settings"), timezone);
                datetimeInterface->call("setTimezone", timezone);
            } else {
                UkccCommon::buriedSettings(name(), "Add Timezone", QString("settings"), timezone);
                if (!otherTimezoneList.contains(timezone)) {
                    otherTimezoneList.append(timezone);
                    toChangeKey = "otherTimezones";
                    QDBusReply<bool> reply = datetimeInterface->call("setOtherTimezones", otherTimezoneList);
                    if (reply.isValid() && reply.value()) {
                        if (otherTimezoneList.size() >= MAX_TIMEZONES) {
                            datetimeUi->setAddTimezoneBtnEnabled(false);
                        } else {
                            datetimeUi->setAddTimezoneBtnEnabled(true);
                        }
                        QDBusReply<QStringList> l = datetimeInterface->call("timezoneName", timezone);
                        if (l.isValid() && l.value().size() > 1) {
                            datetimeUi->addOtherTimezone(timezone, l.value().at(1));
                        }
                    } else {
                        otherTimezoneList.removeOne(timezone);
                    }
                }
            }
        });
    }
}

void DateTime::initTimeModeSet()
{
    QString timeMode = datetimeInterface->property("timeMode").toString();
    if (timeMode == "manual") {
        datetimeUi->setTimeMode("manual");
    } else {
        datetimeUi->setTimeMode("automatic");
    }
}

void DateTime::initNtpServers()
{
    QStringList list = datetimeInterface->property("ntpServers").toStringList();
    datetimeUi->setNtpServers(list);
}

void DateTime::initNtpServer()
{
    datetimeUi->setNtpServer(datetimeInterface->property("ntpServer").toString());
}

void DateTime::initOtherTimezone()
{
    QStringList list = datetimeInterface->property("otherTimezones").toStringList();
    for (QString s1 : list) {
        bool add = true;
        for (QString s2 : otherTimezoneList) {
            if (s2 == s1) {
                add = false;
                break;
            }
        }
        if (add) {
            otherTimezoneList.append(s1);
            QDBusReply<QStringList> l = datetimeInterface->call("timezoneName", s1);
            if (l.isValid() && l.value().size() > 1) {
                datetimeUi->addOtherTimezone(s1, l.value().at(1));
            }
        }
    }
}

void DateTime::connectUiSignals()
{
    connect(datetimeUi, &DatetimeUi::timezoneButtonClicked, this, [=](){
        showTimezoneMap(0);
    });
    connect(datetimeUi, &DatetimeUi::dateTimeChanged, this, [=](QDate d, QTime t){
        QDateTime setdt(d, t);
        datetimeInterface->call("setTime", setdt.toSecsSinceEpoch() * G_TIME_SPAN_SECOND);
    });
    connect(datetimeUi, &DatetimeUi::timeModeChanged, this, [=](int toggledBtn, QString mode){
       UkccCommon::buriedSettings(name(), "Set Time", QString("settings"), mode);
       QDBusReply<bool> reply;
       initNtpServer();
       if (mode == "manual") {
           toChangeKey = "timeMode";
           reply = datetimeInterface->call("setTimeMode", "manual");
           timerTimes = 0;
           timer->stop();
           datetimeUi->setSyncResultLabel(2);
       } else {
           toChangeKey = "timeMode";
           reply = datetimeInterface->call("setTimeMode", "automatic");
           if (!reply.isValid() || reply.value()) {
                timer->start(80);
           }
       }
       if (!reply.value()) {
           if (mode == "manual" && toggledBtn == 1)
                datetimeUi->setTimeMode("automatic");
           else if (mode == "automatic" && toggledBtn == 0)
               datetimeUi->setTimeMode("manual");
       }
    });
    connect(datetimeUi, &DatetimeUi::ntpServerChanged, this, [=](QString server){
        UkccCommon::buriedSettings(name(), "Sync Server", QString("select"), server);
        toChangeKey = "ntpServer";
        QDBusReply<bool> reply = datetimeInterface->call("setNtpServer", server);
        if (!reply.value()) { // 未设置成功,未授权
            initNtpServer();
        } else {
            timerTimes = 0;
            timer->stop();
            timer->start(80);
        }
    });
    connect(datetimeUi, &DatetimeUi::addTimezoneButtonClicked, this, [=](){
        showTimezoneMap(1);
    });
    connect(datetimeUi, &DatetimeUi::timezoneRemoved, this, [=](QString t){
        otherTimezoneList.removeOne(t);
        toChangeKey = "otherTimezones";
        QDBusReply<bool> reply = datetimeInterface->call("setOtherTimezones", otherTimezoneList);
        if (reply.isValid() && reply.value()) {
            if (otherTimezoneList.size() >= MAX_TIMEZONES) {
                datetimeUi->setAddTimezoneBtnEnabled(false);
            } else {
                datetimeUi->setAddTimezoneBtnEnabled(true);
            }
        }
    });
}

void DateTime::showTimezoneMap(int flag)
{
    if (flag == 1) {
        timezoneMap->setTitle(tr("Add Timezone"));
    } else {
        timezoneMap->setTitle(tr("Change Timezone"));
    }

    int mapX = datetimeUi->topLevelWidget()->x() + (datetimeUi->topLevelWidget()->width() - 960)/2;
    int mapY = datetimeUi->topLevelWidget()->y() + (datetimeUi->topLevelWidget()->height() - 640)/2;
    mapX = mapX > 0 ? mapX : 0;
    mapY = mapY > 0 ? mapY : 0;
    timezoneMap->move(mapX, mapY);
    timezoneMap->show();
    timezoneMap->setMarkedTimeZoneSlot(datetimeInterface->property("timezone").toStringList().at(2));
}
