/*
    SPDX-FileCopyrightText: 2009 Thilo-Alexander Ginkel <thilo@ginkel.com>

    Based upon BBC Weather Ion by Shawn Starr
    SPDX-FileCopyrightText: 2007-2009 Shawn Starr <shawn.starr@rogers.com>

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

/* Ion for weather data from wetter.com */

// Sample URLs:
// https://api.wetter.com/location/index/search/Heidelberg/project/weatherion/cs/9090dec6e783b96bd6a6ca9d451f3fee
// https://api.wetter.com/forecast/weather/city/DE0004329/project/weatherion/cs/89f1264869cce5c6fd5a2db80051f3d8

#include "ion_wettercom.h"

#include "ion_wettercomdebug.h"

#include <KIO/TransferJob>
#include <KLocalizedString>
#include <KUnitConversion/Converter>

#include <QCryptographicHash>
#include <QLocale>
#include <QXmlStreamReader>

/*
 * Initialization
 */

WetterComIon::WetterComIon(QObject *parent)
    : IonInterface(parent)

{
#if defined(MIN_POLL_INTERVAL)
    setMinimumPollingInterval(MIN_POLL_INTERVAL);
#endif
    setInitialized(true);
}

WetterComIon::~WetterComIon()
{
    cleanup();
}

void WetterComIon::cleanup()
{
    // Clean up dynamically allocated forecasts
    QMutableHashIterator<QString, WeatherData> it(m_weatherData);
    while (it.hasNext()) {
        it.next();
        WeatherData &item = it.value();
        qDeleteAll(item.forecasts);
        item.forecasts.clear();
    }
}

void WetterComIon::reset()
{
    cleanup();
    m_sourcesToReset = sources();
    updateAllSources();
}

QMap<QString, IonInterface::ConditionIcons> WetterComIon::setupCommonIconMappings() const
{
    return QMap<QString, ConditionIcons>{
        {QStringLiteral("3"), Overcast},
        {QStringLiteral("30"), Overcast},
        {QStringLiteral("4"), Haze},
        {QStringLiteral("40"), Haze},
        {QStringLiteral("45"), Haze},
        {QStringLiteral("48"), Haze},
        {QStringLiteral("49"), Haze},
        {QStringLiteral("5"), Mist},
        {QStringLiteral("50"), Mist},
        {QStringLiteral("51"), Mist},
        {QStringLiteral("53"), Mist},
        {QStringLiteral("55"), Mist},
        {QStringLiteral("56"), FreezingDrizzle},
        {QStringLiteral("57"), FreezingDrizzle},
        {QStringLiteral("6"), Rain},
        {QStringLiteral("60"), LightRain},
        {QStringLiteral("61"), LightRain},
        {QStringLiteral("63"), Rain},
        {QStringLiteral("65"), Rain},
        {QStringLiteral("66"), FreezingRain},
        {QStringLiteral("67"), FreezingRain},
        {QStringLiteral("68"), RainSnow},
        {QStringLiteral("69"), RainSnow},
        {QStringLiteral("7"), Snow},
        {QStringLiteral("70"), LightSnow},
        {QStringLiteral("71"), LightSnow},
        {QStringLiteral("73"), Snow},
        {QStringLiteral("75"), Flurries},
        {QStringLiteral("8"), Showers},
        {QStringLiteral("81"), Showers},
        {QStringLiteral("82"), Showers},
        {QStringLiteral("83"), RainSnow},
        {QStringLiteral("84"), RainSnow},
        {QStringLiteral("85"), Snow},
        {QStringLiteral("86"), Snow},
        {QStringLiteral("9"), Thunderstorm},
        {QStringLiteral("90"), Thunderstorm},
        {QStringLiteral("96"), Thunderstorm},
        {QStringLiteral("999"), NotAvailable},
    };
}

