// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi

#include "Arduino.h"

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__));
  Serial.print( F("  compiled ") );
  Serial.print(F(__DATE__));
  Serial.print( F(" ") );
  Serial.println(F(__TIME__));
}


boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

unsigned long MyTestTimer =  0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;

unsigned long buttonReleaseTimer;

void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


#include "NewEncoder.h"

const byte EncChA_Pin = 2;
const byte EncChB_Pin = 3;
const int minVal = -20;
const int maxVal =  20;
const int startVal = 2;

// Pins 2 and 3 should work for many processors, including Uno. See README for meaning of constructor arguments.
// Use FULL_PULSE for encoders that produce one complete quadrature pulse per detnet, such as: https://www.adafruit.com/product/377
// Use HALF_PULSE for endoders that produce one complete quadrature pulse for every two detents, such as: https://www.mouser.com/ProductDetail/alps/ec11e15244g1/?qs=YMSFtX0bdJDiV4LBO61anw==&countrycode=US&currencycode=USD

NewEncoder myEncoderObject(EncChA_Pin, EncChB_Pin, minVal, maxVal, startVal, FULL_PULSE);
// myEncState is a variable of type EncoderState
// EncoderState is a structured variable that has two "simple" variables
// .currentValue which is type int16_t
// (16 bit signed integer valuerange -36767 to 36767)
// currentValue counts up / down with each pulse created through rotating the encoder
// and
// .currentClick which is of type "EncoderClick"
// the variable type "EncoderClick" can have just 3 values
// NoClick, DownClick, UpClick where "click" means a "pulse" created through rotating the encoder
NewEncoder::EncoderState myEncState;
//NewEncoder::EncoderState myDummyState;

int16_t currentValue;
int16_t prevEncoderValue;

int16_t tempMin = 10;
int16_t tempMax = 30;
int16_t actualTemperature;
int16_t storedTemperature;

int16_t rpmMin = 40;
int16_t rpmMax = 50;
int16_t actualRpm;
int16_t storedRpm;


const byte EncBtnPin = 4;

const byte TEMPERATURE_MODE = 0;
const byte RPM_MODE = 1;

byte myMode = TEMPERATURE_MODE;
byte myLastMode = myMode;


void setup() {
  pinMode(EncBtnPin, INPUT_PULLUP);

  Serial.begin(115200);
  PrintFileNameDateTime(); 

  Serial.println("Starting");

  if (!myEncoderObject.begin()) {
    Serial.println("Encoder Failed to Start. Check pin assignments and available interrupts. Aborting.");
    while (1) {
      yield();
    }
  } else {
    // store values of currentValue and EncoderClick into variable myEncState
    myEncoderObject.getState(myEncState);
    Serial.print("Encoder Successfully Started at value = ");
    prevEncoderValue = myEncState.currentValue;
    Serial.println(prevEncoderValue);
  }
}

void pushBtnTogglesMode(byte p_IO_Pin, byte &p_mode) {

  static int buttonState;             // the current reading from the input pin
  static int lastButtonState = LOW;   // the previous reading from the input pin

  // the following variables are unsigned longs because the time, measured in
  // milliseconds, will quickly become a bigger number than can be stored in an int.
  static unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
  unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

  // read the state of the switch into a local variable:
  int reading = digitalRead(p_IO_Pin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        if (p_mode == 1) {
          p_mode = 0;
        }
        else {
          p_mode = 1;
        }
      }
    }
  }
  // save the reading. Next time through the loop, it'll be the lastButtonState:
  lastButtonState = reading;  
}

void CheckForModeChange() {
  if (myMode != myLastMode) {
    myLastMode = myMode;
    Serial.print("mode changed new mode ");
    
    if (myMode == TEMPERATURE_MODE) {
      Serial.println("Temperature");      
    }
    if (myMode == RPM_MODE) {
      Serial.println("rpm");      
    }

    if (myMode == RPM_MODE) {
      storedTemperature = actualTemperature; // store actual temperature for later RE-storing
      myEncoderObject.newSettings(rpmMin, rpmMax, 22, myEncState); // setup encoder for rpm-mode
      //myEncoderObject.getAndSet(storedRpm,myDummyState,myEncState);
      actualRpm = myEncState.currentValue;
      dbg("RPM-Mode active", actualRpm);
      Serial.println();
    }

    if (myMode == TEMPERATURE_MODE) {
      storedRpm = actualRpm;                  // store actual RPM for later RE-storing
      actualTemperature = storedTemperature;  // restore temperature as we are changing to temperature-mode
      myEncoderObject.newSettings(tempMin, tempMax, actualTemperature, myEncState); // setup encoder for temperature-mode
      //myEncoderObject.getAndSet(storedTemperature,myDummyState,myEncState);
      actualTemperature = myEncState.currentValue;
      dbg("TemperatureMode active", actualTemperature);
      Serial.println();
    }
  }
}

void PrintEncoderValue() {
  // very important ! this stores actual values into variable myEncState
  if (myEncoderObject.getState(myEncState)) { //<<<== very important
    Serial.print("Encoder: ");
    if (myMode == TEMPERATURE_MODE) {
      Serial.print("Temperature=");      
    }
    if (myMode == RPM_MODE) {
      Serial.print("rpm=");      
    }
    currentValue = myEncState.currentValue;

    // if currentValue has REALLY changed print new currentValue
    if (currentValue != prevEncoderValue) {
      Serial.println(currentValue);
      prevEncoderValue = currentValue;

      // if currentValue stayed the same because the number is at upper/lower limit
      // check if encoder was rotated by using the UpClick / DownClick-values
    } else
      switch (myEncState.currentClick) {
        case NewEncoder::UpClick:
          Serial.println("at upper limit.");
          break;

        case NewEncoder::DownClick:
          Serial.println("at lower limit.");
          break;

        default:
          break;
      }
  }
}  


void loop() {
  pushBtnTogglesMode(EncBtnPin, myMode); // check if button was pressed if Yes change value of variable myMode

  CheckForModeChange();

  PrintEncoderValue();
  
  // update value according to active mode
  if (myMode == TEMPERATURE_MODE) {
    actualTemperature = myEncState.currentValue;
  }
  else {
    actualRpm = myEncState.currentValue;
  }
}