#include <Arduino.h>
#if defined(__BUILD_DEBUG__) && !defined(ARDUINO_AVR_UNO)
/* Using interrupts with serial seems to cause instability */
#define ENCODER_DO_NOT_USE_INTERRUPTS
#endif
#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(FirstLedOn)                                                                                                     \
  MODE(LedOnLength)                                                                                                    \
  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::FirstLedOn;
  }
  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::ControlOff};

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

/*  IO Objects */
Encoder encoder{EncoderPinA, EncoderPinB};
#if defined(ARDUINO_AVR_ATTINYX5)
AnalogButton button{ButtonPin, AnalogButtonVRef};
#elif defined(ARDUINO_AVR_ATTINYX41)
Button button{ButtonPin};
#else
#if defined(ANALOG_BUTTON)
/* No vRef defined force to calibrate for test purpose */
AnalogButton button{ButtonPin};
#else
Button button{ButtonPin};
#endif /* defined(ANALOG_BUTTON) */
#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;
#else
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)

int get_encoder_shift(void);

/* Function definitions */
/*--------------------------------------------------------------------------------------------------------------------*/
void 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__) */

  button.setup();
  ledStrip.setup(button.is_pressed());

  get_encoder_shift();

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

void loop() {
  int shift{get_encoder_shift()};

  if (button.is_pressed()) {
    while (button.is_pressed()) {
      delay(10);
    }
    ++currentMode;

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

    if (currentMode != Mode_e::ControlOff) {
      ledStrip.display_mode(static_cast<unsigned int>(currentMode) + 1);
    }
  }

  if (shift != 0) {
    switch (currentMode) {
    case Mode_e::ControlOff:
      ControlOff_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::FirstLedOn:
      FirstLedOn_execute(shift);
      break;

    case Mode_e::LedOnLength:
      LedOnLength_execute(shift);
      break;

    default:
      break;
    }
  }

  if (currentMode != Mode_e::ControlOff && shift != 0) {
    ledStrip.refresh();
  }
}

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

/* State Machine functions */

void ControlOff_execute(int shift) {}

void SetColor_execute(int shift) {
  ledStrip.shiftHue(shift);
} /* SetColor_execute */

void SetBrightness_execute(int shift) {
  ledStrip.shiftBrightness(shift);
} /* SetBrightness_execute */

void SetSaturation_execute(int shift) {
  ledStrip.shiftSaturation(shift);
} /* SetSaturation_execute */

void FirstLedOn_execute(int shift) {
  ledStrip.shiftFirstLedOn(shift);
} /* FirstLedOn_execute */

void LedOnLength_execute(int shift) {
  ledStrip.shiftLedOnLenght(shift);
} /* LedOnLength_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;
  }
}