// Light Controller Type 1
// ***************************
#include <GEM_u8g2.h>                             // GEM library written by Spirik: https://github.com/Spirik/GEM
#include <KeyDetector.h>                          // Used for detecting button presses

// Define signal identifiers for our outputs
#define ROTARY_CW_SIGNAL_KEY 1
#define ROTARY_CCW_SIGNAL_KEY 2
#define ROTARY_BUTTON_SIGNAL_KEY 3
#define GREEN_BUTTON_SIGNAL_KEY 4
#define RED_BUTTON_SIGNAL_KEY 5

// Button pins
#define GREEN_BUTTON_PIN 7
#define RED_BUTTON_PIN 8

// Pins encoder is connected to
#define ROTARY_CW_PIN 11
#define ROTARY_CCW_PIN 12
#define ROTARY_BUTTON_PIN 10

#define splash_width  12
#define splash_height 12
static const unsigned char splash_bits [] U8X8_PROGMEM = {
  0xc0,0xf7,0x60,0xfe,0x30,0xfe,0x18,0xff,0x8c,0xff,0xc6,0xff,
  0xe3,0xff,0xf1,0xff,0x19,0xff,0x7f,0xfc,0xff,0xfd,0xfe,0xf7
};

// Array of Key objects that will link GEM key identifiers with dedicated pins
// Key keys[] = {{ROTARY_CW_SIGNAL_KEY, ROTARY_CW_PIN}, {ROTARY_BUTTON_SIGNAL_KEY, ROTARY_BUTTON_PIN}};
Key keys[] = {{ROTARY_CW_SIGNAL_KEY, ROTARY_CW_PIN}, {ROTARY_BUTTON_SIGNAL_KEY, ROTARY_BUTTON_PIN}, {GREEN_BUTTON_SIGNAL_KEY, GREEN_BUTTON_PIN}, {RED_BUTTON_SIGNAL_KEY, RED_BUTTON_PIN}};

// Create KeyDetector object
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 5, /* analogThreshold= */ 16, /* pullup= */ true);

bool secondaryPressed = false;  // If encoder rotated while key was being pressed; used to prevent unwanted triggers
bool cancelPressed = false;  // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times
const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms
long keyPressTime = 0; // Variable to hold time of the key press event
long now; // Variable to hold current time taken with millis() function at the beginning of loop()

bool countdownRunning = false;
int countdownRefreshInterval = 1000;                  // Countdown will refresh every n milliseconds
unsigned long countdownPreviousMillis = 0;            // Used to show the countdown timer
int currentCountdownSeconds = 0;

// Create an instance of the U8g2 library.
//U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// ********************************* TIMER CONTROL *********************************
// Create variable that will be editable through option select and create associated option select

//MIN TIME
byte timerMinMinutes = 1;                        // Default to n minutes
//SelectOptionByte selectTimerMinOptions[] = {{"0 mins", 0}, {"5 mins", 5}, {"10 mins", 10}, {"15 mins", 15}, {"30 mins", 30}, {"45 mins", 45}};
//SelectOptionByte selectTimerMinOptions[] = {{"1 min", 1}, {"2 min", 2}, {"3 min", 3},{"4 min", 4}, {"5 min", 5}, {"6 min", 6}, {"7 min", 7}, {"8 min", 8}, {"9 min", 9}, {"10 min", 10},
//{"11 min", 11}, {"12 min", 12}, {"13 min", 13},{"14 min", 14}, {"15 min", 15}, {"16 min", 16}, {"17 min", 17}, {"18 min", 18}, {"19 min", 19}, {"20 min", 20},
//{"21 min", 21}, {"22 min", 22}, {"23 min", 23},{"24 min", 24}, {"25 min", 25}, {"26 min", 26}, {"27 min", 27}, {"28 min", 28}, {"29 min", 29}, {"30 min", 30} };

//SelectOptionByte selectTimerMinOptions[] = {{"1 min", 1}, {"2 min", 2}, {"3 min", 3},{"4 min", 4}, {"5 min", 5}, {"6 min", 6}, {"7 min", 7}, {"8 min", 8}, {"9 min", 9}, {"10 min", 10},
//{"11 min", 11}, {"12 min", 12}, {"13 min", 13},{"14 min", 14}, {"15 min", 15} };

SelectOptionByte selectTimerMinOptions[] = {{"5 secs", 5}, {"8 secs", 8}, {"10 secs", 10}, {"20 secs", 20}, {"30 secs", 30}, {"40 secs", 40}, {"50 secs", 50}, {"1 mins", 60}, {"2 mins", 120}, {"3 mins", 180},{"4 mins", 240}, {"5 mins", 300}, {"8 mins", 480},{"10 mins", 600}, {"15 mins", 900}, {"30 mins", 1800}, {"45 mins", 2700}};


