#include "my_board.h"

#include <ArduinoJson.h>
#include <FS.h>
#include <LittleFS.h>
#include "global.h"
#include "JsonConstrain.h"

static const char* tag = "board";
PWM_Output *pwmOut[4];
RAMP_Output *RearLightControl;

void Init_Board_Pins(void)
{
  pinMode(BoardLED1, OUTPUT);
  pinMode(BoardLED2, OUTPUT);

  pinMode(Button1_Pin, INPUT_PULLUP);
  pinMode(Button2_Pin, INPUT_PULLUP);
  pinMode(Button3_Pin, INPUT_PULLUP);

  ESP_LOGV(tag, "Board pins initialized...");
}

void Init_PWM_Outputs(void) 
{
  File file = LittleFS.open("/cfg/relays.json");
  if(!file){
    ESP_LOGE(tag, "Error opening relays.json...");
  }else{
    StaticJsonDocument<1024> doc;
    DeserializationError error = deserializeJson(doc, file);
    if(!error){
      file.close();
      if(error){ ESP_LOGE(tag, "relays.json deserialize error!.."); return;}

      JsonArray jsArray = doc["relays"];

      if(jsArray.isNull() || jsArray.size() < 1 || jsArray.size() > 4 ) { return; }

      int x = 0;
      for(JsonObject obj : jsArray){
        //Default config if keys are not found
        if (!obj.containsKey("pin") || !obj.containsKey("freq") || !obj.containsKey("max") || !obj.containsKey("default")) { 
          pwmOut[x] = new PWM_Output(0, x, 12, 500, 100.0, false);
          ESP_LOGD(tag, "pwmOut[%d] default config", x);
          x++;
          continue; 
        }

        int pin;
        switch(x){
          case 0:
            pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY1_Pin);
            break;
          case 1:
            pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY2_Pin);
            break;
          case 2:
            pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY3_Pin);
            break;
          default:
            pin = jsonConstrainInt(obj, "pin", 0, 48, RELAY4_Pin);
        }

        int freq = jsonConstrainInt(obj, "freq", 100, 2000, 500);
        float maxVal = jsonConstrainInt(obj, "max", 2.0, 100.0, 95);
        float defaultVal = jsonConstrainInt(obj, "default", 0.0, 100.0, 40);
        ESP_LOGD(tag, "pwmOut[%d]: freq:%d, max:%F, default:%F", x, freq, maxVal, defaultVal);
        
        pwmOut[x] = new PWM_Output(pin, x, RELAY_RES, freq, maxVal, false);
        pwmOut[x]->setOutput(defaultVal);
        x++;
      }  
    }else{
      ESP_LOGE(tag, "Deserialization error on relays.json");
    }
  }                                                            
}

int linearizeLED(float inp){
  if(inp > 100.0){ inp = 100.0;};
  return (inp*inp*0.4095f);
}

/******************* PWM_Output    Definition   ********************/

// max: 0-100%
PWM_Output::PWM_Output(uint8_t pin, uint8_t ch, int res, uint32_t freq, float maxDuty, bool visionCorrected)
{
  this->currDuty = 0;
  this->ch = ch;
  this->maxDuty = maxDuty;
  this->visionCorrected = visionCorrected;
  this->freq = freq;
  this->res = res;
  //this->msecRampRate = msecRampRate;
  pinMode(pin, OUTPUT);
  if(!ledcSetup(ch, freq, res)) { 
    ESP_LOGE(tag, "pwmOut-> ch:%d ledcSetup failed!", ch); 
    return;
  }
  ledcAttachPin(pin, ch);
  setOutput(this->currDuty);
}

