/*
   This file is part of the KDE libraries
   Copyright (c) 2005-2010 David Jarvie <djarvie@kde.org>
   Copyright (c) 2005 S.R.Haque <srhaque@iee.org>.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

// This file requires HAVE_STRUCT_TM_TM_ZONE to be defined if struct tm member tm_zone is available.
// This file requires HAVE_TM_GMTOFF to be defined if struct tm member tm_gmtoff is available.

#include "moc_ksystemtimezone.cpp"

#include <config-date.h>

#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#if HAVE_TIME_H
#include <time.h>
#endif
#include <climits>
#include <cstdlib>

#include <QDebug>
#include <QtCore/QCoreApplication>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QRegExp>
#include <QtCore/QStringList>
#include <QtCore/QTextStream>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusConnectionInterface>
#include <QtDBus/QDBusReply>

#include <kconfig.h>
#include <klocalizedstring.h>
#include <kstringhandler.h>
#include <kconfiggroup.h>
#include "ktzfiletimezone.h"
#ifdef Q_OS_WIN
#include "ktimezone_win.h"
#endif

#define KTIMEZONED_DBUS_IFACE "org.kde.KTimeZoned"

/* Return the offset to UTC in the current time zone at the specified UTC time.
 * The thread-safe function localtime_r() is used in preference if available.
 */
int gmtoff(time_t t)
{
#ifdef _POSIX_THREAD_SAFE_FUNCTIONS
    tm tmtime;
    if (!localtime_r(&t, &tmtime)) {
        return 0;
    }
#if HAVE_TM_GMTOFF
    return tmtime.tm_gmtoff;
#else
    int lwday = tmtime.tm_wday;
    int lt = 3600 * tmtime.tm_hour + 60 * tmtime.tm_min + tmtime.tm_sec;
    if (!gmtime_r(&t, &tmtime)) {
        return 0;
    }
    int uwday = tmtime.tm_wday;
    int ut = 3600 * tmtime.tm_hour + 60 * tmtime.tm_min + tmtime.tm_sec;
#endif
#else
    tm *tmtime = localtime(&t);
    if (!tmtime) {
        return 0;
    }
#if HAVE_TM_GMTOFF
    return tmtime->tm_gmtoff;
#else
    int lwday = tmtime->tm_wday;
    int lt = 3600 * tmtime->tm_hour + 60 * tmtime->tm_min + tmtime->tm_sec;
    tmtime = gmtime(&t);
    int uwday = tmtime->tm_wday;
    int ut = 3600 * tmtime->tm_hour + 60 * tmtime->tm_min + tmtime->tm_sec;
#endif
#endif
#if ! HAVE_TM_GMTOFF
    if (lwday != uwday) {
        // Adjust for different day
        if (lwday == uwday + 1  || (lwday == 0 && uwday == 6)) {
            lt += 24 * 3600;
        } else {
            lt -= 24 * 3600;
        }
    }
    return lt - ut;
#endif
}

/******************************************************************************/

class KSystemTimeZonesPrivate : public KTimeZones
{
public:
    static KSystemTimeZonesPrivate *instance();
    static KTzfileTimeZoneSource *tzfileSource();
    static void setLocalZone();
    static void cleanup();
    static void readConfig(bool init);
#ifdef Q_OS_WIN
    static void updateTimezoneInformation()
    {
        instance()->updateTimezoneInformation(true);
    }
#else
    static void updateZonetab()
    {
        instance()->readZoneTab(true);
    }
#endif

    static KTimeZone m_localZone;
    static QString m_localZoneName;
    static QString m_zoneinfoDir;
    static QString m_zonetab;
    static KSystemTimeZoneSource *m_source;
    static bool m_ktimezonedError;

private:
    KSystemTimeZonesPrivate() {}
#ifdef Q_OS_WIN
    void updateTimezoneInformation(bool update);
#else
    void readZoneTab(bool update);
    static float convertCoordinate(const QString &coordinate);
#endif