GEMSelect selectTimerMinMinutes(sizeof(selectTimerMinOptions)/sizeof(SelectOptionByte), selectTimerMinOptions);


// Create menu item for option select with callback function
void applyTimerMinMinutes(); // Forward declaration
GEMItem menuItemTimerMinMinutes("Min Time:", timerMinMinutes, selectTimerMinMinutes, applyTimerMinMinutes);

//// MAX TIME

// Create variable that will be editable through option select and create associated option select
byte timerMaxMinutes = 2;                        // Default to n minutes
//SelectOptionByte selectTimerMaxOptions[] = {{"5 mins", 5}, {"10 mins", 10}, {"15 mins", 15}, {"30 mins", 30}, {"45 mins", 45}, {"60 mins", 60}};
//SelectOptionByte selectTimerMaxOptions[] = {{"1 min", 1}, {"2 min", 2}, {"3 min", 3},{"4 min", 4}, {"5 min", 5}, {"6 min", 6}, {"7 min", 7}, {"8 min", 8}, {"9 min", 9}, {"10 min", 10},
//{"11 min", 11}, {"12 min", 12}, {"13 min", 13},{"14 min", 14}, {"15 min", 15} };

SelectOptionByte selectTimerMaxOptions[] = {{"5 secs", 5}, {"8 secs", 8}, {"10 secs", 10}, {"20 secs", 20}, {"30 secs", 30}, {"40 secs", 40}, {"50 secs", 50}, {"1 mins", 60}, {"2 mins", 120}, {"3 mins", 180},{"4 mins", 240}, {"5 mins", 300}, {"8 mins", 480},{"10 mins", 600}, {"15 mins", 900}, {"30 mins", 1800}, {"45 mins", 2700}};

GEMSelect selectTimerMaxMinutes(sizeof(selectTimerMaxOptions)/sizeof(SelectOptionByte), selectTimerMaxOptions);

// Create menu item for option select with callback function
void applyTimerMaxMinutes(); // Forward declaration
GEMItem menuItemTimerMaxMinutes("Max Time:", timerMaxMinutes, selectTimerMaxMinutes, applyTimerMaxMinutes);
// *********************************************************************************

// ********************************* LEVEL CONTROL *********************************
// Create variable that will be editable through option select and create associated option select
byte level = 20;                        // Default to n level
SelectOptionByte selectLevelOptions[] = {{"1", 1}, {"10", 10}, {"20", 20}, {"30", 30}, {"40", 40}, {"50", 50}, {"60", 60}, {"70", 70}, {"80", 80}, {"90", 90}, {"100", 100}};
GEMSelect selectLevel(sizeof(selectLevelOptions)/sizeof(SelectOptionByte), selectLevelOptions);

// Create menu item for option select with callback function
void applyLevel(); // Forward declaration
GEMItem menuItemLevel("Level:", level, selectLevel, applyLevel);

// Create variable that will be editable through option select and create associated option select
byte duration = 5;                        // Default to n duration
SelectOptionByte selectLevelDurationOptions[] = {{"1 sec", 1}, {"2 secs", 2}, {"3 secs", 3}, {"4 secs", 4}, {"5 secs", 5}, {"6 secs", 6}, {"7 secs", 7}, {"8 secs", 8}, {"9 secs", 9}, {"10 secs", 10}};
GEMSelect selectDuration(sizeof(selectLevelDurationOptions)/sizeof(SelectOptionByte), selectLevelDurationOptions);

// Create menu item for option select with callback function
void applyDuration(); // Forward declaration
GEMItem menuItemDuration("Duration:", duration, selectDuration, applyDuration);
// *********************************************************************************

// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level.
// Menu can have multiple menu pages (linked to each other) with multiple menu items each
GEMPage menuPageMain("Main Menu"); // Main page
GEMPage menuPageTimerControl("Timer Control"); // Timer control submenu
GEMPage menuPageLevelControl("Level Control"); // Level control submenu
//GEMPage menuPageStart("Start"); // Start

// Create menu item linked to Timer and Level Control menu pages
GEMItem menuPageMainTimerControl("Timer Control", menuPageTimerControl);
GEMItem menuPageMainLevelControl("Level Control", menuPageLevelControl);
//GEMItem menuPageMainStart("Start", menuPageStart);

void start(); // Forward declaration
GEMItem menuItemStartButton("Start", start);

// Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier
//GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO);
// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary):
GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 18, /* menuValuesLeftOffset= */ 86);

// Apply preset based on Timer variable value
void applyTimerMinMinutes() {
  // Print variable to Serial
   Serial.print(F("Timer Min Minutes option set: "));
   Serial.println(timerMinMinutes);
}