QMap<QString, IonInterface::ConditionIcons> WetterComIon::setupDayIconMappings() const
{
    QMap<QString, ConditionIcons> conditionList = setupCommonIconMappings();

    conditionList.insert(QStringLiteral("0"), ClearDay);
    conditionList.insert(QStringLiteral("1"), FewCloudsDay);
    conditionList.insert(QStringLiteral("10"), FewCloudsDay);
    conditionList.insert(QStringLiteral("2"), PartlyCloudyDay);
    conditionList.insert(QStringLiteral("20"), PartlyCloudyDay);
    conditionList.insert(QStringLiteral("80"), ChanceShowersDay);
    conditionList.insert(QStringLiteral("95"), ChanceThunderstormDay);

    return conditionList;
}

QMap<QString, IonInterface::ConditionIcons> const &WetterComIon::dayIcons() const
{
    static QMap<QString, ConditionIcons> const val = setupDayIconMappings();
    return val;
}

QMap<QString, IonInterface::ConditionIcons> WetterComIon::setupNightIconMappings() const
{
    QMap<QString, ConditionIcons> conditionList = setupCommonIconMappings();

    conditionList.insert(QStringLiteral("0"), ClearNight);
    conditionList.insert(QStringLiteral("1"), FewCloudsNight);
    conditionList.insert(QStringLiteral("10"), FewCloudsNight);
    conditionList.insert(QStringLiteral("2"), PartlyCloudyNight);
    conditionList.insert(QStringLiteral("20"), PartlyCloudyNight);
    conditionList.insert(QStringLiteral("80"), ChanceShowersNight);
    conditionList.insert(QStringLiteral("95"), ChanceThunderstormNight);

    return conditionList;
}

QMap<QString, IonInterface::ConditionIcons> const &WetterComIon::nightIcons() const
{
    static QMap<QString, ConditionIcons> const val = setupNightIconMappings();
    return val;
}

QHash<QString, QString> WetterComIon::setupCommonConditionMappings() const
{
    return QHash<QString, QString>{
        {QStringLiteral("1"), i18nc("weather condition", "few clouds")},
        {QStringLiteral("10"), i18nc("weather condition", "few clouds")},
        {QStringLiteral("2"), i18nc("weather condition", "cloudy")},
        {QStringLiteral("20"), i18nc("weather condition", "cloudy")},
        {QStringLiteral("3"), i18nc("weather condition", "overcast")},
        {QStringLiteral("30"), i18nc("weather condition", "overcast")},
        {QStringLiteral("4"), i18nc("weather condition", "haze")},
        {QStringLiteral("40"), i18nc("weather condition", "haze")},
        {QStringLiteral("45"), i18nc("weather condition", "haze")},
        {QStringLiteral("48"), i18nc("weather condition", "fog with icing")},
        {QStringLiteral("49"), i18nc("weather condition", "fog with icing")},
        {QStringLiteral("5"), i18nc("weather condition", "drizzle")},
        {QStringLiteral("50"), i18nc("weather condition", "drizzle")},
        {QStringLiteral("51"), i18nc("weather condition", "light drizzle")},
        {QStringLiteral("53"), i18nc("weather condition", "drizzle")},
        {QStringLiteral("55"), i18nc("weather condition", "heavy drizzle")},
        {QStringLiteral("56"), i18nc("weather condition", "freezing drizzle")},
        {QStringLiteral("57"), i18nc("weather condition", "heavy freezing drizzle")},
        {QStringLiteral("6"), i18nc("weather condition", "rain")},
        {QStringLiteral("60"), i18nc("weather condition", "light rain")},
        {QStringLiteral("61"), i18nc("weather condition", "light rain")},
        {QStringLiteral("63"), i18nc("weather condition", "moderate rain")},
        {QStringLiteral("65"), i18nc("weather condition", "heavy rain")},
        {QStringLiteral("66"), i18nc("weather condition", "light freezing rain")},
        {QStringLiteral("67"), i18nc("weather condition", "freezing rain")},
        {QStringLiteral("68"), i18nc("weather condition", "light rain snow")},
        {QStringLiteral("69"), i18nc("weather condition", "heavy rain snow")},
        {QStringLiteral("7"), i18nc("weather condition", "snow")},
        {QStringLiteral("70"), i18nc("weather condition", "light snow")},
        {QStringLiteral("71"), i18nc("weather condition", "light snow")},
        {QStringLiteral("73"), i18nc("weather condition", "moderate snow")},
        {QStringLiteral("75"), i18nc("weather condition", "heavy snow")},
        {QStringLiteral("8"), i18nc("weather condition", "showers")},
        {QStringLiteral("80"), i18nc("weather condition", "light showers")},
        {QStringLiteral("81"), i18nc("weather condition", "showers")},
        {QStringLiteral("82"), i18nc("weather condition", "heavy showers")},
        {QStringLiteral("83"), i18nc("weather condition", "light snow rain showers")},
        {QStringLiteral("84"), i18nc("weather condition", "heavy snow rain showers")},
        {QStringLiteral("85"), i18nc("weather condition", "light snow showers")},
        {QStringLiteral("86"), i18nc("weather condition", "snow showers")},
        {QStringLiteral("9"), i18nc("weather condition", "thunderstorm")},
        {QStringLiteral("90"), i18nc("weather condition", "thunderstorm")},
        {QStringLiteral("95"), i18nc("weather condition", "light thunderstorm")},
        {QStringLiteral("96"), i18nc("weather condition", "heavy thunderstorm")},
        {QStringLiteral("999"), i18nc("weather condition", "n/a")},
    };
}

