// vi:ts=4
// ----------------------------------------------------------------------------
// HelloWorld - simple demonstration of lcd
// Created by Bill Perry 2016-07-02
// [email protected]
//
// This example code is unlicensed and is released into the public domain
// ----------------------------------------------------------------------------
//
// This sketch is for LCDs with PCF8574 or MCP23008 chip based backpacks
// WARNING:
//	Use caution when using 3v only processors like arm and ESP8266 processors
//	when interfacing with 5v modules as not doing proper level shifting or
//	incorrectly hooking things up can damage the processor.
//
// Sketch prints "Hello, World!" on the lcd
//
// If initialization of the LCD fails and the arduino supports a built in LED,
// the sketch will simply blink the built in LED.
//
// NOTE:
//	If the sketch fails to produce the expected results, or blinks the LED,
//	run the included I2CexpDiag sketch to test the i2c signals and the LCD.
//
// SEE ALSO:
//    https://github.com/duinoWitchery/hd44780
//    https://github.com/duinoWitchery/hd44780/blob/master/examples/ioClass/hd44780_I2Cexp/HelloWorld/HelloWorld.ino
//    Wokwi simulation https://wokwi.com/projects/360021172047397889
//      (sim adapted from https://wokwi.com/projects/345429854144954963
//    https://github.com/duinoWitchery/hd44780/issues/35
//
// ----------------------------------------------------------------------------
// LiquidCrystal compability:
// Since hd44780 is LiquidCrystal API compatible, most existing LiquidCrystal
// sketches should work with hd44780 hd44780_I2Cexp i/o class once the
// includes are changed to use hd44780 and the lcd object constructor is
// changed to use the hd44780_I2Cexp i/o class.

// Data model -- A parameter table holding names and parameters for a PID

// Mypin-ish TA-4
// per https://blog.uvm.edu/cwcallah/files/2016/04/Mypin-TA4-manual1.pdf

typedef struct  {
  const  int8_t id;
  const int8_t next; // next SET (-means SET->abs(next) long set goes+1)
  const char * prefix;
  const char * name;
  const char * units;
  float val1;
  float val2;
  float *ref;
} TcStates;

 TcStates tcStates[] = {
  {0, 1, "Str", "Four button Menu", "", 0, 0, NULL},
  {1, 2, "Unt", "Units ", "", 0, 0, NULL},
  {2, 3, "InL", "Input Limits", "", 0, 0, NULL},
  {3, -1, "M/S", "Measure+SetPoint", "", 0, 0, NULL},
  {4, 5, "LSP", "Lower SP", "", 0, 0, NULL},
  {5, 6, "USP", "Upper SP", "", 0, 0, NULL},
  {6, 7, "Hs1", "Hys 1", "", 0, 0, NULL},
  {7, 8, "Hy2", "Hys 2", "", 0, 0, NULL},
  {8, 9, "Dec", "Decimal pts", "", 0, 0, NULL},
  {9, 10, "Sec", "Security", "", 0, 0, NULL},
  {10, 11, "AL1", "AL1 Set", "", 0, 0, NULL},
  {11, 12, "Am1", "AL1 Mode", "", 0, 0, NULL},
  {12, 13, "AL2", "AL2 Set", "", 0, 0, NULL},
  {13, 14, "Am2", "AL2 Mode", "", 0, 0, NULL},
  {14, 15, "PvF", "PvF Offset", "", 0, 0, NULL}, // -100-100
  {15, 16, "InT", "Input Type", "", 0, 0, NULL},
  {16, 17, "P", "Prop Range ", "", 0, 0, NULL}, // Off 0.1-1600
  {17, 18, "I", "T_i", "", 0, 0, NULL},
  {18, 19, "D", "T_d", "", 0, 0, NULL},
  {19, 20, "Dir", "Out Dir", "", 0, 0, NULL}, // Heat/Cool
  {20, 21, "Hys", "Hysteresis", "", 0, 0, NULL}, // Deadband
  {21, 22, "Ctl", "Ctl", "", 0, 0, NULL}, // Seconds 1-150
  {22, 23, "Pc", "Prop Cooling", "", 0, 0, NULL},
  {23, 24, "Cdb", "Cooling Deadband", "", 0, 0, NULL},
  {24, 25, "C-F", "C-F", "", 0, 0, NULL},
  {25, 3, "Lck", "Lock", "", 0, 0, NULL},
};
const size_t ntcStates = sizeof(tcStates) / sizeof(tcStates[0]);

