#include <Arduino.h>
#include <Encoder.h>

#include "global.hpp"
#include "pin_map.hpp"

#include "button.hpp"
#include "led_strip.hpp"

/* Types and Constants */
/*--------------------------------------------------------------------------------------------------------------------*/

/* State machine */
#define FOREACH_MODE(MODE)                                                                                             \
  MODE(Init)                                                                                                           \
  MODE(LightOnMin)                                                                                                     \
  MODE(LightOnMax)                                                                                                     \
  MODE(Preset)                                                                                                         \
  MODE(SetColor)                                                                                                       \
  MODE(SetSaturation)                                                                                                  \
  MODE(SetBrightness)                                                                                                  \
  MODE(ControlOff)

#define GENERATE_MODE_ENUM(ENUM) ENUM,

/* Generate Mode_e enum */
enum class Mode_e { FOREACH_MODE(GENERATE_MODE_ENUM) };

/* Cyclic increment operator for Mode_e */
Mode_e &operator++(Mode_e &mode) {
  if (mode == Mode_e::ControlOff) {
    return mode = Mode_e::Init;
  }
  return mode = static_cast<Mode_e>(static_cast<int>(mode) + 1);
}

#if defined(__BUILD_DEBUG__)
/* Generate string array from Mode_e names */
#define GENERATE_MODE_STRING(STRING) #STRING,
char static const *mode_str[] = {FOREACH_MODE(GENERATE_MODE_STRING)};
#endif /* defined(__BUILD_DEBUG__) */

/* Led ring system state */
Mode_e currentMode{Mode_e::Init};

/* Global variables */
/*--------------------------------------------------------------------------------------------------------------------*/

/*  IO Objects */
Encoder encoder{EncoderPinA, EncoderPinB};
#if defined(ARDUINO_AVR_ATTINYX5)
AnalogButton button{AnalogButtonPin, AnalogButtonVRef};
#elif defined(ARDUINO_AVR_ATTINYX41)
Button button{ButtonPin};
#else
/* No vRef defined force to calibrate for test purpose */
AnalogButton button{AnalogButtonPin};
#endif /* defined(ARDUINO_AVR_ATTINYX41) */

LedStrip ledStrip{};

/* debug Serial declaration */
#if defined(__BUILD_DEBUG__)
#if defined(ARDUINO_AVR_ATTINYX5)
SoftwareSerial debugSerial{RxPin, TxPin};
#elif defined(ARDUINO_AVR_ATTINYX41)
HardwareSerial &debugSerial = Serial1;
#elif
HardwareSerial &debugSerial = Serial;
#endif
#endif /* defined(__BUILD_DEBUG__) */

/* Private function declarations */
/*--------------------------------------------------------------------------------------------------------------------*/

/* Generates state machine handlers for each Mode_e */
#define GENERATE_MODE_EXEC_DECLARTION(MODE) void MODE##_execute(int shift);
FOREACH_MODE(GENERATE_MODE_EXEC_DECLARTION)

/* Encoder */
void analog_button_calibration(void);

int get_encoder_shift(void);

// void display_mode(Mode_e mode);

// void display_led_ring(void);

// void refresh_led_ring(LedRingColorState_t const &ledRingColorState = ledRingState.ledRingColorState);

/* Function definitions */
/*--------------------------------------------------------------------------------------------------------------------*/
void setup() {
  ledStrip.setup();
  button.setup();

#if defined(__BUILD_DEBUG__)
  debugSerial.begin(9600);
#if defined(DEBUG_INIT)
  while (!debugSerial.available())
    ;
#endif /* defined(DEBUG_INIT) */
  debugSerial.println("Init ... ");
#endif /* defined(__BUILD_DEBUG__) */

  get_encoder_shift();

#if defined(__BUILD_DEBUG__)
  debugSerial.println(mode_str[static_cast<int>(currentMode)]);
#endif /* defined(__BUILD_DEBUG__) */
}