QHash<QString, QString> WetterComIon::setupDayConditionMappings() const
{
    QHash<QString, QString> conditionList = setupCommonConditionMappings();
    conditionList.insert(QStringLiteral("0"), i18nc("weather condition", "sunny"));
    return conditionList;
}

QHash<QString, QString> const &WetterComIon::dayConditions() const
{
    static QHash<QString, QString> const val = setupDayConditionMappings();
    return val;
}

QHash<QString, QString> WetterComIon::setupNightConditionMappings() const
{
    QHash<QString, QString> conditionList = setupCommonConditionMappings();
    conditionList.insert(QStringLiteral("0"), i18nc("weather condition", "clear sky"));
    return conditionList;
}

QHash<QString, QString> const &WetterComIon::nightConditions() const
{
    static QHash<QString, QString> const val = setupNightConditionMappings();
    return val;
}

QString WetterComIon::getWeatherCondition(const QHash<QString, QString> &conditionList, const QString &condition) const
{
    return conditionList[condition];
}

bool WetterComIon::updateIonSource(const QString &source)
{
    // We expect the applet to send the source in the following tokenization:
    // ionname|validate|place_name|extra - Triggers validation of place
    // ionname|weather|place_name|extra - Triggers receiving weather of place

    const QStringList sourceAction = source.split(QLatin1Char('|'));

    if (sourceAction.size() < 3) {
        setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed"));
        return true;
    }

    if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() >= 3) {
        // Look for places to match
        findPlace(sourceAction[2], source);
        return true;
    }

    if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() >= 3) {
        if (sourceAction.count() >= 4) {
            if (sourceAction[3].isEmpty()) {
                setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed"));
                return true;
            }

            // Extra data format: placeCode;displayName
            const QStringList extraData = sourceAction[3].split(QLatin1Char(';'));
            if (extraData.count() != 2) {
                setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed"));
                return true;
            }

            const QString &placeCode = extraData[0];
            m_place[placeCode].name = sourceAction[2];
            m_place[placeCode].placeCode = placeCode;
            m_place[placeCode].displayName = extraData[1];

            qCDebug(IONENGINE_WETTERCOM) << "About to retrieve forecast for source: " << placeCode << sourceAction[2];

            fetchForecast(placeCode);

            return true;
        }

        return false;
    }

    setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed"));
    return true;
}

/*
 * Handling of place searches
 */

void WetterComIon::findPlace(const QString &place, const QString &source)
{
    QCryptographicHash md5(QCryptographicHash::Md5);
    md5.addData(QByteArrayLiteral(PROJECTNAME));
    md5.addData(QByteArrayLiteral(APIKEY));
    md5.addData(place.toUtf8());
    const QString encodedKey = QString::fromLatin1(md5.result().toHex());

    const QUrl url(QStringLiteral(SEARCH_URL).arg(place, encodedKey));
    qCDebug(IONENGINE_WETTERCOM) << "Search URL for" << place << url;

    KIO::TransferJob *getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo);
    getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); // Disable displaying cookies
    m_searchJobXml.insert(getJob, new QXmlStreamReader);
    m_searchJobList.insert(getJob, source);

    connect(getJob, &KIO::TransferJob::data, this, &WetterComIon::setup_slotDataArrived);
    connect(getJob, &KJob::result, this, &WetterComIon::setup_slotJobFinished);
}

void WetterComIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data)
{
    QByteArray local = data;

    if (data.isEmpty() || !m_searchJobXml.contains(job)) {
        return;
    }

    m_searchJobXml[job]->addData(local);
}

void WetterComIon::setup_slotJobFinished(KJob *job)
{
    if (job->error() == KIO::ERR_SERVER_TIMEOUT) {
        setData(m_searchJobList[job], QStringLiteral("validate"), QStringLiteral("wettercom|timeout"));
        disconnectSource(m_searchJobList[job], this);
        m_searchJobList.remove(job);
        delete m_searchJobXml[job];
        m_searchJobXml.remove(job);
        return;
    }

    QXmlStreamReader *reader = m_searchJobXml.value(job);

    if (reader) {
        parseSearchResults(m_searchJobList[job], *reader);
    }

    m_searchJobList.remove(job);

    delete m_searchJobXml[job];
    m_searchJobXml.remove(job);
}

void WetterComIon::parseSearchResults(const QString &source, QXmlStreamReader &xml)
{
    QString name, code, quarter, state, country;

    while (!xml.atEnd()) {
        xml.readNext();

        const auto elementName = xml.name();

        if (xml.isEndElement()) {
            if (elementName == QLatin1String("search")) {
                break;
            } else if (elementName == QLatin1String("item")) {
                // we parsed a place from the search result
                QString placeName;

                if (quarter.isEmpty()) {
                    placeName = i18nc("Geographical location: city, state, ISO-country-code", "%1, %2, %3", name, state, country);
                } else {
                    placeName = i18nc("Geographical location: quarter (city), state, ISO-country-code", "%1 (%2), %3, %4", quarter, name, state, country);
                }

                qCDebug(IONENGINE_WETTERCOM) << "Storing place data for place:" << placeName << "with code" << code;

                PlaceInfo &place = m_place[code];
                place.name = placeName;
                place.displayName = name;
                place.placeCode = code;
                m_locations.append(code);

                name.clear();
                code.clear();
                quarter.clear();
                country.clear();
                state.clear();
            }
        }

        if (xml.isStartElement()) {
            if (elementName == QLatin1String("name")) {
                name = xml.readElementText();
            } else if (elementName == QLatin1String("city_code")) {
                code = xml.readElementText();
            } else if (elementName == QLatin1String("quarter")) {
                quarter = xml.readElementText();
            } else if (elementName == QLatin1String("adm_1_code")) {
                country = xml.readElementText();
            } else if (elementName == QLatin1String("adm_2_name")) {
                state = xml.readElementText();
            }
        }
    }

    validate(source, xml.error() != QXmlStreamReader::NoError);
}

void WetterComIon::validate(const QString &source, bool parseError)
{
    if (m_locations.isEmpty() || parseError) {
        const QString invalidPlace = source.section(QLatin1Char('|'), 2, 2);

        if (m_place[invalidPlace].name.isEmpty()) {
            setData(source, QStringLiteral("validate"), QVariant(QString(u"wettercom|invalid|multiple|" + invalidPlace)));
        }

        m_locations.clear();

        return;
    }

    QString placeList;
    for (const QString &code : std::as_const(m_locations)) {
        const PlaceInfo &place = m_place[code];
        // Extra data format: placeCode;displayName
        placeList.append(QStringLiteral("|place|%1|extra|%2;%3").arg(place.name, place.placeCode, place.displayName));
    }

    qCDebug(IONENGINE_WETTERCOM) << "Returning place list:" << placeList;

    if (m_locations.count() > 1) {
        setData(source, QStringLiteral("validate"), QVariant(QString(u"wettercom|valid|multiple" + placeList)));
    } else {
        placeList[7] = placeList[7].toUpper();
        setData(source, QStringLiteral("validate"), QVariant(QString(u"wettercom|valid|single" + placeList)));
    }

    m_locations.clear();
}

/*
 * Handling of forecasts
 */