float tcVals [ntcStates];  // ram parameters

// required libraries

#include <Wire.h>  // https://www.arduino.cc/reference/en/language/functions/communication/wire/
#include <hd44780.h> // https://github.com/duinoWitchery/hd44780
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
#include <AceButton.h> // https://github.com/bxparks/AceButton
#include <Streaming.h> // 
// inputs
ace_button::AceButton bUp, bDown, bShift, bSet;
hd44780_I2Cexp lcd; // declare lcd object: auto locate & auto config expander chip
constexpr byte bUp_pin = 9, bDown_pin = 10, bShift_pin = 11, bSet_pin = 12;

// Forward reference to prevent Arduino compiler becoming confused.
void handleEvent(ace_button::AceButton*, uint8_t, uint8_t);

// Outputs
// LCD geometry
const int LCD_COLS = 20;
const int LCD_ROWS = 4;

// Modes of operation

int8_t modifyMode = -1; // modifying the digits of a number
int8_t parameterMode = 0, parameter = 0, modeManualAutoTune;
float floatval = 0;
float increment = 0.01;
int intval = 0;

void setup()
{
  int status;

  status = lcd.begin(LCD_COLS, LCD_ROWS);
  if (status) // non zero status means it was unsuccesful
  {
    // hd44780 has a fatalError() routine that blinks an led if possible
    // begin() failed so blink error code using the onboard LED if possible
    hd44780::fatalError(status); // does not return
  }
  // Print a message to the LCD
  lcd.print("Hello, World!");
  Serial.begin(115200);
  pinMode(bUp_pin, INPUT_PULLUP);
  bUp.init(bUp_pin);
  pinMode(bDown_pin, INPUT_PULLUP);
  bDown.init(bDown_pin);
  pinMode(bShift_pin, INPUT_PULLUP);
  bShift.init(bShift_pin);
  pinMode(bSet_pin, INPUT_PULLUP);
  bSet.init(bSet_pin);
  bSet.setEventHandler(handleEvent);

  printGraphviz(); // 
}

char lines[4][21]; // LCD buffer
char dtostrbuff[10]; // for floats
static int state = 0; // which parameter
static  char *l0 = "", *l1 = ""; // lones for updating buffer

void loop() {
  checkButtons();
  checkState();
  updateScreen(l0, l1);
}

void checkButtons() {
  static uint16_t prev = millis();

  // The (uint16_t) cast is required on 32-bit processors, harmless on 8-bit.
  uint16_t now = millis();
  if ((uint16_t) (now - prev) >= 5) {
    bDown.check();
    bUp.check();
    bShift.check();
    bSet.check();
    prev = now;
  }
}

void checkState(void) {
  static unsigned long last = -100;
  if (millis() - last > 100) {
    last = millis();
    if (state >= ntcStates - 1) state = 0;
    strncpy(lines[0], tcStates[state].prefix, 4);
    // tcStates[state].val1 += (random(100) - 50) / 100.0;
    dtostrf(tcStates[state].val1, 6, 2, dtostrbuff);
    snprintf(lines[0], 20, "%3s %7s", tcStates[state].prefix, dtostrbuff);
    l0 = lines[0];
    l1 = tcStates[state].name;
    //Serial.print('.');
    // Serial.println(l0);
    //Serial.println(l1);


    //++state;
  }
}

void update_l0(void) {
  dtostrf(tcStates[state].val1, 6, 2, dtostrbuff);
  snprintf(lines[0], 20, "%3s %7s       ", tcStates[state].prefix, dtostrbuff);
  lcd.setCursor(0, 0);
  lcd.print(lines[0]);
}
void update_l1(void) {
  snprintf(lines[1], 20, "%-20s", tcStates[state].name, dtostrbuff);
  lcd.setCursor(0, 1);
  lcd.print(lines[1]);
}
void update_l2(void) {
  snprintf(lines[2], 20, "%-20s", tcStates[state].name, dtostrbuff);
  lcd.setCursor(0, 2);
  lcd.print(lines[2]);
}

