#include <Arduino.h>

// TODO : use core built-in lib tinyNeoPixel
#include <Adafruit_NeoPixel.h>

#include "pin_map.hpp"

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

/* State machine */
#define FOREACH_MODE(MODE)                                                     \
  MODE(Idle)                                                                   \
  MODE(Chaser)                                                                 \
  MODE(Rainbow)

#define GENERATE_MODE_ENUM(ENUM) ENUM,

enum class Mode_e { FOREACH_MODE(GENERATE_MODE_ENUM) };

Mode_e &operator++(Mode_e &mode) {
  if (mode == Mode_e::Rainbow) {
    return mode = Mode_e::Idle;
  }

  return mode = static_cast<Mode_e>(static_cast<int>(mode) + 1);
}

#ifdef DEBUG
#define GENERATE_MODE_STRING(STRING) #STRING,
char static const *mode_str[] = {FOREACH_MODE(GENERATE_MODE_STRING)};
#endif /*DEBUG*/

/* Leds */
uint16_t static const LedCount{300};

/* Colors */
uint32_t const Red{0x00FF0000};
uint32_t const Green{0x0000FF00};
uint32_t const Blue{0x000000FF};

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

/* State machine */
Mode_e currentMode{Mode_e::Idle};

/* Feedback led state */
bool feedbackLedState{HIGH};

/*  IO Objects */
Adafruit_NeoPixel ledBar{LedCount, LedPin, NEO_GRB + NEO_KHZ800};

/* debug Serial definition */
#if defined(__PLATFORMIO_BUILD_DEBUG__)
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_PROMICRO16)
Serial_ &debugSerial = Serial;
#else
#warning "No serial defined for debug"
#endif
#endif

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

bool has_mode_changed(char c);

/* State machine */
#define GENERATE_MODE_EXEC_DECLARTION(MODE) void MODE##_execute(char c);
FOREACH_MODE(GENERATE_MODE_EXEC_DECLARTION)

/* Function definitions */
/*------------------------------------------------------------------------------------------------*/
void setup() {
  pinMode(FeedbackLedPin, OUTPUT);
  digitalWrite(FeedbackLedPin, feedbackLedState);
#if defined(__PLATFORMIO_BUILD_DEBUG__)
  debugSerial.begin(9600);
  while (!debugSerial.available())
    ;
  debugSerial.println("Init ... ");
#endif /*DEBUG*/

  ledBar.begin();
  ledBar.setBrightness(50);
  ledBar.show(); // Initialize all pixels to 'off'
}

unsigned i = 0;

void loop() {
  char c = debugSerial.read();

  //   if (c == '\n') {
  //     if (i >= LedCount) {
  //       i = 0;
  //     }
  //     debugSerial.print("Lighting ");
  //     debugSerial.println(i, DEC);
  //     ledBar.setPixelColor(i++, ledBar.Color(100, 0, 0));
  //   }
  // }
  if (has_mode_changed(c)) {
    ledBar.clear();
  }

  switch (currentMode) {
#define GENERATE_MODE_EXEC_CALL(MODE)                                          \
  case Mode_e::MODE:                                                           \
    MODE##_execute(c);                                                         \
    break;
    FOREACH_MODE(GENERATE_MODE_EXEC_CALL)

  default:
    break;
  }

  ledBar.show();
}

bool has_mode_changed(char c) {
  Mode_e previousMode = currentMode;
  switch (c) {
  case 'y':
    currentMode = Mode_e::Idle;
    break;
  case 'u':
    currentMode = Mode_e::Chaser;
    break;
  case 'i':
    currentMode = Mode_e::Rainbow;
    break;
  case 'o':
    break;
  case 'h':
    break;
  case 'j':
    break;
  case 'k':
    break;
  case 'l':
    break;
  default:
    return false;
  }
  return currentMode != previousMode;
}

void Idle_execute(char c) {
  switch (c) {
  case 'r':
    debugSerial.println("\nLighting all in red");
    ledBar.fill(Red);
    break;
  case 'g':
    debugSerial.println("\nLighting all in green");
    ledBar.fill(Green);
    break;
  case 'b':
    debugSerial.println("\nLighting all in blue");
    ledBar.fill(Blue);
    break;
  case 'n':
    debugSerial.println("\nReset");
    ledBar.clear();
    break;
  }
}

void Chaser_execute(char c) { (void)c; }
void Rainbow_execute(char c) { (void)c; }