// Apply preset based on Timer variable value
void applyTimerMaxMinutes() {
  // Print variable to Serial
   Serial.print(F("Timer Max Minutes option set: "));
   Serial.println(timerMaxMinutes);
}

// Apply preset based on Timer variable value
void applyLevel() {
  // Print variable to Serial
  // Serial.print(F("Level: "));
  // Serial.println(level);
}

// Apply preset based on Timer variable value
void applyDuration() {
  // Print variable to Serial
  // Serial.print(F("Duration: "));
  // Serial.println(duration);
}

void turnOn(byte level, byte duration) {
  /*Serial.print(F("Turn On...Level: "));
  Serial.print(level);
  Serial.print(F(", Duration: "));
  Serial.println(duration);*/
}

void runTest() {
  // Serial.println(F("Testing..."));
}

void setupMenu() {
  // Add menu items to Main Menu page
  menuPageMain.addMenuItem(menuPageMainTimerControl);
  menuPageMain.addMenuItem(menuPageMainLevelControl);
//  menuPageMain.addMenuItem(menuPageMainStart);
  menuPageMain.addMenuItem(menuItemStartButton);

  // Add menu items to Timer and Level Control menu pages
  menuPageTimerControl.addMenuItem(menuItemTimerMinMinutes);
  menuPageTimerControl.addMenuItem(menuItemTimerMaxMinutes);
  menuPageLevelControl.addMenuItem(menuItemLevel);
  menuPageLevelControl.addMenuItem(menuItemDuration);

  // Specify parent menu page for the Timer and Level Control menu pages
  menuPageTimerControl.setParentMenuPage(menuPageMain);
  menuPageLevelControl.setParentMenuPage(menuPageMain);
//  menuPageStart.setParentMenuPage(menuPageMain);

  // Add menu page to menu and set it as current
  menu.setMenuPageCurrent(menuPageMain);
}

void processMenu() {

  // Get current time to use later on
  now = millis();

  // If menu is ready to accept button press...
  if (menu.readyForKey()) {
    // ...detect key press using KeyDetector library
    // and pass pressed button to menu
    myKeyDetector.detect();
  
    switch (myKeyDetector.trigger) {
      case ROTARY_BUTTON_SIGNAL_KEY:
        // Button was pressed
        // Serial.println(F("Button pressed"));
        // Save current time as a time of the key press event
        keyPressTime = now;
        break;
      case GREEN_BUTTON_SIGNAL_KEY:
        // Green button was pressed
        // Serial.println(F("GREEN Button pressed"));
        break;
      case RED_BUTTON_SIGNAL_KEY:
        // Red button was pressed
        // Serial.println(F("RED Button pressed"));
        break;
    }
    /* Detecting rotation of the encoder on release rather than push
    (i.e. myKeyDetector.triggerRelease rather myKeyDetector.trigger)
    may lead to more stable readings (without excessive signal ripple) */
    switch (myKeyDetector.triggerRelease) {
      case ROTARY_CW_SIGNAL_KEY:
        // Serial.println(F("ROTARY_CW_SIGNAL_KEY"));
        // Signal from Channel A of encoder was detected
        if (digitalRead(ROTARY_CCW_PIN) == LOW) {
          // If channel B is low then the knob was rotated CCW
          if (myKeyDetector.current == ROTARY_BUTTON_SIGNAL_KEY) {
            // If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,...
            // Serial.println(F("Rotation CCW with button pressed (release)"));
            menu.registerKeyPress(GEM_KEY_LEFT);
            // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action
            secondaryPressed = true;
          } else {
            // ...or GEM_KEY_UP otherwise
            // Serial.println(F("Rotation CCW (release)"));
            menu.registerKeyPress(GEM_KEY_UP);
          }
        } else {
          // If channel B is high then the knob was rotated CW
          if (myKeyDetector.current == ROTARY_BUTTON_SIGNAL_KEY) {
            // If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,...
            // Serial.println(F("Rotation CW with button pressed (release)"));
            menu.registerKeyPress(GEM_KEY_RIGHT);
            // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action
            secondaryPressed = true;
          } else {
            // ...or GEM_KEY_DOWN otherwise
            // Serial.println(F("Rotation CW (release)"));
            menu.registerKeyPress(GEM_KEY_DOWN);
          }
        }
        break;
      case ROTARY_BUTTON_SIGNAL_KEY:
        // Button was released
        // Serial.println(F("Button released"));
        if (!secondaryPressed) {
          // If button was not used as a modifier to rotation action...
          if (now <= keyPressTime + keyPressDelay) {
            // ...and if not enough time passed since keyPressTime,
            // treat key that was pressed as Ok button
            menu.registerKeyPress(GEM_KEY_OK);
          }
        }
        secondaryPressed = false;
        cancelPressed = false;
        break;
      case GREEN_BUTTON_SIGNAL_KEY:
        // Button was released
        // Serial.println(F("Green button released"));
        runTest();
        break;
      case RED_BUTTON_SIGNAL_KEY:
        // Button was released
        // Serial.println(F("Red button released"));
        turnOn(level, duration);
        break;
    }
    // After keyPressDelay passed since keyPressTime
    if (now > keyPressTime + keyPressDelay) {
      switch (myKeyDetector.current) {
        case ROTARY_BUTTON_SIGNAL_KEY:
          if (!secondaryPressed && !cancelPressed) {
            // If button was not used as a modifier to rotation action, and Cancel action was not triggered yet
            // Serial.println(F("Button remained pressed"));
            // Treat key that was pressed as Cancel button
            menu.registerKeyPress(GEM_KEY_CANCEL);
            cancelPressed = true;
          }
          break;
      }
    }
  }
  
}

