//
// Created by robtor on 05.01.23.
//

#ifndef FLOATPUMP_CONFIG_STORE_H
#define FLOATPUMP_CONFIG_STORE_H

#include <chrono>
#include "stm32f4xx_hal.h"

namespace floatpump {

    class Config_Store {
    private:
        const uint32_t startAddr = 0x08060000;

        static uint32_t StoreInFlash(uint32_t StartAddress, uint32_t *Data, uint16_t nData) {
            uint16_t progr = 0;
            HAL_FLASH_Unlock();
            FLASH_Erase_Sector(FLASH_SECTOR_7, VOLTAGE_RANGE_1);
            while (progr < nData) {
                if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, StartAddress, Data[progr]) == HAL_OK) {
                    progr++;
                    StartAddress += 4;
                } else {
                    return HAL_FLASH_GetError();
                }
            }
            HAL_FLASH_Lock();
            return 0;
        }

        static uint32_t ReadFromFlash(uint32_t StartAddress, uint32_t *Data, uint16_t nData) {
            uint16_t progr = 0;

            while (progr < nData) {
                Data[progr] = *(uint32_t *) StartAddress;
                progr++;
                StartAddress += 4;
            }
            return 0;
        }

        Config_Store() = default;

        Config_Store(Config_Store const &) = delete;

        Config_Store(Config_Store const &&) = delete;

        void operator=(Config_Store const &) = delete;

        void operator=(Config_Store const &&) = delete;

    public:
        static Config_Store &getInstance() {
            static Config_Store instance;

            return instance;
        }


        void saveToFlash() {
            uint32_t data[12];
            data[0] = TankCalibLow.getValue();
            data[1] = TankCalibHigh.getValue();
            data[2] = TankMinLevel.getValue();
            data[3] = TankHysteresis.getValue();
            data[4] = TankPumpInvert.getValue();
            data[5] = RefillEnable.getValue();
            data[6] = RefillBlockInvert.getValue();
            data[7] = RefillBlockEnable.getValue();
            data[8] = RefillBelow.getValue();
            data[9] = RefillHysteresis.getValue();
            data[10] = RefillCooldown.getValue();
            data[11] = 0xf0f0;

            StoreInFlash(startAddr, data, 12);
            return;
        };

        void resetDefaults();

        void loadFromFlash() {
            uint32_t data[12];
            ReadFromFlash(startAddr, data, 12);

            //Check if intial values must be loaded
            if(data[11] == 0xf0f0) {
                TankCalibLow.setValue(data[0]);
                TankCalibHigh.setValue(data[1]);
                TankMinLevel.setValue(data[2]);
                TankHysteresis.setValue(data[3]);
                TankPumpInvert.setValue(data[4]);
                RefillEnable.setValue(data[5]);
                RefillBlockInvert.setValue(data[6]);
                RefillBlockEnable.setValue(data[7]);
                RefillBelow.setValue(data[8]);
                RefillHysteresis.setValue(data[9]);
                RefillCooldown.setValue(data[10]);
                return;
            } else {
                //Set default values for fist initialization only
                //TODO: refactor this, maybe edit linker script to initially store values to correct flash location
                TankCalibLow.setValue(0);
                TankCalibHigh.setValue(10000);
                TankMinLevel.setValue(25);
                TankHysteresis.setValue(5);
                TankPumpInvert.setValue(1);
                RefillEnable.setValue(0);
                RefillBlockInvert.setValue(0);
                RefillBlockEnable.setValue(1);
                RefillBelow.setValue(35);
                RefillHysteresis.setValue(5);
                RefillCooldown.setValue(5);
                saveToFlash();
            }
        };

        template<class T>
        class Config_Object {
        public:
            Config_Object(T initialValue) : m_data(initialValue) {};

            T getValue() {
                return m_data;
            }

            void setValue(T newValue) {
                m_data = newValue;
            }

            T *getLink() {
                return &m_data;
            }

        private:
            T m_data;
        };

        Config_Object<uint16_t> TankCalibLow = Config_Object<uint16_t>(0);
        Config_Object<uint16_t> TankCalibHigh = Config_Object<uint16_t>(65535);
        Config_Object<uint8_t> TankMinLevel = Config_Object<uint8_t>(20);
        Config_Object<uint8_t> TankHysteresis = Config_Object<uint8_t>(5);
        //Config_Object<uint32_t> TankCooldown = Config_Object<uint32_t>(5);
        //Config_Object<uint32_t> TankZeroLevelCM = Config_Object<uint32_t>(0);
        //Config_Object<uint32_t> TankFullLevelCM = Config_Object<uint32_t>(200);
        Config_Object<bool> TankPumpInvert = Config_Object<bool>(false);

        Config_Object<bool> RefillEnable = Config_Object<bool>(false);
        Config_Object<bool> RefillBlockInvert = Config_Object<bool>(false);
        Config_Object<bool> RefillBlockEnable = Config_Object<bool>(true);
        Config_Object<uint8_t> RefillBelow = Config_Object<uint8_t>(false);
        Config_Object<uint8_t> RefillHysteresis = Config_Object<uint8_t>(false);
        Config_Object<uint16_t> RefillCooldown = Config_Object<uint16_t>(60);
    };

} // floatpump

#endif //FLOATPUMP_CONFIG_STORE_H
