//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------

#ifndef __APP_H__
#define __APP_H__

#include <Arduino.h>
#include <ArduinoJson.h>
#if defined(ESP32)
#include <esp_task_wdt.h>
#define WDT_TIMEOUT_SECONDS 8   // Watchdog Timeout 8s
#endif

#include "config/settings.h"
#include "defines.h"
#include "appInterface.h"
#include "hm/hmSystem.h"
#include "hm/NrfRadio.h"
#if defined(ESP32)
#include "hms/CmtRadio.h"
#endif
#if defined(ENABLE_MQTT)
#include "publisher/pubMqtt.h"
#endif /*ENABLE_MQTT*/
#include "publisher/pubSerial.h"
#include "utils/crc.h"
#include "utils/dbg.h"
#include "utils/scheduler.h"
#include "utils/syslog.h"
#include "web/RestApi.h"
#include "web/Protection.h"
#include "plugins/MaxPower.h"
#if defined(ENABLE_HISTORY)
#include "plugins/history.h"
#endif /*ENABLE_HISTORY*/
#include "web/web.h"
#include "hm/Communication.h"
#if defined(ETHERNET)
    #include "network/AhoyEthernet.h"
#else /* defined(ETHERNET) */
    #if defined(ESP32)
        #include "network/AhoyWifiEsp32.h"
    #else
        #include "network/AhoyWifiEsp8266.h"
    #endif
    #include "utils/improv.h"
#endif /* defined(ETHERNET) */

#if defined(ENABLE_SIMULATOR)
    #include "hm/simulator.h"
#endif /*ENABLE_SIMULATOR*/

#include <RF24.h> // position is relevant since version 1.4.7 of this library


// convert degrees and radians for sun calculation
#define SIN(x) (sin(radians(x)))
#define COS(x) (cos(radians(x)))
#define ASIN(x) (degrees(asin(x)))
#define ACOS(x) (degrees(acos(x)))

typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
typedef Web<HmSystemType> WebType;
typedef RestApi<HmSystemType> RestApiType;
#if defined(ENABLE_MQTT)
typedef PubMqtt<HmSystemType> PubMqttType;
#endif /*ENABLE_MQTT*/
typedef PubSerial<HmSystemType> PubSerialType;
#if defined(ENABLE_HISTORY)
typedef HistoryData<HmSystemType> HistoryType;
#endif /*ENABLE_HISTORY*/
#if defined (ENABLE_SIMULATOR)
typedef Simulator<HmSystemType> SimulatorType;
#endif /*ENABLE_SIMULATOR*/

// PLUGINS
#if defined(PLUGIN_DISPLAY)
#include "plugins/Display/Display.h"
#include "plugins/Display/Display_data.h"
typedef Display<HmSystemType, Radio> DisplayType;
#endif

class app : public IApp, public ah::Scheduler {
   public:
        app();
        ~app() {}

        void setup(void);
        void loop(void) override;
        void onNetwork(bool gotIp);
        void regularTickers(void);

        void handleIntr(void) {
            mNrfRadio.handleIntr();
        }
        void* getRadioObj(bool nrf) override {
            if(nrf)
                return (void*)&mNrfRadio;
            else {
                #ifdef ESP32
                return (void*)&mCmtRadio;
                #else
                return NULL;
                #endif
            }
        }

        #ifdef ESP32
        void handleHmsIntr(void) {
            mCmtRadio.handleIntr();
        }
        #endif

        uint32_t getUptime() override {
            return Scheduler::getUptime();
        }

        uint32_t getTimestamp() override {
            return Scheduler::mTimestamp;
        }

        uint64_t getTimestampMs() override {
            return ((uint64_t)Scheduler::mTimestamp * 1000) + ((uint64_t)millis() - (uint64_t)Scheduler::mTsMillis) % 1000;
        }

        bool saveSettings(bool reboot) override {
            mShowRebootRequest = true; // only message on index, no reboot
            mSavePending = true;
            mSaveReboot = reboot;
            if(reboot) {
                ah::Scheduler::resetTicker();
            }
            once(std::bind(&app::tickSave, this), 3, "save");
            return true;
        }

        void initInverter(uint8_t id) override {
            mSys.addInverter(id, [this](Inverter<> *iv) {
                if((IV_MI == iv->ivGen) || (IV_HM == iv->ivGen))
                    iv->radio = &mNrfRadio;
                #if defined(ESP32)
                else if((IV_HMS == iv->ivGen) || (IV_HMT == iv->ivGen))
                    iv->radio = &mCmtRadio;
                #endif
            });
        }