    static KSystemTimeZones *m_parent;
    static KSystemTimeZonesPrivate *m_instance;
    static KTzfileTimeZoneSource *m_tzfileSource;
};

KTimeZone                KSystemTimeZonesPrivate::m_localZone;
QString                  KSystemTimeZonesPrivate::m_localZoneName;
QString                  KSystemTimeZonesPrivate::m_zoneinfoDir;
QString                  KSystemTimeZonesPrivate::m_zonetab;
KSystemTimeZoneSource   *KSystemTimeZonesPrivate::m_source = 0;
bool                     KSystemTimeZonesPrivate::m_ktimezonedError = true;
KTzfileTimeZoneSource   *KSystemTimeZonesPrivate::m_tzfileSource = 0;
KSystemTimeZones        *KSystemTimeZonesPrivate::m_parent = 0;
KSystemTimeZonesPrivate *KSystemTimeZonesPrivate::m_instance = 0;

KTzfileTimeZoneSource *KSystemTimeZonesPrivate::tzfileSource()
{
    if (!m_tzfileSource) {
        instance();
        m_tzfileSource = new KTzfileTimeZoneSource(m_zoneinfoDir);
    }
    return m_tzfileSource;
}

// for the benefit of KTimeZoneTest
KDE4SUPPORT_EXPORT void k_system_time_zone_private_reset_config()
{
    // Remove any old zones from the collection
    const KTimeZones::ZoneMap oldZones = KSystemTimeZonesPrivate::instance()->zones();
    for (KTimeZones::ZoneMap::ConstIterator it = oldZones.constBegin();  it != oldZones.constEnd();  ++it) {
        KSystemTimeZonesPrivate::instance()->remove(it.value());
    }

    // Read new config file
    KSystemTimeZonesPrivate::readConfig(false);
}

#ifndef NDEBUG
Q_GLOBAL_STATIC(KTimeZone, simulatedLocalZone)
#endif

KSystemTimeZones::KSystemTimeZones()
    : d(0)
{
    QDBusConnection dbus = QDBusConnection::sessionBus();
    const QString dbusIface = QString::fromLatin1(KTIMEZONED_DBUS_IFACE);
    dbus.connect(QString(), QString(), dbusIface, QLatin1String("timeZoneChanged"), this, SLOT(configChanged()));
    dbus.connect(QString(), QString(), dbusIface, QLatin1String("timeZoneDatabaseUpdated"), this, SLOT(zonetabChanged(QString)));
    // No need to connect to definitionChanged() - see comments in zoneDefinitionChanged()
    //dbus.connect(QString(), QString(), dbusIface, QLatin1String("definitionChanged"), this, SLOT(zoneDefinitionChanged(QString)));
}

KSystemTimeZones::~KSystemTimeZones()
{
}

KTimeZone KSystemTimeZones::local()
{
#ifndef NDEBUG
    if (simulatedLocalZone()->isValid()) {
        return *simulatedLocalZone();
    }
#endif
    KSystemTimeZonesPrivate::instance();
    return KSystemTimeZonesPrivate::m_localZone;
}

KTimeZone KSystemTimeZones::realLocalZone()
{
    KSystemTimeZonesPrivate::instance();
    return KSystemTimeZonesPrivate::m_localZone;
}

void KSystemTimeZones::setLocalZone(const KTimeZone &tz)
{
    Q_UNUSED(tz);
#ifndef NDEBUG
    *simulatedLocalZone() = tz;
#endif
}

bool KSystemTimeZones::isSimulated()
{
#ifndef NDEBUG
    return simulatedLocalZone()->isValid();
#else
    return false;
#endif
}

QString KSystemTimeZones::zoneinfoDir()
{
    KSystemTimeZonesPrivate::instance();
    return KSystemTimeZonesPrivate::m_zoneinfoDir;
}