void update_l3(void) {
  char buff2[10];
  dtostrf(tcStates[state].val1, 6, 2, dtostrbuff);
  dtostrf(increment, 6, 2, buff2);
  snprintf(lines[3], 20, "%s:%s +-%s", tcStates[state].prefix,  dtostrbuff, buff2);

  lcd.setCursor(0, 3);
  lcd.print(lines[3]);
}


void updateScreen(const char * line0, const char* line1) {
  static const char* l0 = "";
  static const char* l1 = "";

  if (line0 != l0 || line1 != l1) {
    // lcd.clear();
    // lcd.setCursor(0, 0);
    // lcd.print(line0);
    // lcd.setCursor(0, 1);
    // lcd.print(line1);
    l0 = line0;
    l1 = line1;

  }
}

void handleEventX(ace_button::AceButton* /* button */, uint8_t eventType,
                  uint8_t /* buttonState */) {
  switch (eventType) {
    case ace_button::AceButton::kEventPressed:
      digitalWrite(LED_BUILTIN, HIGH);
      break;
    case ace_button::AceButton::kEventReleased:
      digitalWrite(LED_BUILTIN, LOW);
      if (++state >= ntcStates) state = 0;
      break;
  }
}

// The event handler for both buttons.
void handleEvent(ace_button::AceButton* button, uint8_t eventType, uint8_t buttonState) {

  // Print out a message for all events, for both buttons.
  Serial.print(F("handleEvent(): pin: "));
  Serial.print(button->getPin());
  Serial.print(F("; eventType: "));
  Serial.print(eventType);
  Serial.print(F("; buttonState: "));
  Serial.println(buttonState);

  // Control the LED only for the Pressed and Released events of Button 1.
  // Notice that if the MCU is rebooted while the button is pressed down, no
  // event is triggered and the LED remains off.
  switch (eventType) {
    case ace_button::AceButton::kEventPressed:
      if (button->getPin() == bSet_pin) {
        digitalWrite(LED_BUILTIN, HIGH);
      }
      break;
    case ace_button::AceButton::kEventReleased:
      if (button->getPin() == bSet_pin) {
        digitalWrite(LED_BUILTIN, LOW);
        if (++state >= ntcStates) state = 0;
        update_l3();
        update_l2();
      } else if (button->getPin() == bUp_pin) {
        tcStates[state].val1 += increment;
        Serial.println(F("Up!"));
        update_l3();


      } else if (button->getPin() == bDown_pin) {
        if(button)
        tcStates[state].val1 -= increment;
        Serial.println(F("Down clicked!"));
        update_l3();

      } else if (button->getPin() == bShift_pin) {
        Serial.println(F("Shift clicked!"));
        increment *= 10;
        if (increment > 100) increment = 0.01;
        Serial.println(increment);
        update_l3();
      }
      break;
    case ace_button::AceButton::kEventClicked:
      if (button->getPin() == bShift_pin) {
        Serial.println(F("Shift clicked!"));
      } else if (button->getPin() == bDown_pin) {
        Serial.println(F("Down clicked!"));
      } else if (button->getPin() == bUp_pin) {
        Serial.println(F("Up clicked!"));
      }
      break;
  }
}


void printGraphviz() {
  Serial.println("//// https://dreampuf.github.io/GraphvizOnline/");
  Serial << "// for " << __FILE__ << "\n//" << __DATE__ " " __TIME__ <<'\n';
  Serial.println("digraph G {\n PowerOn ->");

  for (auto &x : tcStates) {
    int next = x.next;
    int n = &x - tcStates; // index
    Serial << "##n:" << n << " next:" << next <<"\n";
    Serial.print('"');
    Serial.print(x.name);
    Serial.print('"');
    if (next < 0){ // long press to next menu head
      Serial.print(" -> ");
      Serial.print('"');
      Serial.print(tcStates[n+1].name);
      Serial.print('"');
      Serial.println("[label = \"long press\"];");
      next = -next;
          Serial.print('"');
    Serial.print(x.name);
    Serial.print('"');
    }
    if (next >= 0) {
   // Serial << "##n:" << n << " next:" << next <<"\n";

      Serial.print(" -> ");
      Serial.print('"');
      Serial.print(tcStates[next].name);
      Serial.print('"');
      Serial.println(";");
    }
  }

  Serial.println(" }");
}