        bool readSettings(const char *path) override {
            return mSettings.readSettings(path);
        }

        bool eraseSettings(bool eraseWifi = false) {
            return mSettings.eraseSettings(eraseWifi);
        }

        bool getSavePending() override {
            return mSavePending;
        }

        bool getLastSaveSucceed() override {
            return mSettings.getLastSaveSucceed();
        }

        bool getShouldReboot() override {
            return mSaveReboot;
        }

        #if !defined(ETHERNET)
        bool getAvailNetworks(JsonObject obj) override {
            return mNetwork->getAvailNetworks(obj, this);
        }

        void setupStation(void) override {
            mNetwork->begin();
        }

        bool getWasInCh12to14(void) const override {
            #if defined(ESP8266)
            return mNetwork->getWasInCh12to14();
            #else
            return false;
            #endif
        }
        #endif /* !defined(ETHERNET) */

        String getIp(void) override {
            return mNetwork->getIp();
        }

        bool isApActive(void) override {
            return mNetwork->isApActive();
        }

        void setRebootFlag() override {
            once(std::bind(&app::tickReboot, this), 3, "rboot");
        }

        const char *getVersion() override {
            return mVersion;
        }

        const char *getVersionModules() override {
            return mVersionModules;
        }

        void addOnce(ah::scdCb c, uint32_t timeout, const char *name) override {
            once(c, timeout, name);
        }

        uint32_t getSunrise() override {
            return mSunrise;
        }

        uint32_t getSunset() override {
            return mSunset;
        }

        bool getSettingsValid() override {
            return mConfig->valid;
        }

        bool getRebootRequestState() override {
            return mShowRebootRequest;
        }

        void setMqttDiscoveryFlag() override {
            #if defined(ENABLE_MQTT)
            once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf");
            #endif
        }

        bool getMqttIsConnected() override {
            #if defined(ENABLE_MQTT)
                return mMqtt.isConnected();
            #else
                return false;
            #endif
        }

        uint32_t getMqttTxCnt() override {
            #if defined(ENABLE_MQTT)
                return mMqtt.getTxCnt();
            #else
                return 0;
            #endif
        }

        uint32_t getMqttRxCnt() override {
            #if defined(ENABLE_MQTT)
                return mMqtt.getRxCnt();
            #else
                return 0;
            #endif
        }

        void lock(bool fromWeb) override {
            mProtection->lock(fromWeb);
        }

        char *unlock(const char *clientIp, bool loginFromWeb) override {
            return mProtection->unlock(clientIp, loginFromWeb);
        }

        void resetLockTimeout(void) override {
            mProtection->resetLockTimeout();
        }

        bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const override {
            return mProtection->isProtected(clientIp, token, askedFromWeb);
        }

        bool getNrfEnabled(void) override {
            return mConfig->nrf.enabled;
        }

        bool getCmtEnabled(void) override {
            return mConfig->cmt.enabled;
        }

        uint8_t getNrfIrqPin(void) {
            return mConfig->nrf.pinIrq;
        }

        uint8_t getCmtIrqPin(void) {
            return mConfig->cmt.pinIrq;
        }

        uint32_t getTimezoneOffset() override {
            return mApi.getTimezoneOffset();
        }

        void getSchedulerInfo(uint8_t *max) override {
            getStat(max);
        }

        void getSchedulerNames(void) override {
            printSchedulers();
        }

        void setTimestamp(uint32_t newTime) override {
            DPRINT(DBG_DEBUG, F("setTimestamp: "));
            DBGPRINTLN(String(newTime));
            if(0 == newTime)
                mNetwork->updateNtpTime();
            else {
                Scheduler::setTimestamp(newTime);
                onNtpUpdate(false);
            }
        }

        float getTotalMaxPower(void) override {
            return mMaxPower.getTotalMaxPower();
        }

        uint16_t getHistoryValue(uint8_t type, uint16_t i) override {
            #if defined(ENABLE_HISTORY)
                return mHistory.valueAt((HistoryStorageType)type, i);
            #else
                return 0;
            #endif
        }

        uint32_t getHistoryPeriod(uint8_t type) override {
            #if defined(ENABLE_HISTORY)
                return mHistory.getPeriod((HistoryStorageType)type);
            #else
                return 0;
            #endif
        }