bool KSystemTimeZones::isTimeZoneDaemonAvailable()
{
    KSystemTimeZonesPrivate::instance();
    return !KSystemTimeZonesPrivate::m_ktimezonedError;
}

KTimeZones *KSystemTimeZones::timeZones()
{
    return KSystemTimeZonesPrivate::instance();
}

KTimeZone KSystemTimeZones::readZone(const QString &name)
{
    return KTzfileTimeZone(KSystemTimeZonesPrivate::tzfileSource(), name);
}

const KTimeZones::ZoneMap KSystemTimeZones::zones()
{
    return KSystemTimeZonesPrivate::instance()->zones();
}

KTimeZone KSystemTimeZones::zone(const QString &name)
{
    return KSystemTimeZonesPrivate::instance()->zone(name);
}

void KSystemTimeZones::configChanged()
{
    //qDebug() << "KSystemTimeZones::configChanged()";
    KSystemTimeZonesPrivate::m_ktimezonedError = false;
    KSystemTimeZonesPrivate::readConfig(false);
}

void KSystemTimeZones::zonetabChanged(const QString &zonetab)
{
    Q_UNUSED(zonetab)
#ifndef Q_OS_WIN
    //qDebug() << "KSystemTimeZones::zonetabChanged()";
    KSystemTimeZonesPrivate::m_ktimezonedError = false;
    // Re-read zone.tab and update our collection, removing any deleted
    // zones and adding any new zones.
    KSystemTimeZonesPrivate::updateZonetab();
#endif
}

void KSystemTimeZones::zoneDefinitionChanged(const QString &zone)
{
    // No need to do anything when the definition (as opposed to the
    // identity) of the local zone changes, since the updated details
    // will always be accessed by the system library calls to fetch
    // local zone information.
    Q_UNUSED(zone)
    KSystemTimeZonesPrivate::m_ktimezonedError = false;
}

// Perform initialization, create the unique KSystemTimeZones instance,
// whose only function is to receive D-Bus signals from KTimeZoned,
// and create the unique KSystemTimeZonesPrivate instance.
KSystemTimeZonesPrivate *KSystemTimeZonesPrivate::instance()
{
    if (!m_instance) {
        m_instance = new KSystemTimeZonesPrivate;

        // A KSystemTimeZones instance is required only to catch D-Bus signals.
        m_parent = new KSystemTimeZones;
        // Ensure that the KDED time zones module has initialized. The call loads the module on demand.
        QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
        if (!bus->isServiceRegistered(QLatin1String("org.kde.kded5"))) {
            // kded isn't even running: start it
            QDBusReply<void> reply = bus->startService(QLatin1String("org.kde.kded5"));
            if (!reply.isValid()) {
                qWarning() << "Couldn't start kded5 from org.kde.kded5.service:" << reply.error();
            }
        }
        const QString dbusIface = QString::fromLatin1(KTIMEZONED_DBUS_IFACE);
        QDBusInterface *ktimezoned = new QDBusInterface(QLatin1String("org.kde.kded5"), QLatin1String("/modules/ktimezoned"), dbusIface);
        QDBusReply<void> reply = ktimezoned->call(QLatin1String("initialize"), false);
        m_ktimezonedError = !reply.isValid();
        if (m_ktimezonedError) {
            qCritical() << "KSystemTimeZones: ktimezoned initialize() D-Bus call failed: " << reply.error().message() << endl;
        }
//qDebug()<<"instance(): ... initialised";
        delete ktimezoned;

        // Read the time zone config written by ktimezoned
        readConfig(true);

        // Go read the database.
#ifdef Q_OS_WIN
        // On Windows, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
        // is the place to look. The TZI binary value is the TIME_ZONE_INFORMATION structure.
        m_instance->updateTimezoneInformation(false);
#else
        // For Unix, read zone.tab.
        if (!m_zonetab.isEmpty()) {
            m_instance->readZoneTab(false);
        }
#endif
        setLocalZone();

        qAddPostRoutine(KSystemTimeZonesPrivate::cleanup);
    }
    return m_instance;
}

