// 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;

template<typename T> 
void t_constrain(T &val, T min, T max) {
    if (val < min)
        val = min;
    if (val > max)
        val = max;
}

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();
  float v = 10;
  t_constrain(v, -63.5f, 0.0f);
  Serial.println(v);
    
}

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()
mega:SCL
mega:SDA
mega:AREF
mega:GND.1
mega:13
mega:12
mega:11
mega:10
mega:9
mega:8
mega:7
mega:6
mega:5
mega:4
mega:3
mega:2
mega:1
mega:0
mega:14
mega:15
mega:16
mega:17
mega:18
mega:19
mega:20
mega:21
mega:5V.1
mega:5V.2
mega:22
mega:23
mega:24
mega:25
mega:26
mega:27
mega:28
mega:29
mega:30
mega:31
mega:32
mega:33
mega:34
mega:35
mega:36
mega:37
mega:38
mega:39
mega:40
mega:41
mega:42
mega:43
mega:44
mega:45
mega:46
mega:47
mega:48
mega:49
mega:50
mega:51
mega:52
mega:53
mega:GND.4
mega:GND.5
mega:IOREF
mega:RESET
mega:3.3V
mega:5V
mega:GND.2
mega:GND.3
mega:VIN
mega:A0
mega:A1
mega:A2
mega:A3
mega:A4
mega:A5
mega:A6
mega:A7
mega:A8
mega:A9
mega:A10
mega:A11
mega:A12
mega:A13
mega:A14
mega:A15
oled1:GND
oled1:VCC
oled1:SCL
oled1:SDA
ir1:GND
ir1:VCC
ir1:DAT
encoder1:CLK
encoder1:DT
encoder1:SW
encoder1:VCC
encoder1:GND