void WetterComIon::fetchForecast(const QString &source)
{
    for (const QString &fetching : std::as_const(m_forecastJobList)) {
        if (fetching == source) {
            // already fetching!
            return;
        }
    }

    QCryptographicHash md5(QCryptographicHash::Md5);
    md5.addData(QByteArrayLiteral(PROJECTNAME));
    md5.addData(QByteArrayLiteral(APIKEY));
    md5.addData(m_place[source].placeCode.toUtf8());
    const QString encodedKey = QString::fromLatin1(md5.result().toHex());

    const QUrl url(QStringLiteral(FORECAST_URL).arg(m_place[source].placeCode, encodedKey));
    qCDebug(IONENGINE_WETTERCOM) << "Fetch Weather URL for" << source << url;

    KIO::TransferJob *getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo);
    getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none"));
    m_forecastJobXml.insert(getJob, new QXmlStreamReader);
    m_forecastJobList.insert(getJob, source);

    connect(getJob, &KIO::TransferJob::data, this, &WetterComIon::forecast_slotDataArrived);
    connect(getJob, &KJob::result, this, &WetterComIon::forecast_slotJobFinished);
}

void WetterComIon::forecast_slotDataArrived(KIO::Job *job, const QByteArray &data)
{
    QByteArray local = data;

    if (data.isEmpty() || !m_forecastJobXml.contains(job)) {
        return;
    }

    m_forecastJobXml[job]->addData(local);
}

void WetterComIon::forecast_slotJobFinished(KJob *job)
{
    const QString source(m_forecastJobList.value(job));
    setData(source, Data());
    QXmlStreamReader *reader = m_forecastJobXml.value(job);

    if (reader) {
        parseWeatherForecast(source, *reader);
    }

    m_forecastJobList.remove(job);

    delete m_forecastJobXml[job];
    m_forecastJobXml.remove(job);

    if (m_sourcesToReset.contains(source)) {
        m_sourcesToReset.removeAll(source);
        const PlaceInfo &place = m_place[source];
        const QString weatherSource = QStringLiteral("wettercom|weather|%1|%2;%3").arg(place.name, place.placeCode, place.displayName);

        // so the weather engine updates it's data
        forceImmediateUpdateOfAllVisualizations();

        // update the clients of our engine
        Q_EMIT forceUpdate(this, weatherSource);
    }
}