void loop() {
  bool hasModeChanged{false};
  int shift{get_encoder_shift()};

  if (button.is_button_pressed()) {
    while (button.is_button_pressed()) {
      delay(150);
    }
    ++currentMode;

#if defined(__BUILD_DEBUG__)
    debugSerial.println(mode_str[static_cast<int>(currentMode)]);
#endif /* defined(__BUILD_DEBUG__) */

    // display_mode(currentMode);
    hasModeChanged = true;
  }
  ledStrip.setup();

  if (shift != 0) {
    switch (currentMode) {
    case Mode_e::ControlOff:
      ControlOff_execute(shift);
      break;

    case Mode_e::Preset:
      Preset_execute(shift);
      break;

      //   case Mode_e::SetColor:
      //     SetColor_execute(shift);
      //     break;

      //   case Mode_e::SetBrightness:
      //     SetBrightness_execute(shift);
      //     break;

      //   case Mode_e::SetSaturation:
      //     SetSaturation_execute(shift);
      //     break;

      //   case Mode_e::LightOnMin:
      //     LightOnMin_execute(shift);
      //     break;

      //   case Mode_e::LightOnMax:
      //     LightOnMax_execute(shift);
      //     break;

    default:
      break;
    }
  }

  // if (currentMode != Mode_e::ControlOff && (shift != 0 || hasModeChanged)) {
  //   display_led_ring();
  // }
}

/* Private function definitions */
/*--------------------------------------------------------------------------------------------------------------------*/

/* State Machine functions */

void ControlOff_execute(int shift) {}

void Preset_execute(int shift) {
  if (shift > 0) {
    ledStrip.next_preset();
  } else {
    ledStrip.previous_preset();
  }
} /* Preset_execute */

// void SetColor_execute(int shift) {
//   uint16_t &hue = ledRingState.ledRingColorState.hue;

//   hue += (shift << 8);

// #if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
//   if (shift != 0) {
//     debugSerial.print(__func__);
//     debugSerial.print(" : shift=");
//     debugSerial.print(shift, DEC);
//     debugSerial.print(", hue=");
//     debugSerial.println(hue, DEC);
//   }
// #endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
// } /* SetColor_execute */

// void SetBrightness_execute(int shift) {
//   uint8_t &brightness = ledRingState.ledRingColorState.brightness;

//   brightness += (shift << 2);

// #if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
//   if (shift != 0) {
//     debugSerial.print(__func__);
//     debugSerial.print(" : shift=");
//     debugSerial.print(shift, DEC);
//     debugSerial.print(", brightness=");
//     debugSerial.println(brightness, DEC);
//   }
// #endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
// } /* SetBrightness_execute */

// void SetSaturation_execute(int shift) {
//   uint8_t &saturation = ledRingState.ledRingColorState.saturation;

//   saturation += (shift << 2);

// #if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
//   if (shift != 0) {
//     debugSerial.print(__func__);
//     debugSerial.print(" : shift=");
//     debugSerial.print(shift, DEC);
//     debugSerial.print(", saturation=");
//     debugSerial.println(saturation, DEC);
//   }
// #endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
// } /* SetSaturation_execute */

// void LightOnMin_execute(int shift) {
//   uint16_t &ledOnMin = ledRingState.ledRingColorState.ledOnMin;
//   uint16_t &ledOnMax = ledRingState.ledRingColorState.ledOnMax;

//   if (shift > 0) {
//     if (ledOnMin == ledOnMax) {
//       currentMode = Mode_e::LightOnMax;
//       ++ledOnMax;
//     } else {
//       ++ledOnMin;
//       if (ledOnMin == LedCount) {
//         ledOnMin = 0;
//       }
//     }
//   } else {
//     if (ledOnMin == ledOnMax) {
//       currentMode = Mode_e::LightOnMax;
//       --ledOnMax;
//     } else {
//       --ledOnMin;
//       if (ledOnMin == UINT16_MAX) {
//         ledOnMin = LedCount - 1;
//       }
//     }
//   }

// #if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
//   if (shift != 0) {
//     debugSerial.print(__func__);
//     debugSerial.print(" : shift=");
//     debugSerial.print(shift, DEC);
//     debugSerial.print(", ledOnMin=");
//     debugSerial.println(ledOnMin, DEC);
//   }
// #endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
// } /* LightOnMin_execute */

// void LightOnMax_execute(int shift) {
//   uint16_t &ledOnMin = ledRingState.ledRingColorState.ledOnMin;
//   uint16_t &ledOnMax = ledRingState.ledRingColorState.ledOnMax;

//   if (shift > 0) {
//     if (ledOnMax == ledOnMin) {
//       currentMode = Mode_e::LightOnMin;
//       ++ledOnMin;
//     } else {
//       ++ledOnMax;
//       if (ledOnMax == LedCount) {
//         ledOnMax = 0;
//       }
//     }
//   } else {
//     if (ledOnMax == ledOnMin) {
//       currentMode = Mode_e::LightOnMin;
//       --ledOnMin;
//     } else {
//       --ledOnMax;
//       if (ledOnMax == UINT16_MAX) {
//         ledOnMax = LedCount - 1;
//       }
//     }
//   }

