// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-energy-plugin-nymea.
*
* nymea-energy-plugin-nymea.s 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 of the License, or
* (at your option) any later version.
*
* nymea-energy-plugin-nymea.s 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 nymea-energy-plugin-nymea. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "manualslotconfig.h"

// ---------------------------------------------------------------------------
// Free helpers for powerAllocations map serialisation
// ---------------------------------------------------------------------------

QString manualSlotAllocationKey(LoadSource source)
{
    switch (source) {
    case LoadSource::SmartCharging: return QStringLiteral("ev");
    case LoadSource::Battery:       return QStringLiteral("battery");
    case LoadSource::DHW:           return QStringLiteral("dhw");
    case LoadSource::HeatPump:      return QStringLiteral("heatpump");
    case LoadSource::FeedIn:        return QStringLiteral("feedin");
    default:                        return QStringLiteral("external");
    }
}

LoadSource manualSlotSourceFromKey(const QString &key)
{
    if (key == QLatin1String("ev"))       return LoadSource::SmartCharging;
    if (key == QLatin1String("battery"))  return LoadSource::Battery;
    if (key == QLatin1String("dhw"))      return LoadSource::DHW;
    if (key == QLatin1String("heatpump")) return LoadSource::HeatPump;
    if (key == QLatin1String("feedin"))   return LoadSource::FeedIn;
    return LoadSource::External;
}

// ---------------------------------------------------------------------------
// Expiry check
// ---------------------------------------------------------------------------

bool ManualSlotConfig::isExpired() const
{
    return expiresAt.isValid()
           && expiresAt <= QDateTime::currentDateTimeUtc();
}

// ---------------------------------------------------------------------------
// Slot matching
// ---------------------------------------------------------------------------

// Compute "minutes since start of week" for repeating-slot comparison.
// Qt dayOfWeek(): 1=Mon … 7=Sun.  We convert to 0-based (Mon=0).
static int minsOfWeek(const QDateTime &dt)
{
    QDateTime utc = dt.toUTC();
    int dayIndex  = utc.date().dayOfWeek() - 1; // 0=Mon … 6=Sun
    return dayIndex * 24 * 60
           + utc.time().hour() * 60
           + utc.time().minute();
}

bool ManualSlotConfig::matchesSlotIgnoreExpiry(const QDateTime &slotStart) const
{
    if (!start.isValid() || !end.isValid())
        return false;

    if (!repeating)
        return slotStart >= start && slotStart < end;

    // Repeating: compare minutes-of-week for day-of-week + time-of-day
    int slotMins  = minsOfWeek(slotStart);
    int startMins = minsOfWeek(start);
    int endMins   = minsOfWeek(end);

    if (startMins <= endMins) {
        // Normal case — stays within the same calendar week segment
        return slotMins >= startMins && slotMins < endMins;
    } else {
        // Wrap-around case — e.g. Sun 22:00 → Mon 06:00
        return slotMins >= startMins || slotMins < endMins;
    }
}

bool ManualSlotConfig::matchesSlot(const QDateTime &slotStart) const
{
    if (isExpired())
        return false;
    return matchesSlotIgnoreExpiry(slotStart);
}

// ---------------------------------------------------------------------------
// JSON serialisation
// ---------------------------------------------------------------------------

QVariantMap ManualSlotConfig::toJson() const
{
    QVariantMap map;
    if (start.isValid())
        map.insert("start", start.toUTC().toString(Qt::ISODateWithMs));
    if (end.isValid())
        map.insert("end",   end.toUTC().toString(Qt::ISODateWithMs));
    map.insert("label",     label);
    map.insert("repeating", repeating);
    if (expiresAt.isValid())
        map.insert("expiresAt", expiresAt.toUTC().toString(Qt::ISODateWithMs));

    QVariantMap allocs;
    for (auto it = powerAllocations.constBegin(); it != powerAllocations.constEnd(); ++it)
        allocs.insert(manualSlotAllocationKey(it.key()), it.value());
    map.insert("allocations", allocs);

    return map;
}

ManualSlotConfig ManualSlotConfig::fromJson(const QVariantMap &map)
{
    ManualSlotConfig cfg;
    cfg.start = QDateTime::fromString(
        map.value("start").toString(), Qt::ISODateWithMs).toUTC();
    cfg.end = QDateTime::fromString(
        map.value("end").toString(), Qt::ISODateWithMs).toUTC();
    cfg.label     = map.value("label").toString();
    cfg.repeating = map.value("repeating", false).toBool();

    const QString expiresStr = map.value("expiresAt").toString();
    if (!expiresStr.isEmpty())
        cfg.expiresAt = QDateTime::fromString(expiresStr, Qt::ISODateWithMs).toUTC();

    const QVariantMap allocs = map.value("allocations").toMap();
    for (auto it = allocs.constBegin(); it != allocs.constEnd(); ++it) {
        LoadSource src = manualSlotSourceFromKey(it.key());
        if (src != LoadSource::External || it.key() == QLatin1String("external"))
            cfg.powerAllocations.insert(src, it.value().toDouble());
    }

    return cfg;
}