// Range is 0 to 100%
void PWM_Output::setOutput(float duty)
{
  if(duty > maxDuty) { duty = maxDuty;}

  // calculate correct duty value
  int outDutyVal;
  if(this->visionCorrected){ outDutyVal = linearizeLED(duty); }
  else{ outDutyVal = duty * 40.95f; }

  // FIXME pwm output ramp cause a freeze here
  // Smooth Transition to final duty value
  /*
  if(msecRampRate){
    int d = this->currOutVal; 
    float dutyInc = (outDutyVal - this->currOutVal)/msecRampRate;
    for(;;){
      d += dutyInc;
      if(d < 0){d = 0;}
      if(d > 4095){d = 4095;}
      ledcWrite(this->ch, d);
      if(d >= outDutyVal || d <= 0){ break; }
      vTaskDelay(msecRampRate);
    }
    ledcWrite(this->ch, outDutyVal);
    this->currOutVal = outDutyVal;
  }
  else{ 
  */
    ledcWrite(this->ch, outDutyVal); 
    this->currOutVal = outDutyVal;
  //}

  this->currDuty = duty;
}

void PWM_Output::setFreq(uint32_t fq)
{
  uint32_t newFreq;

  if(this->freq != fq){
    newFreq = ledcChangeFrequency(this->ch, fq, this->res);
    if(newFreq){
      this->freq = fq;
    }
  }
}



/******************* RAMP_Output    Definition   ********************/



void Initialize_Rear_Control(int relayIndex, int buttonIndex, int rampTime, int steps, float min, float max){
  RearLightControl = new RAMP_Output(pwmOut[relayIndex], btn[buttonIndex], rampTime, steps, min, max);
}

RAMP_Output *RAMP_Output::instance = nullptr; // Initialize the static member variable

void longPressStartCallback(void){
  xTimerStart(RearLightControl->timerHandle, 0);
}

void longPressStopCallback(void){
  xTimerStop(RearLightControl->timerHandle, 0);
  RAMP_Output::instance->SwitchDirection(); // Call the non-static member function using the stored instance
}

void SingleClickCallback(void){
  RAMP_Output::instance->ClickOnOff();
}

void RAMP_Output::TimerCallback(TimerHandle_t xTimer) {
  RearLightControl->IncrementTick();
}

RAMP_Output::RAMP_Output(PWM_Output *output, OneButton *button, int rampTime, int steps, float min, float max) {
  this->Output = output;
  this->Button = button;
  this->rampTime = rampTime;
  this->steps = steps;
  this->min = min;
  this->max = max;
  this->stepSize = (max - min) / steps;

  ESP_LOGD(tag, "Rear Lights: stepSize= %.2f", stepSize);

  instance = this; // Store the instance in the static member variable
  
  if (this->Button) {
    this->Button->attachLongPressStart(longPressStartCallback);
    this->Button->attachLongPressStop(longPressStopCallback);
    this->Button->attachClick(SingleClickCallback);
  } else {
    Serial.println("Error: Button is nullptr");
  }

  Output->visionCorrected = true;
  Output->currDuty = min;
  Output->setOutput(min);

  timerHandle = xTimerCreate("RampTimer", pdMS_TO_TICKS(rampTime/8), pdTRUE, this, TimerCallback);
}

void RAMP_Output::ClickOnOff(void) {
  if(IsOn){
    IsOn = false;
    Output->setOutput(0);
  }else{
    IsOn = true;
    Output->setOutput(lastDuty);
  }
}

void RAMP_Output::IncrementTick(void) {
  if (dirFwd) {
    // Increment output value while ensuring it doesn't exceed max
    float newValue = std::min(Output->currDuty + stepSize, max);
    Output->setOutput( newValue);
    lastDuty = newValue;
    //ESP_LOGD(tag, "up: %.2f", newValue);
  } else {
    // Decrement output value while ensuring it doesn't go below min
    float newValue = std::max(Output->currDuty - stepSize, min);
    Output->setOutput( newValue );
    lastDuty = newValue;
    //ESP_LOGD(tag, "down: %.2f", newValue);
  }
}

void RAMP_Output::SwitchDirection(void) {
  dirFwd = !dirFwd;
}

// Add destructor to clean up resources
RAMP_Output::~RAMP_Output() {
    xTimerDelete(timerHandle, 0);
    // Add any other necessary cleanup here
}



