// https://programmersqtcpp.blogspot.com/2022/04/arduino-lcd-con-sprintf.html

#include <Wire.h>
//#include <U8glib.h>
//#include <IRremote.h>
#include <IRremote.hpp>
//#include "RTClib.h"
//#include <U8x8lib.h>
#include <JC_Button.h>

Button btnEncoder(7);

#define IR_RECEIVE_PIN 2   // Signal Pin of IR receiver
//IRrecv receiver(PIN_RECEIVER);
static
const uint8_t SEQ_TAB[7][4] = {
      { 0, 4, 1, 0 }  // 0
    , { 2, 1, 1, 0 }  // 1
    , { 2, 3, 1, 2 }  // 2
    , { 2, 3, 3, 0 }  // 3
    , { 5, 4, 4, 0 }  // 4
    , { 5, 4, 6, 5 }  // 5
    , { 5, 6, 6, 0 }  // 6
};

// dichiarazione preventiva di GCounter
template <typename T>
class GCounter;
// classe Encoder 
class Encoder {
  public:
  // default ctor
  // Encoder encoder;
  void begin(uint8_t a, uint8_t b, void (*userFunc)(void)) {
    m_pinA = a;
    m_pinB = b;
    attachInterrupt(digitalPinToInterrupt(m_pinA), userFunc, CHANGE);    
    attachInterrupt(digitalPinToInterrupt(m_pinB), userFunc, CHANGE);  

  }
  int16_t getCounter() {
    int16_t s = counter;
    clear();
    m_isChanged = false;
    return s;
  }

  void clear() { 
    m_oldCounter = counter = 0; 
    
  }
  bool isChanged() { return m_isChanged; }
  enum ptrType_e {enullptr, eiptr, efptr};
  void isrEvent() {
      uint8_t ab = (digitalRead(m_pinB) << 1)
                      | digitalRead(m_pinA);
  
      if (3 == m_seq  &&  3 == ab) {
          counter++;
          // non gli sta bene la forward declare di GCounter
          // dovrò usare qualche funzione di libreria stl
          /*GCounter<int> *gc = (GCounter<int> *)pGcounter;
          gc->inc();*/
          //Serial.println(counter);
      } else if (6 == m_seq  &&  3 == ab) {
          counter--;
      }
      if (counter != m_oldCounter) {
          m_oldCounter = counter;
          m_isChanged = true;
      }
      m_seq = SEQ_TAB[m_seq][ab];

  }
  void setGCounter(GCounter<int> *iptr) {
      pGcounter = iptr;
      ptyp = eiptr;
  }
  void setGCounter(GCounter<float> *fptr) {
      pGcounter = fptr;
      ptyp = efptr;
  }
  private:
  uint8_t m_seq;
  volatile int16_t counter;
  int16_t m_oldCounter;
  bool m_isChanged;
  uint8_t m_pinA;
  uint8_t m_pinB;
  void *pGcounter;
  ptrType_e ptyp = enullptr;

};

template <typename T>
class GCounter {
  public:
  GCounter(T v, T l, T h, T s ) {
    oldCounter = counter = v;
    low = l;
    high = h;
    step = s;
    toRange();
  }
  // restituisce il valore del contatore counter
  T value() {
    oldCounter = counter;
    m_isChanged = false;
    return counter;
  }

  void inc() {}
  // void setEncoder(Encoder *ePtr)
  // Obligatorio assegnare un puntatore ad oggetto 
  // di classe Encoder
  void setEncoder(Encoder *ePtr) {
    m_enc = ePtr;
    ePtr->clear();
  }
  bool isChanged() {
    return m_isChanged;
  }

  void run() {
    if (m_enc == nullptr && !m_enc->isChanged())
      return;
    
    int16_t i = m_enc->getCounter();
    counter += i * step;
    toRange();
    if (counter != oldCounter) {
      oldCounter = counter;
      m_isChanged =  true;
    }
  }

  private:
  // void toRange()
  // riporta il valore al range specificato con:
  // l low value
  // h high value
  void toRange() {
    if (counter > high) {
      counter = high;
    } else if (counter < low) {
      counter = low;
    }
  }
  bool m_isChanged;

  Encoder *m_enc; // puntatore a classe Encoder
  T oldCounter;
  T counter;
  T low;  // valore minimo
  T high; // valore massimo consentito
  T step;  // di quanto incrementa/decrementa counter 
};

Encoder enc0;



GCounter<int16_t> acounter(-1000, -100, 100, 1);
GCounter<float> bcounter(-63, -63, 0, 0.5);
//GCounter *gptr = &bcounter;
float vf = 101.5;
// errore: -std=c++1z deduce il tipo dalla variabile
//GCounter vfc(vf, 0.0, 200.0, 1.0);


//U8X8_SSD1306_128X64_NONAME_HW_I2C oled(/* reset=*/U8X8_PIN_NONE);


/*void oledInit() {
    oled.setBusClock(400000);
    oled.begin();
    oled.setPowerSave(0);
}*/


//uint8_t m_seq;
void isrEnc() {
  
  enc0.isrEvent();
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  btnEncoder.begin();
  //pinMode(18, INPUT_PULLUP);
  //pinMode(19, INPUT_PULLUP);
  enc0.begin(19, 18, isrEnc);
  //bcounter.setEncoder(&enc0);
    
  IrReceiver.begin(IR_RECEIVE_PIN);
  //oledInit();
    
}

uint8_t state;

void loop() {
  //uint8_t signalStates = 0b11011100; // Leggi gli stati dei segnali utilizzando la manipolazione diretta del port

  //int status = (~signalStates >> 2) & 0b00111111;
  //Serial.println(status);
  btnEncoder.read();
  switch (state) {
    case 0:
      bcounter.setEncoder(&enc0);
      Serial.println("## Change bcounter ##");
      Serial.println(bcounter.value());
      state = 1;
    break;
    case 1:
      bcounter.run();
      if (bcounter.isChanged()) {
        auto v = bcounter.value();
        Serial.println(v);
      }
      if (btnEncoder.wasPressed()) 
        state = 2;
    break;
    case 2:
      acounter.setEncoder(&enc0);
      Serial.println("## Change acounter ##");
      Serial.println(acounter.value());
      state = 3;
    break;
    case 3:
      acounter.run();
      if (acounter.isChanged()) {
        auto v = acounter.value();
        Serial.println(v);
      }
      if (btnEncoder.wasPressed()) 
        state = 0;
    break;

  }
  
} // end void loop()