void KSystemTimeZonesPrivate::readConfig(bool init)
{
    KConfig config(QLatin1String("ktimezonedrc"));
    if (!init) {
        config.reparseConfiguration();
    }
    KConfigGroup group(&config, "TimeZones");
    if (!group.exists()) {
        qCritical() << "No time zone information obtained from ktimezoned";
        m_ktimezonedError = true;
    }
    m_zoneinfoDir   = group.readEntry("ZoneinfoDir");
    m_zonetab       = group.readEntry("Zonetab");
    m_localZoneName = group.readEntry("LocalZone");
    if (m_zoneinfoDir.length() > 1 && m_zoneinfoDir.endsWith(QLatin1Char('/'))) {
        m_zoneinfoDir.truncate(m_zoneinfoDir.length() - 1);    // strip trailing '/'
    }
    if (!init) {
        updateZonetab();
        setLocalZone();
    }
    //qDebug() << "readConfig(): local zone=" << m_localZoneName;
}

void KSystemTimeZonesPrivate::setLocalZone()
{
    // Check whether the local zone name is set at all
    if (m_localZoneName.isEmpty()) {
        m_localZone = KTimeZone::utc();
        return;
    }

    // Check if the zone name is a known zone
    if (m_instance) {
        m_localZone = m_instance->zone(m_localZoneName);
        if (m_localZone.isValid()) {
            return;
        }
    }

    QString filename;
    if (m_localZoneName.startsWith(QLatin1Char('/'))) {
        // The time zone is specified by a file outside the zoneinfo directory
        filename = m_localZoneName;
    } else {
        // The zone name is specified by a relative file name in zoneinfo directory.
        filename = m_zoneinfoDir + QLatin1Char('/') + m_localZoneName;
    }

    // Verify that the time zone file actually exists
    if (!QFile::exists(filename)) {
        m_localZone = KTimeZone::utc();
        return;
    }

    // Parse the specified time zone data file
    QString zonename = filename;
    if (zonename.startsWith(m_zoneinfoDir + QLatin1Char('/'))) {
        zonename = zonename.mid(m_zoneinfoDir.length() + 1);
    }
    m_localZone = KTzfileTimeZone(KSystemTimeZonesPrivate::tzfileSource(), zonename);

    // Add the new time zone to the list of known time zones
    if (m_instance) {
        const KTimeZone oldzone = m_instance->zone(zonename);
        if (!oldzone.isValid() || oldzone.type() != "KTzfileTimeZone") {
            m_instance->remove(oldzone);
            m_instance->add(m_localZone);
        }
    }
}

void KSystemTimeZonesPrivate::cleanup()
{
    delete m_parent;
    delete m_instance;
    delete m_source;
    delete m_tzfileSource;
}

#ifdef Q_OS_WIN

void KSystemTimeZonesPrivate::updateTimezoneInformation(bool update)
{
    if (!m_source) {
        m_source = new KSystemTimeZoneSourceWindows;
    }
    QStringList newZones;
    Q_FOREACH (const QString &tz, KSystemTimeZoneWindows::listTimeZones()) {
        // const std::wstring wstr = tz.toStdWString();
        // const KTimeZone info = make_time_zone( wstr.c_str() );
        KSystemTimeZoneWindows stz(m_source, tz);
        if (update) {
            // Update the existing collection with the new zone definition
            newZones += stz.name();
            KTimeZone oldTz = zone(stz.name());
            if (oldTz.isValid()) {
                oldTz.updateBase(stz);    // the zone previously existed, so update its definition
            } else {
                add(stz);    // the zone didn't previously exist, so add it
            }
        } else {
            add(stz);
        }
    }
    if (update) {
        // Remove any zones from the collection which no longer exist
        const ZoneMap oldZones = zones();
        for (ZoneMap::const_iterator it = oldZones.begin();  it != oldZones.end();  ++it) {
            if (newZones.indexOf(it.key()) < 0) {
                remove(it.value());
            }
        }
    }
}

