#include <RotaryEncoder.h>
#include <LiquidCrystal_I2C.h>
#include "PwmTimer1.hpp"
#include "Button_SL.hpp"


constexpr byte MAX_HERTZ{15};
constexpr byte MIN_HERTZ{1};
constexpr byte pin_in1{2};
constexpr byte pin_in2{3};
constexpr byte pin_btn{4};
constexpr byte pin_led{13};

#ifndef LCD20x4
constexpr byte I2C_ADDR{0x27};
constexpr byte LCD_COLUMNS{16};
constexpr byte LCD_LINES{2};
#else
constexpr byte I2C_ADDR{0x3F};
constexpr byte LCD_COLUMNS{20};
constexpr byte LCD_LINES{4};
#endif

enum class ProgState : byte { adjust = 0, start, running, idle };
enum class InputState : byte { hertz = 0, duty };

struct EncoderData {
  byte pos;
  byte newPos;
  byte hertz;
  byte dutyc;
};

using namespace Btn;
ButtonSL btn{pin_btn};

LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
// RotaryEncoder encoder(pin_in1, pin_in2, RotaryEncoder::LatchMode::FOUR0);
RotaryEncoder encoder(pin_in1, pin_in2, RotaryEncoder::LatchMode::FOUR3);
// RotaryEncoder encoder(pin_in1, pin_in2, RotaryEncoder::LatchMode::TWO03);
EncoderData encData{0, 0, MIN_HERTZ, 10};

bool askEncoder(EncoderData &, InputState);
void outputLcd(char*, EncoderData&, InputState);

void setup() {
  Serial.begin(115200);
  Serial.println("Start");

  btn.begin();
  btn.releaseOn();
  btn.setDebounceTime_ms(150);
  lcd.init();
  lcd.backlight();
  Pwm.begin(encData.hertz, encData.dutyc);
}

void loop() {
  static bool lastBtnAction{false};
  static char buffer[LCD_COLUMNS + 1]; // Achtung: buffer an Textlänge + 1 anpassen. Sonst gibt es komische Effekte.
  static ProgState state{ProgState::adjust};
  static InputState inState{InputState::hertz};

  if (askEncoder(encData, inState)) {
    state = ProgState::adjust;
  }
  switch (state) {
    case ProgState::adjust:
      Pwm.stopPwm();
      buffer[0] = ' ';
      outputLcd(buffer, encData, inState);
      state = ProgState::idle;
      break;
    case ProgState::start:
      buffer[0] = 'R';
      lcd.setCursor(0, 1);
      lcd.print(buffer[0]);
      state = ProgState::running;
      [[fallthrough]]   // https://en.cppreference.com/w/cpp/language/attributes/fallthrough
    case ProgState::running:
      Pwm.setHz(encData.hertz);
      Pwm.setDutyCycle(encData.dutyc);
      Pwm.startPwm();
      state = ProgState::idle;
      break;
    default: break;
  }

  // Tasterabfrage
  switch (btn.tick()) {
    case ButtonState::longPressed:
      lastBtnAction = !lastBtnAction;
      if (lastBtnAction) {
        state = ProgState::start;     // Will swtich PWM on
      } else {
        state = ProgState::adjust;    // Will switch PWM off
      }
      break;
    case ButtonState::shortPressed:
      switch (inState) {
        case InputState::duty: inState = InputState::hertz; break;
        case InputState::hertz: inState = InputState::duty; break;
        default: break;
      }
      outputLcd(buffer, encData, inState);
      break;
    default: break;
  }
}

bool askEncoder(EncoderData &data, InputState inState) {
  encoder.tick();
  data.newPos = encoder.getPosition();
  if (data.pos != data.newPos) {
    data.pos = data.newPos;
    int dir = static_cast<int>(encoder.getDirection());
    switch (inState) {
      case InputState::hertz:
        if (dir > 0) {
          data.hertz = (++data.hertz > MAX_HERTZ) ? MIN_HERTZ : data.hertz;
        }
        if (dir < 0) {
          data.hertz = (--data.hertz < MIN_HERTZ) ? MAX_HERTZ : data.hertz;
        }
        break;
      case InputState::duty:
        if (dir > 0) {
          data.dutyc = (++data.dutyc > 100) ? 0 : data.dutyc;  // 100% or less
        }
        if (dir < 0) {
          data.dutyc = (--data.dutyc > 100) ? 100 : data.dutyc;  // Unsigned! -1 becomes 255
        }
        break;
      default: break;
    }
    return true;
  }
  return false;
}

void outputLcd(char *text, EncoderData &ed, InputState is) {
  bool isRunning = (text[0] == 'R') ? true : false;
  sprintf(text, "%3d Hz  Tv: %3d%%", ed.hertz, ed.dutyc);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(text);
  memset(text, 32, LCD_COLUMNS);
  text[LCD_COLUMNS + 1] = '\0';
  switch (is) {
    case InputState::hertz: text[2] = '^'; break;
    case InputState::duty: text[14] = '^'; break;
    default: break;
  }
  if (isRunning) {
    text[0] = 'R';
  }
  lcd.setCursor(0, 1);
  lcd.print(text);
}
$abcdeabcde151015202530fghijfghij