// #if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
//   if (shift != 0) {
//     debugSerial.print(__func__);
//     debugSerial.print(" : shift=");
//     debugSerial.print(shift, DEC);
//     debugSerial.print(", ledOnMax=");
//     debugSerial.println(ledOnMax, DEC);
//   }
// #endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
// } /* LightOnMax_execute */

/* Others functions */
int get_encoder_shift(void) {
  int32_t static _previousValue{encoder.read()};

  int32_t currentValue{encoder.read()};
  int32_t diff{currentValue - _previousValue};

  if (diff <= -4) {
    _previousValue = currentValue;
    return diff >> 2;

  } else if (diff >= 4) {
    _previousValue = currentValue;
    return diff >> 2;

  } else {
    return 0;
  }
}

// void display_mode(Mode_e mode) {
//   if (currentMode != Mode_e::ControlOff) {
//     for (size_t i = 0; i < 3; ++i) {
//       ledRing.fill(0);
//       ledRing.show();
//       delay(300);
//       for (Mode_e i = Mode_e::Init; i < currentMode; ++i) {
//         ledRing.setPixelColor(int(i), Adafruit_NeoPixel::ColorHSV(0, 255, 16));
//       }
//       ledRing.show();
//       delay(300);
//     }
//   }
// }

// void display_led_ring(void) {
//   PresetState_t &preset = ledRingState.presetState;
//   LedRingColorState_t ledRingColorState;

//   if (currentMode == Mode_e::Preset) {
// #if defined(__BUILD_DEBUG__) && false
//     debugSerial.print("Preset index,level=");
//     debugSerial.print(preset.index);
//     debugSerial.print(",");
//     debugSerial.print(preset.level);
//     debugSerial.println();
// #endif /* defined(__BUILD_DEBUG__) */
//     ledRingColorState = {.ledOnMin = ledRingState.ledRingColorState.ledOnMin,
//                          .ledOnMax = ledRingState.ledRingColorState.ledOnMax,
//                          .hue = PresetHue[preset.index],
//                          .saturation = (preset.index == (PresetMax - 1)) ? 0 : UINT8_MAX,
//                          .brightness = PresetBrightness[preset.level]};
//     refresh_led_ring(ledRingColorState);
//   } else {
//     refresh_led_ring();
//   }
// }

// void refresh_led_ring(LedRingColorState_t const &ledRingColorState) {
//   uint16_t const &ledOnMin = ledRingColorState.ledOnMin;
//   uint16_t const &ledOnMax = ledRingColorState.ledOnMax;
//   uint16_t LedOnCount = 0;

// #if defined(__BUILD_DEBUG__)
//   debugSerial.print("Color hue,sat,bri=");
//   debugSerial.print(ledRingColorState.hue);
//   debugSerial.print(",");
//   debugSerial.print(ledRingColorState.saturation);
//   debugSerial.print(",");
//   debugSerial.println(ledRingColorState.brightness);
// #endif /* defined(__BUILD_DEBUG__) */

//   /* reset ring */
//   ledRing.fill(0);

//   /* compute number of led on */
//   if (ledOnMin <= ledOnMax) {
//     LedOnCount = ledOnMax - ledOnMin;
//   } else {
//     LedOnCount = LedCount - (ledOnMax - ledOnMax);
//   }

// #if defined(__BUILD_DEBUG__)
//   debugSerial.print("Led min,max,count=");
//   debugSerial.print(ledOnMin);
//   debugSerial.print(",");
//   debugSerial.print(ledOnMax);
//   debugSerial.print(",");
//   debugSerial.println(LedOnCount);
// #endif /* defined(__BUILD_DEBUG__) */

//   for (size_t i = ledRingColorState.ledOnMin; i < ledRingColorState.ledOnMin + LedOnCount; ++i) {
//     unsigned int ledId = i;
//     if (ledId >= LedCount) {
//       ledId -= LedCount;
//     }
//     ledRing.setPixelColor(ledId, Adafruit_NeoPixel::ColorHSV(ledRingColorState.hue, ledRingColorState.saturation,
//                                                              ledRingColorState.brightness));
//   }
//   ledRing.show();
// }