void getRandomMinute() {

  // Make sure the min number is 1 minute and then multiple by 60 for total seconds
  //currentCountdownSeconds = random(max(timerMinMinutes, 1), timerMaxMinutes) * 60;        // This will give us total number of seconds to count down
  currentCountdownSeconds = random(max(timerMinMinutes, 1), timerMaxMinutes) ;        // This will give us total number of seconds to count down

   Serial.print(F(" Random CountdownSeconds "));
   Serial.println(currentCountdownSeconds);
}

void start() {

  menu.context.loop = startLoop;
  menu.context.enter = startEnter;
  menu.context.exit = startExit;
  menu.context.allowExit = false; // Setting to false will require manual exit from the loop
  menu.context.enter();

}

void startLoop() {

  unsigned long now = millis();


  myKeyDetector.detect();
  if (myKeyDetector.trigger == ROTARY_BUTTON_SIGNAL_KEY) {
    menu.context.exit();  
  } else {
    if (now - countdownPreviousMillis >= countdownRefreshInterval) {
      byte actualMinutes = (currentCountdownSeconds / 60) % 60;
      byte actualSeconds = currentCountdownSeconds % 60;

      u8g2.firstPage();
      do {
        u8g2.setFont(u8g2_font_6x12_tr);
        if (currentCountdownSeconds >= 0 && actualSeconds <= 60) {
          // Show the countdown
          u8g2.setCursor(36, 5);
          u8g2.print("COUNTDOWN");
          u8g2.setFont(u8g2_font_profont29_mn);           // Use a larger font for the countdown timer
          // Minutes
          u8g2.setCursor(27, 22);
          if (actualMinutes < 10) {
            u8g2.print("0");
          }
          u8g2.print(actualMinutes);
          u8g2.setCursor(57, 22);
          u8g2.print(":");
          // Seconds
          u8g2.setCursor(71, 22);
          if (actualSeconds < 10) {
            u8g2.print("0");
          }
          u8g2.print(actualSeconds);
        } else if (currentCountdownSeconds < 0 && currentCountdownSeconds >= -2) {
          // Briefly let the user know we are transmitting
          u8g2.setCursor(25, 5);
          u8g2.print("TRANSMITTING...");
          //Serial.println(F("Transmit "));

        } else {
          // Transmit and then restart countdown
          turnOn(level, duration);
          getRandomMinute();                                // Start timer again
          break;
        }
        u8g2.setFont(u8g2_font_6x12_tr);
        u8g2.setCursor(14, 50);
        u8g2.print("Push knob to exit");
      } while (u8g2.nextPage());
      countdownPreviousMillis = now;
      currentCountdownSeconds--;
    }
  }
  
}

void startEnter() {

  // Clear sreen
  u8g2.clear();

  getRandomMinute();

}

// Invoked once when the GEM_KEY_CANCEL key is pressed
void startExit() {
  // Reset variables
//  previousMillis = 0;
//  currentFrame = framesCount;

  // Draw menu back on screen and clear context
  menu.reInit();
  menu.drawMenu();
  menu.clearContext();
}

void setup() {
  // Pin modes
  pinMode(GREEN_BUTTON_PIN, INPUT_PULLUP);
  pinMode(RED_BUTTON_PIN, INPUT_PULLUP);
  pinMode(ROTARY_CW_PIN, INPUT_PULLUP);
  pinMode(ROTARY_CCW_PIN, INPUT_PULLUP);
  pinMode(ROTARY_BUTTON_PIN, INPUT_PULLUP);

  // Serial communication setup
  Serial.begin(115200);

  // U8g2 library init
  u8g2.begin();

  // Load initial menu preset selected through option select
  applyTimerMinMinutes();
  applyTimerMaxMinutes();
  applyLevel();
  applyDuration();
  
  // Menu init, setup and draw
  menu.setSplash(splash_width, splash_height, splash_bits);
  menu.init();
  setupMenu();
  menu.drawMenu();
}

void loop() {

  processMenu();

}