#else
/*
 * Find the location of the zoneinfo files and store in mZoneinfoDir.
 * Parse zone.tab and for each time zone, create a KSystemTimeZone instance.
 */
void KSystemTimeZonesPrivate::readZoneTab(bool update)
{
    //qDebug() << "readZoneTab(" << m_zonetab<< ")";
    QStringList newZones;
    QFile f;
    f.setFileName(m_zonetab);
    if (!f.open(QIODevice::ReadOnly)) {
        return;
    }
    QTextStream str(&f);
    const QRegExp lineSeparator(QLatin1String("[ \t]"));
    const QRegExp ordinateSeparator(QLatin1String("[+-]"));
    if (!m_source) {
        m_source = new KSystemTimeZoneSource;
    }
    while (!str.atEnd()) {
        const QString line = str.readLine();
        if (line.isEmpty() || line[0] == QLatin1Char('#')) {
            continue;
        }
        QStringList tokens = KStringHandler::perlSplit(lineSeparator, line, 4);
        const int n = tokens.count();
        if (n < 3) {
            qCritical() << "readZoneTab(): invalid record: " << line << endl;
            continue;
        }

        // Got three tokens. Now check for two ordinates plus first one is "".
        const int i = tokens[1].indexOf(ordinateSeparator, 1);
        if (i < 0) {
            qCritical() << "readZoneTab() " << tokens[2] << ": invalid coordinates: " << tokens[1] << endl;
            continue;
        }

        const float latitude = convertCoordinate(tokens[1].left(i));
        const float longitude = convertCoordinate(tokens[1].mid(i));

        // Add entry to list.
        if (tokens[0] == QLatin1String("??")) {
            tokens[0] = QString::fromLatin1("");
        }
        // Solaris sets the empty Comments field to '-', making it not empty.
        // Clean it up.
        if (n > 3  && tokens[3] == QLatin1String("-")) {
            tokens[3] = QString::fromLatin1("");
        }
        // Note: KTzfileTimeZone is used in preference to KSystemTimeZone because of
        //       the large overhead incurred by tzset() - see KSystemTimeZones class
        //       description for details.
        const KTzfileTimeZone tz(tzfileSource(), tokens[2], tokens[0], latitude, longitude, (n > 3 ? tokens[3] : QString()));
        if (update) {
            // Update the existing collection with the new zone definition
            newZones += tz.name();
            KTimeZone oldTz = zone(tz.name());
            if (oldTz.isValid()) {
                oldTz.updateBase(tz);    // the zone previously existed, so update its definition
            } else {
                add(tz);    // the zone didn't previously exist, so add it
            }
        } else {
            add(tz);
        }
    }
    f.close();

    if (update) {
        // Remove any zones from the collection which no longer exist
        const ZoneMap oldZones = zones();
        for (ZoneMap::ConstIterator it = oldZones.constBegin();  it != oldZones.constEnd();  ++it) {
            if (newZones.indexOf(it.key()) < 0) {
                remove(it.value());
            }
        }
    }
}

/**
 * Convert sHHMM or sHHMMSS to a floating point number of degrees.
 */
float KSystemTimeZonesPrivate::convertCoordinate(const QString &coordinate)
{
    int value = coordinate.toInt();
    int degrees = 0;
    int minutes = 0;
    int seconds = 0;

    if (coordinate.length() > 6) {
        degrees = value / 10000;
        value -= degrees * 10000;
        minutes = value / 100;
        value -= minutes * 100;
        seconds = value;
    } else {
        degrees = value / 100;
        value -= degrees * 100;
        minutes = value;
    }
    value = degrees * 3600 + minutes * 60 + seconds;
    return value / 3600.0;
}
#endif

