// 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/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef SMARTCHARGINGMANAGER_H
#define SMARTCHARGINGMANAGER_H

#include <QObject>
#include <QTimer>

#include "energymanagerconfiguration.h"
#include "types/charginginfo.h"
#include "types/chargingaction.h"
#include "types/chargingschedule.h"
#include "types/chargingprocessinfo.h"
#include "spotmarket/spotmarketmanager.h"

// from libnymea
#include <integrations/thingmanager.h>
#include <hardware/electricity.h>

// from libnymea-energy
#include <energymanager.h>

class EvCharger;
class RootMeter;

class SmartChargingManager : public QObject
{
    Q_OBJECT
public:
    explicit SmartChargingManager(EnergyManager *energyManager, ThingManager *thingManager, SpotMarketManager *spotMarketManager, EnergyManagerConfiguration *configuration, QObject *parent = nullptr);

    uint phasePowerLimit() const;
    void setPhasePowerLimit(uint phasePowerLimit);

    double acquisitionTolerance() const;
    void setAcquisitionTolerance(double acquisitionTolerance);

    double batteryLevelConsideration() const;
    void setBatteryLevelConsideration(double batteryLevelConsideration);

    bool lockOnUnplug() const;
    void setLockOnUnplug(bool lockOnUnplug);

    ChargingInfos chargingInfos() const;
    ChargingInfo chargingInfo(const ThingId &evChargerId) const;
    EnergyManager::EnergyError setChargingInfo(const ChargingInfo &chargingInfo);

    ChargingSchedules chargingSchedules() const;

    SpotMarketManager *spotMarketManager() const;

#ifdef ENERGY_SIMULATION
    void simulationCallUpdate(const QDateTime &dateTime);
    void simulationCallUpdateManualSoCsWithMeter(EnergyLogs::SampleRate sampleRate, const ThingPowerLogEntry &entry);
#endif

signals:
    void phasePowerLimitChanged(int phasePowerLimit);
    void acquisitionToleranceChanged(double acquisitionTolerance);
    void batteryLevelConsiderationChanged(double batteryLevelConsideration);
    void lockOnUnplugChanged(bool lockOnUnplug);
    void chargingInfoAdded(const ChargingInfo &chargingInfo);
    void chargingInfoRemoved(const ThingId &evChargerThingId);
    void chargingInfoChanged(const ChargingInfo &chargingInfo);
    void chargingSchedulesChanged();

#ifdef ENERGY_SIMULATION
    void chargingUpdated();
#endif

private slots:
    void update(const QDateTime &currentDateTime);

    // Don't call these methods out of place. it's only meant to keep the otherwise long update() code tidy.
    // Call update() if you want to trigger the smarties.
    void prepareInformation(const QDateTime &currentDateTime);
    void planSpotMarketCharging(const QDateTime &currentDateTime);
    void planSurplusCharging(const QDateTime &currentDateTime);
    void adjustEvChargers(const QDateTime &currentDateTime);
    void updateManualSoCsWithMeter(EnergyLogs::SampleRate sampleRate, const ThingPowerLogEntry &entry);
    void updateManualSoCsWithoutMeter(const QDateTime &currentDateTime);

    void verifyOverloadProtection(const QDateTime &currentDateTime);
    void verifyOverloadProtectionRecovery(const QDateTime &currentDateTime);

    void onThingAdded(Thing *thing);
    void onThingRemoved(const ThingId &thingId);
    void onActionExecuted(const Action &action, Thing::ThingError status);
    void onChargingModeChanged(const ThingId &evChargerId, const ChargingInfo &chargingInfo);

private:
    void setupRootMeter(Thing *thing);
    void setupEvCharger(Thing *thing);
    void setupPluggedInHandlers(const Thing *thing);

    void storeChargingInfo(const ChargingInfo &chargingInfo);
    void storeManualChargingParameters(const ThingId &evChargerId, bool enabled, int maxChargingCurrent, uint desiredPhaseCount);
    bool manualChargingEnabled(const ThingId &evChargerId) const;
    uint manualMaxChargingCurrent(const ThingId &evChargerId) const;
    uint manualDesiredPhaseCount(const ThingId &evChargerId) const;

    Electricity::Phases getAscendingPhasesForCount(uint phaseCount);
    uint getBestPhaseCount(EvCharger *evCharger, double surplusAmpere);

    EnergyManager *m_energyManager = nullptr;
    ThingManager *m_thingManager = nullptr;    
    SpotMarketManager *m_spotMarketManager = nullptr;
    EnergyManagerConfiguration *m_configuration = nullptr;

    QHash<EvCharger *, ChargingSchedules> m_chargingSchedules;
    QHash<EvCharger *, ChargingProcessInfo> m_processInfos;
    QHash<EvCharger *, ChargingActions> m_chargingActions;

    QDateTime m_lastSpotMarketPlanning;

    // Overload protection
    QHash<EvCharger *, bool> m_overloadProtectionActive;

    uint m_phasePowerConsumptionLimit = 25;

    double m_acquisitionTolerance = 0.5;
    double m_batteryLevelConsideration = 0.9;

    QHash<ThingId, ChargingInfo> m_chargingInfos;

    RootMeter *m_rootMeter = nullptr;
    QHash<ThingId, EvCharger *> m_evChargers;

    void executeChargingAction(EvCharger *evCharger, const ChargingAction &chargingAction, const QDateTime &currentDateTime);

};

#endif // SMARTCHARGINGMANAGER_H
