#include <U8g2lib.h>   // u8g2
#include <Wire.h>      // u8g2

using ulong = unsigned long int;
using uint = unsigned int;

//////////////////////////////////////////////////////////////////////////////
/// @brief Helper class for debouncing input signals (e.g. push-buttons)
///
//////////////////////////////////////////////////////////////////////////////
class Debounce {
public:
  Debounce(unsigned int dbt) : debounceTime {dbt} {}
  bool operator()(const uint8_t, const uint8_t);

private:
  uint debounceTime;
  uint8_t prevPinState;
  ulong timeStamp {0};
};

bool Debounce::operator()(const uint8_t pin, uint8_t activePinState = LOW) {
  bool isDebounceReady = false;
#ifdef __digitalWriteFast_h_
  uint8_t pinState = digitalReadFast(pin);
#else
  uint8_t pinState = digitalRead(pin);
#endif
  if (pinState != prevPinState) {   // Restart the debounce timer with every edge change
    timeStamp = millis();
    prevPinState = pinState;
  } else if (pinState == activePinState && (millis() - timeStamp >= debounceTime)) {
    isDebounceReady = true;   // No more edge changes within the debounce time
  }
  return isDebounceReady;
}

//
// global constants

constexpr byte REED_PIN {5};
constexpr byte SCREEN_WIDTH {128};   // OLED display width, in pixels
constexpr byte SCREEN_HEIGHT {64};   // OLED display height, in pixels
constexpr byte X_POS {30};
constexpr byte Y_POS {40};
constexpr ulong CLEAR_DELAY_MS {5000};

//
// globale objects/variables
//
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2 {U8G2_R0, U8X8_PIN_NONE};
enum class State : byte { idle, count, clear } state;

Debounce debounce {10};   // Debounce Time 10ms / active low is default
uint lastActionTime {0};
uint lastSecond {0};
char buffer[6] {"00:00"};

//
// Functions
//
void display(byte x, byte y, const char fBuffer[]) {
  u8g2.firstPage();
  do { u8g2.drawStr(x, y, fBuffer); } while (u8g2.nextPage());
}

void setup() {
  pinMode(REED_PIN, INPUT_PULLUP);   // Taster, später Reed Kontakt
  state = State::idle;
  u8g2.begin();
  u8g2.setFont(u8g2_font_logisoso22_tn);
  display(X_POS, Y_POS, buffer);
}

void loop() {
  switch (state) {
    case State::idle:
      if (true == debounce(REED_PIN)) {   // Timer gestartet
        lastActionTime = millis();
        state = State::count;
      }
      break;
    case State::count: {
      uint seconds = (millis() - lastActionTime) / 1000UL;
      uint minutes = seconds / 60;
      seconds = seconds % 60;
      if (lastSecond != seconds) {
        lastSecond = seconds;
        snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes, seconds);
        display(X_POS, Y_POS, buffer);
      }
      if (HIGH == digitalRead(REED_PIN)) {   // Taster losgelassen
        lastActionTime = millis();
        state = State::clear;
      }
    } break;
    case State::clear:   // Anzeige stehen lassen
      if (millis() - lastActionTime > CLEAR_DELAY_MS) {
        display(X_POS, Y_POS, "00:00");
        state = State::idle;
      }
      break;
    default: break;
  }
}