/******************************************************************************/

KSystemTimeZoneBackend::KSystemTimeZoneBackend(KSystemTimeZoneSource *source, const QString &name,
        const QString &countryCode, float latitude, float longitude, const QString &comment)
    : KTimeZoneBackend(source, name, countryCode, latitude, longitude, comment)
{}

KSystemTimeZoneBackend::~KSystemTimeZoneBackend()
{}

KTimeZoneBackend *KSystemTimeZoneBackend::clone() const
{
    return new KSystemTimeZoneBackend(*this);
}

QByteArray KSystemTimeZoneBackend::type() const
{
    return "KSystemTimeZone";
}

int KSystemTimeZoneBackend::offsetAtZoneTime(const KTimeZone *caller, const QDateTime &zoneDateTime, int *secondOffset) const
{
    if (!caller->isValid()  ||  !zoneDateTime.isValid()  ||  zoneDateTime.timeSpec() != Qt::LocalTime) {
        return 0;
    }
    // Make this time zone the current local time zone
    const QByteArray originalZone = qgetenv("TZ");   // save the original local time zone
    QByteArray tz = caller->name().toUtf8();
    tz.prepend(":");
    const bool change = (tz != originalZone);
    if (change) {
        qputenv("TZ", tz);
        ::tzset();
    }

    // Convert zone time to UTC, and then get the offset to UTC
    tm tmtime;
    tmtime.tm_sec    = zoneDateTime.time().second();
    tmtime.tm_min    = zoneDateTime.time().minute();
    tmtime.tm_hour   = zoneDateTime.time().hour();
    tmtime.tm_mday   = zoneDateTime.date().day();
    tmtime.tm_mon    = zoneDateTime.date().month() - 1;
    tmtime.tm_year   = zoneDateTime.date().year() - 1900;
    tmtime.tm_isdst  = -1;
    const time_t t = mktime(&tmtime);
    int offset1 = (t == (time_t) - 1) ? KTimeZone::InvalidOffset : gmtoff(t);
    if (secondOffset) {
        int offset2 = offset1;
        if (t != (time_t) - 1) {
            // Check if there is a backward DST change near to this time, by
            // checking if the UTC offset is different 1 hour later or earlier.
            // ASSUMPTION: DST SHIFTS ARE NEVER GREATER THAN 1 HOUR.
            const int maxShift = 3600;
            offset2 = gmtoff(t + maxShift);
            if (offset2 < offset1) {
                // There is a backward DST shift during the following hour
                if (offset1 - offset2 < maxShift) {
                    offset2 = gmtoff(t + (offset1 - offset2));
                }
            } else if ((offset2 = gmtoff(t - maxShift)) > offset1) {
                // There is a backward DST shift during the previous hour
                if (offset2 - offset1 < maxShift) {
                    offset2 = gmtoff(t - (offset2 - offset1));
                }
                // Put UTC offsets into the correct order
                const int o = offset1;
                offset1 = offset2;
                offset2 = o;
            } else {
                offset2 = offset1;
            }
        }
        *secondOffset = offset2;
    }

    if (change) {
        // Restore the original local time zone
        if (originalZone.isEmpty()) {
            ::unsetenv("TZ");
        } else {
            qputenv("TZ", originalZone);
        }
        ::tzset();
    }
    return offset1;
}

int KSystemTimeZoneBackend::offsetAtUtc(const KTimeZone *caller, const QDateTime &utcDateTime) const
{
    return offset(caller, KTimeZone::toTime_t(utcDateTime));
}

