#include <RotaryEncoder.h>
#include <Tone.h>
#include <LiquidCrystal_I2C.h>
#include "Button.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};

constexpr byte I2C_ADDR {0x27};
constexpr byte LCD_COLUMNS {16};
constexpr byte LCD_LINES   {2};

enum class ProgState : byte {adjust = 0, start, running, stop, idle};

struct EncoderData {
  byte pos;
  byte newPos;
  byte value;
};

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);
Btn::Button btn{pin_btn};
EncoderData encData {0, 0, MIN_HERTZ};
Tone Generator;

void askEncoder(EncoderData& );

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

  btn.begin();
  btn.setDebounceTime_ms(150);
  lcd.init();
  lcd.backlight();
  Generator.begin(pin_led);
}

void loop()
{
  static byte oldValue {1};
  static ProgState state {ProgState::adjust};

  askEncoder(encData);
  if (encData.value != oldValue) {
    oldValue = encData.value;
    state = ProgState::adjust;
  }
  switch (state) {
    case ProgState::adjust:
      Generator.stop();
      char buffer[10]; // Achtung: buffer an Textlänge + 1 anpassen. Sonst gibt es komische Effekte.
      sprintf(buffer, "%d Hz", encData.value);
      lcd.clear();
      lcd.setCursor(4, 0);
      lcd.print(buffer);
      lcd.setCursor(0, 1);
      lcd.print("Aus...");
      state = ProgState::idle;
      break;
    case ProgState::start:
      lcd.setCursor(0, 1);
      lcd.print("Ein...");
      state = ProgState::running;
      [[fallthrough]]
    case ProgState::running:
      Generator.play(encData.value);
      break;
    default:
      break;
  }

  if (btn.tick()) {    // Tasterabfrage
    state = ProgState::start;
    //Serial.println("Hertz übernommen");
  }
}

void askEncoder(EncoderData& data) {
  encoder.tick();
  data.newPos = encoder.getPosition();
  if (data.pos != data.newPos) {
    data.pos = data.newPos;
    int dir = static_cast<int>(encoder.getDirection());
    if (dir >= 0) {
      data.value = (++data.value > MAX_HERTZ) ? MIN_HERTZ : data.value;
    }
    if (dir < 0) {
      data.value = (--data.value < MIN_HERTZ) ? MAX_HERTZ : data.value;
    }
  }
}