//
// Created by kirillius on 09.04.2022.
//

#include <avr/wdt.h>
#include <utils/ADC.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "ServiceApp.h"
#include "NTC.h"
#include <utils/EEPROM.h>

FanController ServiceApp::Fan;
USART ServiceApp::Serial(9600);

ServiceApp::ServiceApp() {
    wdt_enable(WDTO_8S);
    ADCIO::setup();
    readConfig();
    FanController::PWM = 255;
    level = 0;
    temp = 0;
    sei();
}

uint8_t ServiceApp::lerp(uint8_t a, uint8_t b, uint8_t t) {
    return (uint8_t)((float) a + (float) (b - a) * (float) t / 100.0f);
}

void ServiceApp::update() {
    setFanPWM();
    communicate();
    wdt_reset();
    _delay_ms(100);
}

void ServiceApp::readConfig() {
    EEPROM::get(CONFIG_EEPROM_ADDR, &Config, sizeof(Configuration));
    if (!Config.isValid()) {
        //default config
        Config.minLevel = 32;
        Config.maxLevel = 255;
        Config.startTemp = 30;
        Config.maxTemp = 40;
        Config.stopTemp = 26;
    }
}

void ServiceApp::writeConfig() {
    Config.validate();
    EEPROM::set(CONFIG_EEPROM_ADDR, &Config, sizeof(Configuration));
}

bool ServiceApp::readMessage(Message *dest) {
    if (Serial.available() >= 4) {
        dest->action = static_cast<MessageType>(Serial.read());
        dest->dataType = static_cast<DataType>(Serial.read());
        dest->value = Serial.read();
        dest->checksum = Serial.read();
        return true;
    }
    return false;
}

void ServiceApp::sendMessage(Message *src) {
    src->validate();
    Serial.send(src->action);
    Serial.send(src->dataType);
    Serial.send(src->value);
    Serial.send(src->checksum);
}

void ServiceApp::communicate() {
    Message message;
    if (readMessage(&message)) {
        if (message.isValid()) {
            switch (message.action) {
                case SAVE:
                    message.dataType = NONE;
                    message.value = 255;
                    sendMessage(&message);
                    writeConfig();
                    break;
                case PING:
                    sendMessage(&message);
                    break;
                case READ:
                    switch (message.dataType) {
                        case TEMP:
                            message.value = temp;
                            sendMessage(&message);
                            break;
                        case LEVEL:
                            message.value = level;
                            sendMessage(&message);
                            break;
                        case START_TEMP:
                            message.value = Config.startTemp;
                            sendMessage(&message);
                            break;
                        case MAX_TEMP:
                            message.value = Config.maxTemp;
                            sendMessage(&message);
                            break;
                        case MIN_LEVEL:
                            message.value = Config.minLevel;
                            sendMessage(&message);
                            break;
                        case MAX_LEVEL:
                            message.value = Config.maxLevel;
                            sendMessage(&message);
                            break;
                        default:
                            message.action = ERROR;
                            message.dataType = NONE;
                            message.value = ERR_UNKNOWN_DATA_TYPE;
                            sendMessage(&message);
                        case NONE:
                            break;
                        case STOP_TEMP:
                            message.value = Config.stopTemp;
                            sendMessage(&message);
                            break;
                    }
                    break;
                case WRITE:
                    switch (message.dataType) {
                        case STOP_TEMP:
                            Config.stopTemp = message.value;
                            sendMessage(&message);
                            break;
                        case TEMP:
                        case LEVEL:
                            message.action = ERROR;
                            message.dataType = NONE;
                            message.value = ERR_UNSUPPORTED_OPERATION;
                            sendMessage(&message);
                            break;
                        case START_TEMP:
                            Config.startTemp = message.value;
                            sendMessage(&message);
                            break;
                        case MAX_TEMP:
                            Config.maxTemp = message.value;
                            sendMessage(&message);
                            break;
                        case MIN_LEVEL:
                            Config.minLevel = message.value;
                            sendMessage(&message);
                            break;
                        case MAX_LEVEL:
                            Config.maxLevel = message.value;
                            sendMessage(&message);
                            break;
                        default:
                            message.action = ERROR;
                            message.dataType = NONE;
                            message.value = ERR_UNKNOWN_DATA_TYPE;
                            sendMessage(&message);
                    }
                    break;
                case REBOOT:
                    message.dataType = NONE;
                    message.value = 255;
                    sendMessage(&message);
                    wdt_disable();
                    wdt_enable(WDTO_500MS);
                    while (true);
                    break;
                default:
                    message.action = ERROR;
                    message.dataType = NONE;
                    message.value = ERR_UNKNOWN_ACTION_TYPE;
                    sendMessage(&message);
            }
        } else {
            message.action = ERROR;
            message.dataType = NONE;
            message.value = ERR_INVALID_CHECKSUM;
            sendMessage(&message);
        }
    }

    if (comCounter++ == 0) {
        message.action = PING;
        message.value = 0x42;
        message.dataType = NONE;
        sendMessage(&message);
    }
}

void ServiceApp::setFanPWM() {
    temp = NTC::getTemperature(ADCIO::read(ADC6));
    uint8_t minTemp = min(Config.startTemp, Config.stopTemp);

    if (temp >= Config.maxTemp) { //выставить макс. мощность если темп выше или равна максимальной
        level = Config.maxLevel;
    } else if (temp <= minTemp) { //вылкючить если температура меньше или равна минимальной из двух (старт или стоп)
        level = 0;
        FanController::PWM = 0;
        return;
    } else if (temp >= Config.startTemp) { //мощность вычисляется в зависимости от температуры если темп больше стартовой
        uint8_t interpolation = (uint8_t)(100.0f * (float) (temp - Config.startTemp) /
                                          (float) (Config.maxTemp - Config.startTemp));
        level = lerp(Config.minLevel, Config.maxLevel, interpolation > 100 ? 100 : interpolation);
    }else if(level > 0){
        //если темп больше stopTemp но меньше startTemp то задаем минимальную мощность
        level = Config.minLevel;
    }

    if (FanController::PWM == 0 && level > 0) {
        //не стартуем с 0 мощности
        FanController::PWM = 32;
    } else {
        if (FanController::PWM > level) {
            //slow decrease
            if (FanController::PWM - level > 5) {
                FanController::PWM -= 1;
            } else {
                FanController::PWM = level;
            }
        } else {
            FanController::PWM = level;
        }
    }
}

float ServiceApp::clamp(float a, float min, float max) {
    return (a < min ? min : (a > max ? max : a));
}

uint8_t ServiceApp::min(uint8_t a, uint8_t b) {
    return a < b ? a : b;
}