int KSystemTimeZoneBackend::offset(const KTimeZone *caller, time_t t) const
{
    if (!caller->isValid()  ||  t == KTimeZone::InvalidTime_t) {
        return 0;
    }

    // Make this time zone the current local time zone
    const QByteArray originalZone = qgetenv("TZ");   // save the original local time zone
    QByteArray tz = caller->name().toUtf8();
    tz.prepend(":");
    const bool change = (tz != originalZone);
    if (change) {
        qputenv("TZ", tz);
        ::tzset();
    }

    const int secs = gmtoff(t);

    if (change) {
        // Restore the original local time zone
        if (originalZone.isEmpty()) {
            ::unsetenv("TZ");
        } else {
            qputenv("TZ", originalZone);
        }
        ::tzset();
    }
    return secs;
}

bool KSystemTimeZoneBackend::isDstAtUtc(const KTimeZone *caller, const QDateTime &utcDateTime) const
{
    return isDst(caller, KTimeZone::toTime_t(utcDateTime));
}

bool KSystemTimeZoneBackend::isDst(const KTimeZone *caller, time_t t) const
{
    Q_UNUSED(caller)
    if (t != (time_t) - 1) {
#ifdef _POSIX_THREAD_SAFE_FUNCTIONS
        tm tmtime;
        if (localtime_r(&t, &tmtime)) {
            return tmtime.tm_isdst > 0;
        }
#else
        const tm *tmtime = localtime(&t);
        if (tmtime) {
            return tmtime->tm_isdst > 0;
        }
#endif
    }
    return false;
}

/******************************************************************************/

KSystemTimeZone::KSystemTimeZone(KSystemTimeZoneSource *source, const QString &name,
                                 const QString &countryCode, float latitude, float longitude, const QString &comment)
    : KTimeZone(new KSystemTimeZoneBackend(source, name, countryCode, latitude, longitude, comment))
{
}

KSystemTimeZone::~KSystemTimeZone()
{
}

/******************************************************************************/

class KSystemTimeZoneDataPrivate
{
public:
    QByteArray TZ;
    QList<QByteArray> abbreviations;
};

// N.B. KSystemTimeZoneSourcePrivate is also used by KSystemTimeZoneData
class KSystemTimeZoneSourcePrivate
{
public:
    static void setTZ(const QByteArray &zoneName);
    static void restoreTZ();
    static QByteArray savedTZ;       // temporary value of TZ environment variable saved by setTZ()
    static QByteArray originalTZ;    // saved value of TZ environment variable during multiple parse() calls
    static bool       TZIsSaved;     // TZ has been saved in savedTZ
    static bool       multiParse;    // true if performing multiple parse() calls
};

QByteArray KSystemTimeZoneSourcePrivate::savedTZ;
QByteArray KSystemTimeZoneSourcePrivate::originalTZ;
bool       KSystemTimeZoneSourcePrivate::TZIsSaved = false;
bool       KSystemTimeZoneSourcePrivate::multiParse = false;

KSystemTimeZoneSource::KSystemTimeZoneSource()
    : d(0)
//  : d(new KSystemTimeZoneSourcePrivate)
{
}

KSystemTimeZoneSource::~KSystemTimeZoneSource()
{
//    delete d;
}

KTimeZoneData *KSystemTimeZoneSource::parse(const KTimeZone &zone) const
{
    const QByteArray tz = zone.name().toUtf8();
    KSystemTimeZoneSourcePrivate::setTZ(tz);   // make this time zone the current local time zone

    tzset();    // initialize the tzname array
    KSystemTimeZoneData *data = new KSystemTimeZoneData;
    data->d->TZ = tz;
    data->d->abbreviations.append(tzname[0]);
    data->d->abbreviations.append(tzname[1]);

    // There is no easy means to access the sequence of daylight savings time
    // changes, or leap seconds adjustments, so leave that data empty.

    KSystemTimeZoneSourcePrivate::restoreTZ();   // restore the original local time zone if necessary
    return data;
}

void KSystemTimeZoneSource::startParseBlock()
{
    KSystemTimeZoneSourcePrivate::originalTZ = qgetenv("TZ");   // save the original local time zone
    KSystemTimeZoneSourcePrivate::multiParse = true;
}