void WetterComIon::parseWeatherForecast(const QString &source, QXmlStreamReader &xml)
{
    qCDebug(IONENGINE_WETTERCOM) << "About to parse forecast for source:" << source;

    WeatherData &weatherData = m_weatherData[source];

    // Clear old forecasts when updating
    weatherData.forecasts.clear();

    WeatherData::ForecastPeriod *forecastPeriod = new WeatherData::ForecastPeriod;
    WeatherData::ForecastInfo *forecast = new WeatherData::ForecastInfo;
    int summaryWeather = -1, summaryProbability = 0;
    int tempMax = -273, tempMin = 100, weather = -1, probability = 0;
    uint summaryUtcTime = 0, utcTime = 0, localTime = 0;
    QString date, time;

    weatherData.place = m_place[source].name;

    while (!xml.atEnd()) {
        xml.readNext();

        const auto elementName = xml.name();

        if (xml.isEndElement()) {
            if (elementName == QLatin1String("city")) {
                break;
            }
            if (elementName == QLatin1String("date")) {
                // we have parsed a complete day

                forecastPeriod->period = QDateTime::fromSecsSinceEpoch(summaryUtcTime, Qt::LocalTime);
                QString weatherString = QString::number(summaryWeather);
                forecastPeriod->iconName = getWeatherIcon(dayIcons(), weatherString);
                forecastPeriod->summary = getWeatherCondition(dayConditions(), weatherString);
                forecastPeriod->probability = summaryProbability;

                weatherData.forecasts.append(forecastPeriod);
                forecastPeriod = new WeatherData::ForecastPeriod;

                date.clear();
                summaryWeather = -1;
                summaryProbability = 0;
                summaryUtcTime = 0;
            } else if (elementName == QLatin1String("time")) {
                // we have parsed one forecast
                // yep, that field is written to more often than needed...
                weatherData.timeDifference = localTime - utcTime;

                forecast->period = QDateTime::fromSecsSinceEpoch(utcTime, Qt::LocalTime);
                QString weatherString = QString::number(weather);
                forecast->tempHigh = tempMax;
                forecast->tempLow = tempMin;
                forecast->probability = probability;

                QTime localWeatherTime = QDateTime::fromSecsSinceEpoch(utcTime, Qt::LocalTime).time();
                localWeatherTime = localWeatherTime.addSecs(weatherData.timeDifference);

                // TODO use local sunset/sunrise time

                if (localWeatherTime.hour() < 20 && localWeatherTime.hour() > 6) {
                    forecast->iconName = getWeatherIcon(dayIcons(), weatherString);
                    forecast->summary = getWeatherCondition(dayConditions(), weatherString);
                    forecastPeriod->dayForecasts.append(forecast);
                } else {
                    forecast->iconName = getWeatherIcon(nightIcons(), weatherString);
                    forecast->summary = getWeatherCondition(nightConditions(), weatherString);
                    forecastPeriod->nightForecasts.append(forecast);
                }

                forecast = new WeatherData::ForecastInfo;

                tempMax = -273;
                tempMin = 100;
                weather = -1;
                probability = 0;
                utcTime = localTime = 0;
                time.clear();
            }
        }

        if (xml.isStartElement()) {
            if (elementName == QLatin1String("date")) {
                date = xml.attributes().value(u"value").toString();
            } else if (elementName == QLatin1String("time")) {
                time = xml.attributes().value(u"value").toString();
            } else if (elementName == QLatin1String("tx")) {
                tempMax = qRound(xml.readElementText().toDouble());
            } else if (elementName == QLatin1String("tn")) {
                tempMin = qRound(xml.readElementText().toDouble());
            } else if (elementName == QLatin1Char('w')) {
                int tmp = xml.readElementText().toInt();

                if (!time.isEmpty())
                    weather = tmp;
                else
                    summaryWeather = tmp;

            } else if (elementName == QLatin1String("name")) {
                weatherData.stationName = xml.readElementText();
            } else if (elementName == QLatin1String("pc")) {
                int tmp = xml.readElementText().toInt();

                if (!time.isEmpty())
                    probability = tmp;
                else
                    summaryProbability = tmp;

            } else if (elementName == QLatin1String("text")) {
                weatherData.credits = xml.readElementText();
            } else if (elementName == QLatin1String("link")) {
                weatherData.creditsUrl = xml.readElementText();
            } else if (elementName == QLatin1Char('d')) {
                localTime = xml.readElementText().toInt();
            } else if (elementName == QLatin1String("du")) {
                int tmp = xml.readElementText().toInt();

                if (!time.isEmpty())
                    utcTime = tmp;
                else
                    summaryUtcTime = tmp;
            }
        }
    }

    delete forecast;
    delete forecastPeriod;

    updateWeather(source, xml.error() != QXmlStreamReader::NoError);
}

