#include <LiquidCrystal_I2C.h>

#define LED_R 9
#define LED_G 10
#define LED_B 11

#define BUTTON_UP 6
#define BUTTON_DOWN 5
#define BUTTON_LEFT 4
#define BUTTON_RIGHT 7
#define BUTTON_ENTER 8
#define BUTTON_HIGH 1

#define LCD_ROWS_COUNT 2
#define LCD_CELL_COUNT 16

#define POS_X_LINE_INFO 1
#define POS_Y_LINE_INFO 0

#define BLINK_INTERVAL 500
#define MASK_CHAR 35
#define START_MODE_EDIT false
#define BUTTON_DELAY() delay(150)

#define CLICK(_BUTTON_) (digitalRead(_BUTTON_) == BUTTON_HIGH)
#define GETLVAL_COLOR_BY_STATE_LED(_COLOR_, _STATE_LCD_) (((uint8_t*)(&_COLOR_))[_STATE_LCD_ - 1])


// ======================================== DEBUG Mode
#define DEBUG_SERIAL 0

#if DEBUG_SERIAL > 0
#define __DEBUG true
#endif

#if __DEBUG
#define DUBUG_ECHO(_TEXT_) Serial.println(_TEXT_)

String nameEnumsStateLCD[] = {
  "Default",
  "EditRed",
  "EditGreen",
  "EditBlue"
};

#else
#define DUBUG_ECHO(_TEXT_)
#endif
// ========================================

enum StateLCD: uint8_t{
	Default   = 0,
  	EditRed   = 1,
  	EditGreen = 2,
  	EditBlue  = 3
};

struct Color{
  uint8_t R;
  uint8_t G;
  uint8_t B;
};

// ======================================== Константные значения
const char* hexTable = "0123456789ABCDEF";
const char* ledComponentNames = "RGB";
const char MaskLine[] = {MASK_CHAR, MASK_CHAR, 0};
// ======================================== Переменные управления цветом светодиода
Color currentColor;
Color editColor;
uint8_t editingValue = 0;
// ======================================== Переменные состояния экрана
LiquidCrystal_I2C lcd(0x27, LCD_CELL_COUNT, LCD_ROWS_COUNT);
uint8_t writedValue = 0;
StateLCD lcdState = StateLCD::Default;
uint64_t lcdTimer = 0;
bool timerState = false;
// ======================================== Инциализация компонент
inline void initLEDS();
inline void initButtons();
inline void initLCD();
// ======================================== Работо с отображением данных
// Попытка обновления вывода
inline void loopLCD();
// Начинает печатать шаблон 'X:'
void startWritePart(StateLCD ledComponent);
// Печатает HEX значение 8 bits
inline void writeHex(uint8_t value);
// Изменяет состояние экрана
void switchStateLCD(StateLCD state);
// Принудительная перерисовка экрана
void forcedUpdateLCD();
// Конфигурирует редактируемые данные в переменной editColor
inline Color* configurateEditingData();
// ======================================== Прочее
// Обработка нажатий
inline void buttonHandler();
// Выводит RGB компоненты на светодиод
inline void applayStateLEDS(Color* newColor);

void setup()
{
  initLEDS();
  initButtons();
  initLCD();
#if __DEBUG
  Serial.begin(DEBUG_SERIAL);
#endif
}

void loop()
{
  buttonHandler();
  loopLCD();
}

inline void buttonHandler(){
  /*
   * Специально не производиться проверка
   * нижней верхней границы для удобства
  */
  if(CLICK(BUTTON_UP)){
    if(editingValue != 255) editingValue++;
    DUBUG_ECHO("BUTTON_UP: " + String(editingValue));
    BUTTON_DELAY();
  }else if(CLICK(BUTTON_DOWN)){
    if(editingValue != 0) editingValue--;
    DUBUG_ECHO("BUTTON_UP: " + String(editingValue));
    BUTTON_DELAY();
  }else if(CLICK(BUTTON_LEFT)){
    switchStateLCD(
      (StateLCD)((lcdState - 1) & StateLCD::EditBlue)
    );
    DUBUG_ECHO("BUTTON_LEFT: " + nameEnumsStateLCD[lcdState]);
    BUTTON_DELAY();
  }else if(CLICK(BUTTON_RIGHT)){
    switchStateLCD(
      (StateLCD)((lcdState + 1) & StateLCD::EditBlue)
    );
    DUBUG_ECHO("BUTTON_RIGHT: " + nameEnumsStateLCD[lcdState]);
    BUTTON_DELAY();
  }else if(CLICK(BUTTON_ENTER)){
    applayStateLEDS(configurateEditingData());
    switchStateLCD(StateLCD::Default);
    DUBUG_ECHO("BUTTON_ENTER: " + nameEnumsStateLCD[lcdState]);
  }
}

void startWritePart(StateLCD ledComponent){
  ledComponent = (StateLCD)(ledComponent - 1);
  lcd.setCursor(POS_X_LINE_INFO + 5 * ledComponent,
                POS_Y_LINE_INFO);
  lcd.print(ledComponentNames[ledComponent]);
  lcd.print(':');
}

inline void writeHex(uint8_t value){
  lcd.print(hexTable[value >> 4]);
  lcd.print(hexTable[value & 15]);
}

void forcedUpdateLCD(){
  for(uint8_t i = 1; i < 4; i++){
     startWritePart((StateLCD)i);
     writeHex(((uint8_t*)(&currentColor))[i - 1]);
  }
  timerState = START_MODE_EDIT;
}

inline void initLCD(){
  lcd.begin(LCD_CELL_COUNT, LCD_ROWS_COUNT);
  forcedUpdateLCD();
}

inline void loopLCD(){
  if(lcdState){
    uint64_t currentMillis = millis();
    bool isUpdate = false;
    
    if(editingValue != writedValue){
    	isUpdate = true;
      	timerState = !START_MODE_EDIT;
    }
    
    if(isUpdate || currentMillis - lcdTimer > BLINK_INTERVAL){
      lcdTimer = currentMillis;
      startWritePart(lcdState);
      if(timerState){
        writeHex(editingValue);
        writedValue = editingValue;
      }else{
        lcd.print(MaskLine);
      }
      timerState = !timerState;
    }
  }
}

inline Color* configurateEditingData(){
  if(lcdState == StateLCD::Default) return &currentColor;
  GETLVAL_COLOR_BY_STATE_LED(editColor, lcdState) = editingValue;
  return &editColor;
}

void switchStateLCD(StateLCD state){
  if(state == lcdState) return;
  if(lcdState != StateLCD::Default){
    startWritePart(lcdState);
  	writeHex(editingValue);
  }
  if(state == StateLCD::Default){
    forcedUpdateLCD();
    editColor = currentColor;
  }else{
    configurateEditingData();
    editingValue = GETLVAL_COLOR_BY_STATE_LED(editColor, state);
  }
  timerState = START_MODE_EDIT;
  lcdState = state;
}

inline void applayStateLEDS(Color* newColor){
  analogWrite(LED_R, newColor->R << 2); // входные данные от 0 - 1023
  analogWrite(LED_G, newColor->G << 2); // битовый сдвиг ускоренная версия деления
  analogWrite(LED_B, newColor->B << 2); // или умножения числа на 2
  currentColor = *newColor;
}

inline void initLEDS(){
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
}

inline void initButtons(){
  pinMode(BUTTON_UP, INPUT);
  pinMode(BUTTON_DOWN, INPUT);
  pinMode(BUTTON_LEFT, INPUT);
  pinMode(BUTTON_RIGHT, INPUT);
  pinMode(BUTTON_ENTER, INPUT);
}