#include <LiquidCrystal_I2C.h>
#include <RotaryEncoder.h>

//
// globale Konstanten
//
constexpr uint8_t laserPin = 9;   // Pin für die Laserdiode
constexpr uint8_t ledPin = 10;    // Pin für die Leuchtdiode
constexpr uint8_t ldrPin = A0;    // Pin für die Fotozelle
constexpr uint8_t pin_vz_A = 2;   // DT - Drehgeber für Verzögerung PIN A
constexpr uint8_t pin_vz_B = 3;   // CLK - Drehgeber für Verzögerung PIN B
constexpr uint8_t pin_sw_A = 4;   // DT - Drehgeber für Schwellenwert PIN A
constexpr uint8_t pin_sw_B = 5;   // CLK - Drehgeber für Schwellenwert PIN B
constexpr unsigned int minTime_ms {0};
constexpr unsigned int maxTime_ms {1000};

//
// Klassendefinitionen
//
class Timer {
public:
  void start() { timeStamp = millis(); }
  bool operator()(const uint32_t duration) { return (millis() - timeStamp >= duration) ? true : false; }

private:
  uint32_t timeStamp {0};
};

//
// Extend the RotaryEncoder class with
// 1. a lower and upper limit for a counter
// 2. a variable step count
//
template <typename T> class RotaryEncoderExt : public RotaryEncoder {
public:
  RotaryEncoderExt(uint8_t pin1, uint8_t pin2, T valMin, T valMax, LatchMode mode = LatchMode::FOUR0)
      : RotaryEncoder {pin1, pin2, mode}, valMin {valMin}, valMax {valMax} {}
  template <typename V> bool query(V &value);
  void setStep(int value) { step = value; }

private:
  const T valMin;   // value minimum
  const T valMax;   // value maximum
  int step {1};
};

template <typename T> template <typename V> bool RotaryEncoderExt<T>::query(V &value) {
  uint8_t flag {true};
  tick();
  switch (getDirection()) {
    case RotaryEncoder::Direction::NOROTATION: flag = false; break;
    case RotaryEncoder::Direction::CLOCKWISE: value = value < valMax ? value + step : valMin; break;
    case RotaryEncoder::Direction::COUNTERCLOCKWISE: value = value > valMin ? value - step : valMax; break;
  }
  return flag;
}
// ---- RotaryEncoderExt End ------------

enum class Status : uint8_t { idle, active, waitReady };

//
// Globale Variablen/Objekte
//

// Erstelle einen Encoder-Objekt
using Encoder = RotaryEncoderExt<decltype(maxTime_ms)>;
Encoder encVz {pin_vz_A, pin_vz_B, minTime_ms, maxTime_ms, RotaryEncoder::LatchMode::FOUR3};
Encoder encSw {pin_sw_A, pin_sw_B, minTime_ms, maxTime_ms, RotaryEncoder::LatchMode::FOUR3};
Timer wait;

// Erstelle ein LCD-Objekt
LiquidCrystal_I2C lcd(0x27, 16, 2);   // LCD-Adresse (muss ggf. angepasst werden)

unsigned int verz = 300;   // Anfangswert für Verzögerung
unsigned int sw = 200;     // Anfangswert für Schwellenwert

//
// Funktionen
//
void initDisplay() {
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("VZ-Wert:");
  lcd.setCursor(0, 1);
  lcd.print("SW-Wert:");
}

// Funktion zum Aktualisieren des Displays
void updateDisplay() {
  // Erste Zeile für Verzögerung
  lcd.setCursor(9, 0);     // Setze den Cursor auf die zweite Zeile
  lcd.print("        ");   // Leere Zeile
  lcd.setCursor(9, 0);     // Setze den Cursor wieder auf die zweite Zeile
  lcd.print(verz);         // Zeige den aktuellen Wert an

  // Zweite Zeile für Schwellenwert
  lcd.setCursor(9, 1);     // Setze den Cursor auf die zweite Zeile
  lcd.print("        ");   // Leere Zeile
  lcd.setCursor(9, 1);     // Setze den Cursor wieder auf die zweite Zeile
  lcd.print(sw);           // Zeige den aktuellen Wert an
}

//
// Hauptprogramm
//
void setup() {
  Serial.begin(115200);
  pinMode(laserPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  digitalWrite(laserPin, HIGH);   // Laserdiode einschalten

  encVz.setStep(5);   // Jeder klick verändert den Encoderwert je nach Drehrichtung um +/- 5
  encSw.setStep(5);
  initDisplay();
  updateDisplay();
}

void loop() {
  static Status swStatus {Status::idle};
  unsigned int prevAnalogVal {0};

  if (encVz.query(verz)) { updateDisplay(); }
  if (encSw.query(sw)) { updateDisplay(); }

  unsigned int analogVal = analogRead(ldrPin);
  if (analogVal != prevAnalogVal) {   // Folgendes nur durchführen, wenn sich der Messwert geändert hat
    prevAnalogVal = analogVal;
    if (analogVal > sw) {
      wait.start();   // Der Schwellenwert wurde überschritten also Timer für die Verzögerungszeit starten.
      swStatus = Status::active;
      if (digitalRead(ledPin) == HIGH) { digitalWrite(ledPin, LOW); }
    }
  }

  switch (swStatus) {
    case Status::active:
      if (wait(verz)) {               // LED nach Ablauf der eingestellten Verzögerungszeit einschalten
        wait.start();                 // Timer für nächste Warteperiode starten
        digitalWrite(ledPin, HIGH);   // LED einschalten
        swStatus = Status::waitReady;
      }
      break;
    case Status::waitReady:
      if (wait(1000)) {              // Wenn zweite Warteperiode abgelaufen ist, LED wieder ausschalten.
        digitalWrite(ledPin, LOW);   // LED ausschalten
        swStatus = Status::idle;
      }
    default: break;
  }
}