void KSystemTimeZoneSource::endParseBlock()
{
    if (KSystemTimeZoneSourcePrivate::multiParse) {
        // Restore the original local time zone
        if (KSystemTimeZoneSourcePrivate::originalTZ.isEmpty()) {
            ::unsetenv("TZ");
        } else {
            qputenv("TZ", KSystemTimeZoneSourcePrivate::originalTZ);
        }
        ::tzset();
        KSystemTimeZoneSourcePrivate::multiParse = false;
    }
}

// Set the TZ environment variable to the specified time zone,
// saving its current setting first if necessary.
void KSystemTimeZoneSourcePrivate::setTZ(const QByteArray &zoneName)
{
    QByteArray tz = zoneName;
    tz.prepend(":");
    bool setTZ = multiParse;
    if (!setTZ) {
        savedTZ = qgetenv("TZ");   // save the original local time zone
        TZIsSaved = true;
        setTZ = (tz != savedTZ);
    }
    if (setTZ) {
        qputenv("TZ", tz);
        ::tzset();
    }
}

// Restore the TZ environment variable if it was saved by setTz()
void KSystemTimeZoneSourcePrivate::restoreTZ()
{
    if (TZIsSaved) {
        if (savedTZ.isEmpty()) {
            ::unsetenv("TZ");
        } else {
            qputenv("TZ", savedTZ);
        }
        ::tzset();
        TZIsSaved = false;
    }
}

/******************************************************************************/

KSystemTimeZoneData::KSystemTimeZoneData()
    : d(new KSystemTimeZoneDataPrivate)
{ }

KSystemTimeZoneData::KSystemTimeZoneData(const KSystemTimeZoneData &rhs)
    : KTimeZoneData(),
      d(new KSystemTimeZoneDataPrivate)
{
    operator=(rhs);
}

KSystemTimeZoneData::~KSystemTimeZoneData()
{
    delete d;
}

KSystemTimeZoneData &KSystemTimeZoneData::operator=(const KSystemTimeZoneData &rhs)
{
    d->TZ = rhs.d->TZ;
    d->abbreviations = rhs.d->abbreviations;
    return *this;
}

KTimeZoneData *KSystemTimeZoneData::clone() const
{
    return new KSystemTimeZoneData(*this);
}

QList<QByteArray> KSystemTimeZoneData::abbreviations() const
{
    return d->abbreviations;
}

QByteArray KSystemTimeZoneData::abbreviation(const QDateTime &utcDateTime) const
{
    QByteArray abbr;
    if (utcDateTime.timeSpec() != Qt::UTC) {
        return abbr;
    }
    time_t t = utcDateTime.toTime_t();
    if (t != KTimeZone::InvalidTime_t) {
        KSystemTimeZoneSourcePrivate::setTZ(d->TZ);   // make this time zone the current local time zone

        /* Use tm.tm_zone if available because it returns the abbreviation
         * in use at the time specified. Otherwise, use tzname[] which
         * returns the appropriate current abbreviation instead.
         */
#ifdef _POSIX_THREAD_SAFE_FUNCTIONS
        tm tmtime;
        if (localtime_r(&t, &tmtime))
#if HAVE_STRUCT_TM_TM_ZONE
            abbr = tmtime.tm_zone;
#else
            abbr = tzname[(tmtime.tm_isdst > 0) ? 1 : 0];
#endif
#else
        const tm *tmtime = localtime(&t);
        if (tmtime)
#if HAVE_STRUCT_TM_TM_ZONE
            abbr = tmtime->tm_zone;
#else
            abbr = tzname[(tmtime->tm_isdst > 0) ? 1 : 0];
#endif
#endif
        KSystemTimeZoneSourcePrivate::restoreTZ();   // restore the original local time zone if necessary
    }
    return abbr;
}

QList<int> KSystemTimeZoneData::utcOffsets() const
{
    return QList<int>();
}

