/*
    SPDX-FileCopyrightText: 2005-2010 David Jarvie <djarvie@kde.org>
    SPDX-FileCopyrightText: 2005 S.R.Haque <srhaque@iee.org>
    SPDX-FileCopyrightText: 2013 Martin Klapetek <mklapetek@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "ktimezoned.h"

#include <QDBusConnection>
#include <QDBusMessage>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QTimeZone>

#include <KConfig>
#include <KConfigGroup>
#include <KDirWatch>
#include <KPluginFactory>

using namespace Qt::StringLiterals;

K_PLUGIN_CLASS_WITH_JSON(KTimeZoned, "ktimezoned.json")

const char LOCAL_ZONE[] = "LocalZone"; // name of local time zone
const char ZONEINFO_DIR[] = "ZoneinfoDir"; // path to zoneinfo/ directory
const char ZONE_TAB[] = "Zonetab"; // path & name of zone.tab

KTimeZoned::KTimeZoned(QObject *parent, const QList<QVariant> &l)
    : KTimeZonedBase(parent, l)
{
    init(false);
}

KTimeZoned::~KTimeZoned()
{
    delete std::exchange(m_dirWatch, nullptr);
    delete std::exchange(m_zoneTabWatch, nullptr);
}

void KTimeZoned::init(bool restart)
{
    if (restart) {
        delete std::exchange(m_dirWatch, nullptr);
        delete std::exchange(m_zoneTabWatch, nullptr);
        m_localZone = QString();
        m_zoneinfoDir = QString();
        m_zoneTab = QString();
    }

    KConfig config(QStringLiteral("ktimezonedrc"));
    if (restart)
        config.reparseConfiguration();

    KConfigGroup group(&config, u"TimeZones"_s);
    m_localZone = group.readEntry(LOCAL_ZONE);
    m_zoneinfoDir = group.readEntry(ZONEINFO_DIR);
    m_zoneTab = group.readEntry(ZONE_TAB);

    updateLocalZone();

    if (!m_dirWatch) {
        m_dirWatch = new KDirWatch(this);
        m_dirWatch->addFile(QStringLiteral("/etc/timezone"));
        m_dirWatch->addFile(QStringLiteral("/etc/localtime"));

        connect(m_dirWatch, &KDirWatch::dirty, this, &KTimeZoned::updateLocalZone);
        connect(m_dirWatch, &KDirWatch::deleted, this, &KTimeZoned::updateLocalZone);
        connect(m_dirWatch, &KDirWatch::created, this, &KTimeZoned::updateLocalZone);
    }

    if (!m_zoneTabWatch && findZoneTab(m_zoneTab)) {
        // cache the values so we don't look it up on next startup
        KConfig config(QStringLiteral("ktimezonedrc"));
        KConfigGroup group(&config, u"TimeZones"_s);
        group.writeEntry(ZONEINFO_DIR, m_zoneinfoDir);
        group.writeEntry(ZONE_TAB, m_zoneTab);
        group.sync();

        m_zoneTabWatch = new KDirWatch(this);
        m_zoneTabWatch->addDir(m_zoneinfoDir, KDirWatch::WatchFiles);

        connect(m_zoneTabWatch, &KDirWatch::dirty, this, &KTimeZoned::updateLocalZone);
        connect(m_zoneTabWatch, &KDirWatch::created, this, &KTimeZoned::updateLocalZone);
        connect(m_zoneTabWatch, &KDirWatch::deleted, this, &KTimeZoned::updateLocalZone);
    }
}

// Check if the local zone has been updated, and if so, write the new
// zone to the config file and notify interested parties.
void KTimeZoned::updateLocalZone()
{
    QString systemTimeZone = QString::fromLocal8Bit(QTimeZone::systemTimeZoneId());

    if (m_localZone != systemTimeZone) {
        qDebug() << "System timezone has been changed, new timezone is" << systemTimeZone;

        KConfig config(QStringLiteral("ktimezonedrc"));
        KConfigGroup group(&config, u"TimeZones"_s);
        m_localZone = systemTimeZone;
        group.writeEntry(LOCAL_ZONE, m_localZone);
        group.sync();

        QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Daemon"), QStringLiteral("org.kde.KTimeZoned"), QStringLiteral("timeZoneChanged"));
        QDBusConnection::sessionBus().send(message);
    }
}

/*
 * Find the location of the zoneinfo files and store in m_zoneinfoDir.
 */
bool KTimeZoned::findZoneTab(const QString &pathFromConfig)
{
    // First try the cached path
    if (QFileInfo::exists(pathFromConfig)) {
        return true;
    }

    const QString ZONE_TAB_FILE = QStringLiteral("/zone.tab");
    const QString ZONE_INFO_DIR = QStringLiteral("/usr/share/zoneinfo");

    // Find and open zone.tab - it's all easy except knowing where to look.
    // Try the LSB location first.
    QDir dir;
    QString zoneinfoDir = ZONE_INFO_DIR;
    QString zoneTab = ZONE_INFO_DIR + ZONE_TAB_FILE;
    // make a note if the dir exists; whether it contains zone.tab or not
    if (dir.exists(zoneinfoDir) && QFileInfo::exists(zoneTab)) {
        m_zoneinfoDir = zoneinfoDir;
        m_zoneTab = zoneTab;
        return true;
    }

    zoneinfoDir = QStringLiteral("/usr/lib/zoneinfo");
    zoneTab = zoneinfoDir + ZONE_TAB_FILE;
    if (dir.exists(zoneinfoDir) && QFileInfo::exists(zoneTab)) {
        m_zoneinfoDir = zoneinfoDir;
        m_zoneTab = zoneTab;
        return true;
    }

    zoneinfoDir = QString::fromLocal8Bit(::getenv("TZDIR"));
    zoneTab = zoneinfoDir + ZONE_TAB_FILE;
    if (!zoneinfoDir.isEmpty() && dir.exists(zoneinfoDir) && QFileInfo::exists(zoneTab)) {
        m_zoneinfoDir = zoneinfoDir;
        m_zoneTab = zoneTab;
        return true;
    }

    zoneinfoDir = QLatin1String("/usr/share/lib/zoneinfo");
    zoneTab = zoneinfoDir + ZONE_TAB_FILE;
    if (dir.exists(zoneinfoDir + QLatin1String("/src")) && QFileInfo::exists(zoneTab)) {
        m_zoneinfoDir = zoneinfoDir;
        m_zoneTab = zoneTab;
        return true;
    }

    return false;
}

void KTimeZoned::zonetabChanged()
{
    QDBusMessage message =
        QDBusMessage::createSignal(QStringLiteral("/Daemon"), QStringLiteral("org.kde.KTimeZoned"), QStringLiteral("timeZoneDatabaseUpdated"));
    QDBusConnection::sessionBus().send(message);
}

#include "ktimezoned.moc"
#include "moc_ktimezonedbase.cpp"

#include "moc_ktimezoned.cpp"
