#include <digitalWriteFast.h>
#include <RotaryEncoder.h>
#include <Button_SL.hpp>

//
// Gobal constants
//
constexpr byte PIN_IN1 {2};
constexpr byte PIN_IN2 {3};
constexpr byte PIN_BTN {13};

constexpr int16_t VALUE_MIN {1};      // Counter minimum value
constexpr int16_t VALUE_MAX {1000};   // Counter maximum value

constexpr uint8_t MAX_DIGITS {4};
constexpr uint8_t MAX_SEGMENTS {8};
constexpr uint8_t DEC_PLACE_PIN[MAX_DIGITS] {A0, A1, A2, A3};
constexpr uint8_t DIGIT_PIN[MAX_SEGMENTS] {4, 5, 6, 7, 8, 9, 10, 11};

constexpr uint8_t SEGMENT_BITS[10] = {
  //  afbgcDde       character
    0b11101011,   // 0
    0b00101000,   // 1
    0b10110011,   // 2
    0b10111010,   // 3
    0b01111000,   // 4
    0b11011010,   // 5
    0b11011011,   // 6
    0b10101000,   // 7
    0b11111011,   // 8
    0b11111010,   // 9
};

//
// Data definitions
//

//
// Extend the RotaryEncoder class with a lower and upper limit for a counter
//
class RotaryEncoderEnhanced : public RotaryEncoder {
public:
  RotaryEncoderEnhanced(int pin1, int pin2, int min, int max, LatchMode mode = LatchMode::FOUR0)
      : RotaryEncoder {pin1, pin2, mode}, valMin {min}, valMax {max} {}
  int getMin() const { return valMin; }
  int getMax() const { return valMax; }

private:
  const int valMin;   // Counter minimum value
  const int valMax;   // Counter maximum Value
};

//
// Global objects / variables
//
RotaryEncoderEnhanced encoder {PIN_IN1, PIN_IN2, VALUE_MIN, VALUE_MAX, RotaryEncoder::LatchMode::FOUR3};
Btn::ButtonSL btn {PIN_BTN};

//
// Function(s).
//

//////////////////////////////////////////////////////////////////////////////
/// @brief Query encoder
///
/// @param enc        Ecoder object
/// @param value      Value that changes with each encoder rotation
/// @param minValue   Minimum value
/// @param maxValue   Maximum value
/// @return true      Value has changed
/// @return false     no change
//////////////////////////////////////////////////////////////////////////////
bool queryEncoder(RotaryEncoderEnhanced &enc, int &value) {
  byte flag {true};
  enc.tick();
  switch (enc.getDirection()) {
    case RotaryEncoder::Direction::NOROTATION: flag = false; break;
    case RotaryEncoder::Direction::CLOCKWISE: value = value < enc.getMax() ? value + 1 : enc.getMin(); break;
    case RotaryEncoder::Direction::COUNTERCLOCKWISE: value = value > enc.getMin() ? value - 1 : enc.getMax(); break;
  }
  return flag;
}

void digitsOff() {
  // Clear Digits
  for (auto dPP : DEC_PLACE_PIN) { digitalWriteFast(dPP, HIGH); }
}

void digit(uint8_t nbr, uint8_t dpPinIdx) {
  for (uint8_t i = 0; i < MAX_SEGMENTS; ++i) { digitalWriteFast(DIGIT_PIN[i], ((SEGMENT_BITS[nbr] >> i) & 0x01)); }
  digitalWriteFast(DEC_PLACE_PIN[dpPinIdx], LOW);
}

void setDot(uint8_t pos) {
  if (pos > 0 && pos < MAX_DIGITS) {
    for (uint8_t i = 0; i < MAX_SEGMENTS; ++i) { digitalWriteFast(DIGIT_PIN[i], ((0b00000100 >> i) & 0x01)); }
    digitalWriteFast(DEC_PLACE_PIN[pos], LOW);
  }
}

void numberToDisplay(int16_t value, uint8_t dgt[], uint8_t dot) {
  static uint8_t activeDigit {0};
  switch (activeDigit) {
    case 0: digit(dgt[0], 0); break;
    case 1: if (value >   9) { digit(dgt[1], 1); } break;
    case 2: if (value >  99) { digit(dgt[2], 2); } break;
    case 3: if (value > 999) { digit(dgt[3], 3); } break;
    case 4: setDot(dot); break;
  }
  if (++activeDigit == (MAX_DIGITS + 1)) { activeDigit = 0; }
}

void breakNumberDown(uint8_t dgt[], uint16_t nbr) {
  dgt[3] = nbr / 1000;
  nbr %= 1000;
  dgt[2] = nbr / 100;
  nbr %= 100;
  dgt[1] = nbr / 10;
  nbr %= 10;
  dgt[0] = nbr;
}

//
// Hauptprogramm
//
void setup() {
  for (auto dP : DIGIT_PIN) { pinMode(dP, OUTPUT); }
  for (auto dPP : DEC_PLACE_PIN) { pinMode(dPP, OUTPUT); }
  btn.begin();
}

void loop() {
  static int16_t lastNumber {0};
  static int16_t number {VALUE_MIN};
  static uint8_t digits[MAX_DIGITS];
  static uint16_t dotPos;

  queryEncoder(encoder, number);  // Check if encoder was turned
  dotPos = (number == 1000) ? 3 : 0;  // Show a dot at position 3 if number becomes 1000 
  if (btn.tick() != Btn::ButtonState::notPressed) {
    number = VALUE_MIN;
  }
  if (number != lastNumber) { 
    breakNumberDown(digits, number);
    lastNumber = number;
  }
  digitsOff(); // Number changed, so clear display for the new number
  numberToDisplay(number, digits, dotPos);
}
Push Button (middle) to reset number. Turn encoder (arrows) to change the value