/*
    SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>

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

#include "locationinformation.h"

#include <KCountry>
#include <KLazyLocalizedString>
#include <KLocalizedString>

#include <QDebug>

using namespace Qt::Literals;
using namespace KItinerary;

LocationInformation::LocationInformation() = default;
LocationInformation::~LocationInformation() = default;

bool LocationInformation::operator==(const LocationInformation &other) const
{
    const auto dsEqual =
        m_drivingSide == other.m_drivingSide || m_drivingSide == KnowledgeDb::DrivingSide::Unknown || other.m_drivingSide == KnowledgeDb::DrivingSide::Unknown;
    const auto ppEqual = (m_incompatPlugs == other.m_incompatPlugs && m_incompatSockets == other.m_incompatSockets) || m_powerPlugs == KnowledgeDb::Unknown
        || other.m_powerPlugs == KnowledgeDb::Unknown;
    const auto currencyEqual = m_currency == other.m_currency || m_currency.isEmpty() || other.m_currency.isEmpty();

    return dsEqual && ppEqual && !hasRelevantTimeZoneChange(other) && currencyEqual;
}

QString LocationInformation::isoCode() const
{
    return m_isoCode;
}

// see https://en.wikipedia.org/wiki/European_Union_roaming_regulations
static constexpr const QLatin1StringView eu_roaming_map[] = {
    "AT"_L1,
    "BE"_L1,
    "BG"_L1,
    "CY"_L1,
    "DE"_L1,
    "DK"_L1,
    "EE"_L1,
    "ES"_L1,
    "FI"_L1,
    "FR"_L1,
    "GR"_L1,
    "HR"_L1,
    "HU"_L1,
    "IE"_L1,
    "IS"_L1,
    "IT"_L1,
    "LI"_L1,
    "LT"_L1,
    "LU"_L1,
    "LV"_L1,
    "MD"_L1,
    "MT"_L1,
    "NL"_L1,
    "NO"_L1,
    "PL"_L1,
    "PT"_L1,
    "RO"_L1,
    "SE"_L1,
    "SI"_L1,
    "SK"_L1,
    "UA"_L1,
};

void LocationInformation::setIsoCode(const QString &isoCode)
{
    if (m_isoCode == isoCode) {
        return;
    }

    if (!m_isoCode.isEmpty() && !isoCode.isEmpty()) {
        m_leavingEURoaming = std::binary_search(std::begin(eu_roaming_map), std::end(eu_roaming_map), m_isoCode) && !std::binary_search(std::begin(eu_roaming_map), std::end(eu_roaming_map), isoCode);
    }

    m_isoCode = isoCode;

    const auto id = KnowledgeDb::CountryId{isoCode};
    if (!id.isValid()) {
        setDrivingSide(KnowledgeDb::DrivingSide::Unknown);
        setPowerPlugTypes(KnowledgeDb::Unknown);
        return;
    }
    const auto countryRecord = KnowledgeDb::countryForId(id);
    setDrivingSide(countryRecord.drivingSide);
    setPowerPlugTypes(countryRecord.powerPlugTypes);

    auto currency = KCountry::fromAlpha2(isoCode).currencyCode();
    if (currency != m_currency && !m_currency.isEmpty() && !currency.isEmpty()) {
        m_currencyDiffers = true;
    }
    m_currency = currency;
}

KnowledgeDb::DrivingSide LocationInformation::drivingSide() const
{
    return m_drivingSide;
}

void LocationInformation::setDrivingSide(KnowledgeDb::DrivingSide drivingSide)
{
    if (m_drivingSide == drivingSide) {
        return;
    }

    if (m_drivingSide != KnowledgeDb::DrivingSide::Unknown) {
        m_drivingSideDiffers = true;
    }

    m_drivingSide = drivingSide;
}

bool LocationInformation::drivingSideDiffers() const
{
    return m_drivingSideDiffers;
}

QString LocationInformation::drivingSideLabel() const
{
    switch (m_drivingSide) {
    case KItinerary::KnowledgeDb::DrivingSide::Right:
        return i18n("People are driving on the right side.");
    case KItinerary::KnowledgeDb::DrivingSide::Left:
        return i18n("People are driving on the left side.");
    case KItinerary::KnowledgeDb::DrivingSide::Unknown:
        return {};
    }
    return {};
}

LocationInformation::PowerPlugCompatibility LocationInformation::powerPlugCompatibility() const
{
    return m_powerPlugCompat;
}

struct plugTypeName {
    KnowledgeDb::PowerPlugType type;
    const KLazyLocalizedString name;
}

static const plug_name_table[] = {
    {KnowledgeDb::TypeA, kli18n("Type A")},
    {KnowledgeDb::TypeB, kli18n("Type B")},
    {KnowledgeDb::TypeC, kli18n("Europlug")},
    {KnowledgeDb::TypeD, kli18n("Type D")},
    {KnowledgeDb::TypeE, kli18n("Type E")},
    {KnowledgeDb::TypeF, kli18n("Schuko")},
    {KnowledgeDb::TypeG, kli18n("Type G")},
    {KnowledgeDb::TypeH, kli18n("Type H")},
    {KnowledgeDb::TypeI, kli18n("Type I")},
    {KnowledgeDb::TypeJ, kli18n("Type J")},
    {KnowledgeDb::TypeK, kli18n("Type K")},
    {KnowledgeDb::TypeL, kli18n("Type L")},
    {KnowledgeDb::TypeM, kli18n("Type M")},
    {KnowledgeDb::TypeN, kli18n("Type N")},
};

static QString plugTypesToString(KnowledgeDb::PowerPlugTypes type)
{
    QStringList l;
    for (const auto &elem : plug_name_table) {
        if (type & elem.type) {
            l.push_back(KLocalizedString(elem.name).toString());
        }
    }
    return l.join(QLatin1StringView(", "));
}

QString LocationInformation::powerPlugTypes() const
{
    return plugTypesToString(m_incompatPlugs);
}

QString LocationInformation::powerSocketTypes() const
{
    return plugTypesToString(m_incompatSockets);
}

void LocationInformation::setPowerPlugTypes(KItinerary::KnowledgeDb::PowerPlugTypes powerPlugs)
{
    if (m_powerPlugs == powerPlugs) {
        return;
    }

    if (powerPlugs != KnowledgeDb::Unknown && m_powerPlugs != KnowledgeDb::Unknown) {
        m_incompatPlugs = KnowledgeDb::incompatiblePowerPlugs(m_powerPlugs, powerPlugs);
        m_incompatSockets = KnowledgeDb::incompatiblePowerSockets(m_powerPlugs, powerPlugs);

        if ((m_powerPlugs & powerPlugs) == 0) {
            m_powerPlugCompat = Incompatible;
        } else if (m_incompatPlugs != KnowledgeDb::Unknown || m_incompatSockets != KnowledgeDb::Unknown) {
            m_powerPlugCompat = PartiallyCompatible;
        }
    }

    m_powerPlugs = powerPlugs;
}

QTimeZone LocationInformation::timeZone() const
{
    return m_timeZone;
}

QDateTime LocationInformation::transitionTime() const
{
    return m_transitionTime;
}

void LocationInformation::setTimeZone(const QTimeZone &tz, const QDateTime &transitionTime)
{
    if (m_timeZone.isValid() && tz.isValid()) {
        m_timeZoneOffsetDelta = tz.offsetFromUtc(transitionTime) - m_timeZone.offsetFromUtc(transitionTime);
    } else {
        m_timeZoneOffsetDelta = 0;
    }
    m_timeZone = tz;
    m_transitionTime = transitionTime;

    // check if this is a DST transition
    // the date logic is a special here as date/time math around DST changes behaves ... interestingly
    if (m_timeZoneOffsetDelta == 0 && m_timeZone.isValid() && m_timeZone.hasDaylightTime() && m_timeZone.hasTransitions()) {
        const auto nextTrans = m_timeZone.nextTransition(transitionTime.addSecs(-1));
        if (std::abs(nextTrans.atUtc.secsTo(transitionTime)) <= 3600) {
            m_timeZoneOffsetDelta = m_timeZone.offsetFromUtc(transitionTime.addSecs(3601)) - m_timeZone.offsetFromUtc(transitionTime.addSecs(-3601));
        }
    }
}

bool LocationInformation::hasRelevantTimeZoneChange(const LocationInformation &other) const
{
    return m_timeZone.isValid() && other.m_timeZone.isValid() && m_timeZone.offsetFromUtc(m_transitionTime) != other.m_timeZone.offsetFromUtc(m_transitionTime);
}

bool LocationInformation::timeZoneDiffers() const
{
    return m_timeZoneOffsetDelta != 0 && m_timeZone.isValid();
}

QString LocationInformation::timeZoneName() const
{
    return m_timeZone.displayName(m_transitionTime, QTimeZone::LongName);
}

int LocationInformation::timeZoneOffsetDelta() const
{
    return m_timeZoneOffsetDelta;
}

bool LocationInformation::currencyDiffers() const
{
    return m_currencyDiffers;
}

QString LocationInformation::currencyCode() const
{
    return m_currency;
}

bool LocationInformation::dstDiffers() const
{
    return timeZoneDiffers() && m_isoCode.isEmpty();
}

bool LocationInformation::isDst() const
{
    return m_timeZone.isDaylightTime(m_transitionTime.addSecs(3601));
}

bool LocationInformation::leavingEURoaming() const
{
    return m_leavingEURoaming;
}

#include "moc_locationinformation.cpp"