        uint16_t getHistoryMaxDay() override {
            #if defined(ENABLE_HISTORY)
                return mHistory.getMaximumDay();
            #else
                return 0;
            #endif
        }

        uint32_t getHistoryLastValueTs(uint8_t type) override {
            #if defined(ENABLE_HISTORY)
                return mHistory.getLastValueTs((HistoryStorageType)type);
            #else
                return 0;
            #endif
        }
        #if defined(ENABLE_HISTORY_LOAD_DATA)
        void addValueToHistory(uint8_t historyType, uint8_t valueType, uint32_t value) override {
            #if defined(ENABLE_HISTORY)
                return mHistory.addValue((HistoryStorageType)historyType, valueType, value);
            #endif
        }
        #endif

    private:
        #define CHECK_AVAIL     true
        #define SKIP_YIELD_DAY  true

        void resetSystem(void);
        void zeroIvValues(bool checkAvail = false, bool skipYieldDay = true);

        void payloadEventListener(uint8_t cmd, Inverter<> *iv) {
            mMaxPower.payloadEvent(cmd, iv);
            #if defined(ENABLE_MQTT)
                if (mMqttEnabled)
                    mMqtt.payloadEventListener(cmd, iv);
            #endif
            #if defined(PLUGIN_DISPLAY)
                if(DISP_TYPE_T0_NONE != mConfig->plugin.display.type)
                   mDisplay.payloadEventListener(cmd);
            #endif
           updateLed();
        }

        void mqttSubRxCb(JsonObject obj);

        void setupLed();
        void updateLed();

        void tickReboot(void) {
            DPRINTLN(DBG_INFO, F("Rebooting..."));
            ah::Scheduler::resetTicker();
            WiFi.disconnect();
            delay(200);
            ESP.restart();
        }

        void tickSave(void) {
            if(!mSettings.saveSettings())
                mSaveReboot = false;
            mSavePending = false;

            if(mSaveReboot)
                setRebootFlag();
        }

        void tickNtpUpdate(void);
        void onNtpUpdate(bool gotTime);
        bool mNtpReceived = false;
        void updateNtp(void);

        void triggerTickSend(uint8_t id) override {
            once([this, id]() {
                sendIv(mSys.getInverterByPos(id));
            }, 0, "devct");
        }

        void tickCalcSunrise(void);
        void tickIVCommunication(void);
        void tickSun(void);
        void tickSunrise(void);
        void tickComm(void);
        void tickSend(void);
        bool sendIv(Inverter<> *iv);
        void tickMinute(void);
        void tickZeroValues(void);
        void tickMidnight(void);
        void notAvailChanged(void);

        HmSystemType mSys;
        NrfRadio<> mNrfRadio;
        Communication mCommunication;

        bool mShowRebootRequest = false;

        AhoyNetwork *mNetwork = nullptr;
        WebType mWeb;
        RestApiType mApi;
        Protection *mProtection = nullptr;
        #ifdef ENABLE_SYSLOG
        DbgSyslog mDbgSyslog;
        #endif

        PubSerialType mPubSerial;
        #if !defined(ETHERNET)
        //Improv mImprov;
        #endif
        #ifdef ESP32
        CmtRadio<> mCmtRadio;
        #endif

        char mVersion[12];
        char mVersionModules[12];
        settings mSettings;
        settings_t *mConfig = nullptr;
        bool mSavePending = false;
        bool mSaveReboot = false;

        uint8_t mSendLastIvId = 0;
        bool mAllIvNotAvail = false;

        bool mNetworkConnected = false;

        #if defined(ENABLE_MQTT)
        PubMqttType mMqtt;
        #endif
        bool mTickerInstallOnce = false;
        bool mMqttEnabled = false;

        // sun
        int32_t mCalculatedTimezoneOffset = 0;
        uint32_t mSunrise = 0, mSunset = 0;

        // plugins
        MaxPower<float> mMaxPower;
        #if defined(PLUGIN_DISPLAY)
        DisplayType mDisplay;
        DisplayData mDispData;
        #endif
        #if defined(ENABLE_HISTORY)
        HistoryType mHistory;
        #endif /*ENABLE_HISTORY*/

        #if defined(ENABLE_SIMULATOR)
        SimulatorType mSimulator;
        #endif /*ENABLE_SIMULATOR*/
};

#endif /*__APP_H__*/