#include <MD_MAX72xx.h>    // https://github.com/MajicDesigns/MD_MAX72XX
#include <MD_Parola.h>     // https://github.com/MajicDesigns/MD_Parola
#include <Button_SL.hpp>   // https://github.com/DoImant/Button_SL
#include <SPI.h>

namespace gc {
constexpr uint8_t clkPin {13};
constexpr uint8_t dataPin {11};
constexpr uint8_t csPin {10};
constexpr uint8_t kickPin {2};

constexpr uint8_t numCounter {4};
constexpr uint8_t maxDevices {5};

constexpr uint32_t kickDelay_ms {3000};
constexpr uint32_t kickDuration_ms {500};
}

class Interval {
public:
  bool operator()(const uint32_t duration) {
    if (false == isStarted) { return start(false); }
    return (millis() - timeStamp >= duration) ? start(true) : false;
  }
  void reset() { isStarted = false; }

private:
  bool start(bool state = false) {
    isStarted = !state;   // Set the value to true on the first call
    timeStamp = millis();
    return state;
  }

private:
  bool isStarted {false};   // Flag = true if the first Operator() call has been made.
  uint32_t timeStamp {0};
};

class BallKicker {
public:
  BallKicker(Btn::Button& btn, uint8_t pin, uint32_t kDelay_ms, uint32_t kDuration_ms)
      : btn {btn}, pin {pin}, kDelay_ms {kDelay_ms}, kDuration_ms {kDuration_ms} {}
  void begin() { pinMode(pin, OUTPUT); }
  void check() { isKickerActive ? kicker() : checkPin(); };

private:
  void checkPin() {
    if (!btn.tick()) {
      timer.reset();
    } else if (timer(kDelay_ms)) {
      digitalWrite(pin, HIGH);
      isKickerActive = true;
    }
  }
  void kicker() {
    if (timer(kDuration_ms)) {
      digitalWrite(pin, LOW);
      isKickerActive = false;
    }
  }

private:
  Btn::Button& btn;
  uint8_t pin;
  uint32_t kDelay_ms;
  uint32_t kDuration_ms;
  Interval timer;
  bool isKickerActive {false};
};

// KLasse zum speichern und addieren der Punktzahl, mit der sie initialisiert wird
class ScoreCounter {
public:
  ScoreCounter(uint16_t points, uint32_t sum = 0) : points {points}, sum {sum} {}
  uint16_t operator()() {   // Addiere pro Kontakt die initialisierte Punktzahl
    sum += points;
    return points;          // Gib initialisierte Punktzahl zurück. Z.b. für eine andere Addition
  }
  uint32_t getSum() const { return sum; }   // Gebe Summe für die Kontakte * initialisierte Punktzahl zurück

private:
  const uint16_t points;
  uint32_t sum;
};

//
// Abfrage der Kontaktpins. Diese sind Active-Low konfiguriert (INPUT_PULLUP)
// Kann über die Buttonklasse aber auch auf Active High umkonfigurierte werden.
// Rückgabewert ist der Index des Kontaktpins im Array. (-1) = kein Kontakt festgestellt.
//
template <size_t N> int checkSignals(Btn::ButtonSL (&btn)[N]) {
  for (size_t i = 0; i < N; ++i) {
    if (btn[i].tick() != Btn::ButtonState::notPressed) {
      return i;
    }
  }
  return -1;
}

// Array für die Pinabfrage anlegen. Das Lesen der Pins erfolgt entprellt
// Standard Entprellzeit ist 30ms
Btn::ButtonSL signals[gc::numCounter] {{4}, {5}, {6}, {7}};

// Für jede gewünschte Punktzahl (pro Kontakt) ein Objekt anlegen
// Der Array-Reihenfolge (Index) muss mit dem Button Array identisch sein
ScoreCounter score[gc::numCounter] {10, 100, 1000, 10000};

Btn::Button btnKicker {3};
BallKicker bKicker{btnKicker,gc::kickPin,gc::kickDelay_ms,gc::kickDuration_ms};

// Display
MD_Parola mx = MD_Parola(MD_MAX72XX::PAROLA_HW, gc::csPin, gc::maxDevices);

void setup() {
  Serial.begin(115200);
  // Initialisieren der Buttonobjekte
  for (auto& signal : signals) {
    signal.begin();
    signal.setDebounceTime_ms(15);
  }   
  btnKicker.begin();
  bKicker.begin();
  mx.begin();
  mx.displayText("", PA_RIGHT, 0, 0, PA_PRINT, PA_NO_EFFECT);
  mx.print(0);
}

void loop() {
  static uint32_t scoreSum {0};   // Speicher für die Punktzahlsumme

  int8_t index = checkSignals(signals);   // Abfrage der Kontaktpins
  switch(index) {
    case 0 ... 3:
      scoreSum += score[index]();
      mx.print(scoreSum);                   // Kein Test auf Überlauf Stellenzahl!
      Serial.print("Score: ");
      Serial.println(scoreSum);
    break;
    default: break;
  }
  bKicker.check();
}
10er
100er
1000er
10000er
kicker