void WetterComIon::updateWeather(const QString &source, bool parseError)
{
    qCDebug(IONENGINE_WETTERCOM) << "Source:" << source;

    const PlaceInfo &placeInfo = m_place[source];

    QString weatherSource = QStringLiteral("wettercom|weather|%1|%2;%3").arg(placeInfo.name, placeInfo.placeCode, placeInfo.displayName);

    const WeatherData &weatherData = m_weatherData[source];

    Plasma5Support::DataEngine::Data data;
    data.insert(QStringLiteral("Place"), placeInfo.displayName);

    if (!parseError && !weatherData.forecasts.isEmpty()) {
        data.insert(QStringLiteral("Station"), placeInfo.name);
        // data.insert("Condition Icon", "N/A");
        // data.insert("Temperature", "N/A");
        data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Celsius);

        int i = 0;
        for (const WeatherData::ForecastPeriod *forecastPeriod : weatherData.forecasts) {
            if (i > 0) {
                WeatherData::ForecastInfo weather = forecastPeriod->getWeather();

                data.insert(QStringLiteral("Short Forecast Day %1").arg(i),
                            QStringLiteral("%1|%2|%3|%4|%5|%6")
                                .arg(QLocale().toString(weather.period.date().day()), weather.iconName, weather.summary)
                                .arg(weather.tempHigh)
                                .arg(weather.tempLow)
                                .arg(weather.probability));
                i++;
            } else {
                WeatherData::ForecastInfo dayWeather = forecastPeriod->getDayWeather();

                data.insert(QStringLiteral("Short Forecast Day %1").arg(i),
                            QStringLiteral("%1|%2|%3|%4|%5|%6")
                                .arg(i18n("Day"), dayWeather.iconName, dayWeather.summary)
                                .arg(dayWeather.tempHigh)
                                .arg(dayWeather.tempLow)
                                .arg(dayWeather.probability));
                i++;

                if (forecastPeriod->hasNightWeather()) {
                    WeatherData::ForecastInfo nightWeather = forecastPeriod->getNightWeather();
                    data.insert(QStringLiteral("Short Forecast Day %1").arg(i),
                                QStringLiteral("%1 nt|%2|%3|%4|%5|%6")
                                    .arg(i18n("Night"), nightWeather.iconName, nightWeather.summary)
                                    .arg(nightWeather.tempHigh)
                                    .arg(nightWeather.tempLow)
                                    .arg(nightWeather.probability));
                    i++;
                }
            }
        }

        // Set number of forecasts per day/night supported
        data.insert(QStringLiteral("Total Weather Days"), i);

        data.insert(QStringLiteral("Credit"), weatherData.credits); // FIXME i18n?
        data.insert(QStringLiteral("Credit Url"), QStringLiteral("%1/place/%2.html").arg(weatherData.creditsUrl, placeInfo.placeCode));

        qCDebug(IONENGINE_WETTERCOM) << "updated weather data:" << weatherSource << data;
    } else {
        qCDebug(IONENGINE_WETTERCOM) << "Something went wrong when parsing weather data for source:" << source << placeInfo.displayName;
    }

    Q_EMIT cleanUpData(source);
    setData(weatherSource, data);
}

/*
 * WeatherData::ForecastPeriod convenience methods
 */

WeatherData::ForecastPeriod::~ForecastPeriod()
{
    qDeleteAll(dayForecasts);
    qDeleteAll(nightForecasts);
}

WeatherData::ForecastInfo WeatherData::ForecastPeriod::getDayWeather() const
{
    WeatherData::ForecastInfo result;
    result.period = period;
    result.iconName = iconName;
    result.summary = summary;
    result.tempHigh = getMaxTemp(dayForecasts);
    result.tempLow = getMinTemp(dayForecasts);
    result.probability = probability;
    return result;
}

WeatherData::ForecastInfo WeatherData::ForecastPeriod::getNightWeather() const
{
    qCDebug(IONENGINE_WETTERCOM) << "nightForecasts.size() =" << nightForecasts.size();

    // TODO do not just pick the first night forecast
    return *(nightForecasts.at(0));
}

bool WeatherData::ForecastPeriod::hasNightWeather() const
{
    return !nightForecasts.isEmpty();
}

WeatherData::ForecastInfo WeatherData::ForecastPeriod::getWeather() const
{
    WeatherData::ForecastInfo result = getDayWeather();
    result.tempHigh = std::max(result.tempHigh, getMaxTemp(nightForecasts));
    result.tempLow = std::min(result.tempLow, getMinTemp(nightForecasts));
    return result;
}

int WeatherData::ForecastPeriod::getMaxTemp(const QList<WeatherData::ForecastInfo *> &forecastInfos) const
{
    int result = -273;
    for (const WeatherData::ForecastInfo *forecast : forecastInfos) {
        result = std::max(result, forecast->tempHigh);
    }

    return result;
}

int WeatherData::ForecastPeriod::getMinTemp(const QList<WeatherData::ForecastInfo *> &forecastInfos) const
{
    int result = 100;
    for (const WeatherData::ForecastInfo *forecast : forecastInfos) {
        result = std::min(result, forecast->tempLow);
    }

    return result;
}

K_PLUGIN_CLASS_WITH_JSON(WetterComIon, "ion-wettercom.json")

#include "ion_wettercom.moc"

#include "moc_ion_wettercom.cpp"
