//<App !Start!>
// FILE: [_controller.ino]
// Created by GUIslice Builder version: [0.15.b004]
//
// GUIslice Builder Generated File
//
// For the latest guides, updates and support view:
// https://github.com/ImpulseAdventure/GUIslice
//
//<App !End!>

// ------------------------------------------------
// Headers to include
// ------------------------------------------------
//<Includes !Start!>
// Varous system includes
#include <Arduino.h>

// Various library includes
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <BBQ10Keyboard.h>
#include <CircularBuffer.h>

// Various local includes
#include "_controller_GSLC.h"

// SD Card stuff
//    The adagfx driver was updated to be able to retun a reference to the sd card
//    This will NOT work w/o the driver tweak
//    The implementation of gslc_GetSDCard only lives in the ada cpp driver so we need to extern it here
#include "src/SdFat/SdFat.h"
extern SdFat SD;

//<Includes !End!>

// ------------------------------------------------
// Program Globals
// ------------------------------------------------

// Battery level measurement
#define VBATPIN A6
float measuredVBat;
int batteryPercent;

// TFT Setup
#define TFT_CS 9
#define TFT_DC 10
Adafruit_ILI9341 tft(TFT_CS, TFT_DC);
#define CHARS_HORIZONTAL 29
#define CHARS_ROWS 11
#define CHARS_TOTAL CHARS_HORIZONTAL * CHARS_ROWS
CircularBuffer<char,CHARS_TOTAL> textBuffer;

// Keyboard
BBQ10Keyboard keyboard;
#define KBD_BTN_1 0x06
#define KBD_BTN_2 0x11
#define KBD_BTN_3 0x07
#define KBD_BTN_4 0x12
#define KBD_SW_UP 0x01
#define KBD_SW_DN 0x02
#define KBD_SW_LF 0x03
#define KBD_SW_RT 0x04
#define KBD_SW_OK 0x05
CircularBuffer<int16_t,8> uiKeyBuffer;

// Upstream keyboard definitions that will be really important later
#define _REG_CFG 2
#define CFG_OVERFLOW_INT (1 << 1)
#define CFG_KEY_INT      (1 << 4)
#define CFG_USE_MODS     (1 << 7) // Should Alt, Sym and Shifts modify the keys reported
#define CFG_REPORT_MODS  (1 << 6) // Should Alt, Sym and Shifts be reported as well

// NeoPixels
#define PIXELS_NUM_BOARD 1
#define PIXELS_NUM_WING 1
#define PIXELS_WING_PIN 11
Adafruit_NeoPixel pixels_board(PIXELS_NUM_BOARD, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixels_wing(PIXELS_NUM_WING, PIXELS_WING_PIN, NEO_GRB + NEO_KHZ800);

// GUI state globals
bool textChanged = false;
bool popupOnScreen = false;

// Various loop handlers
void handlerKeyboard();
void handlerBatteryLevel();
void processRingBuffer();
void sdCardInfo();

// Save some element references for direct access
//<Save_References !Start!>
gslc_tsElemRef* m_pElemBatteryLevel= NULL;
gslc_tsElemRef* m_pElemBtSDNo     = NULL;
gslc_tsElemRef* m_pElemBtSDYes    = NULL;
gslc_tsElemRef* m_pElemRd1152     = NULL;
gslc_tsElemRef* m_pElemRd96       = NULL;
gslc_tsElemRef* m_pElemRdUART0    = NULL;
gslc_tsElemRef* m_pElemRdUART5    = NULL;
gslc_tsElemRef* m_pElemSDInfo     = NULL;
gslc_tsElemRef* m_pElemStatusText = NULL;
gslc_tsElemRef* m_pElemText       = NULL;
//<Save_References !End!>

// Keyboard map related
#define MAX_INPUT_MAP       5
gslc_tsInputMap             m_asInputMap[MAX_INPUT_MAP];

// Define debug message function
static int16_t DebugOut(char ch) { if (ch == (char)'\n') Serial.println(""); else Serial.write(ch); return 0; }

// ------------------------------------------------
// Callback Methods
// ------------------------------------------------
// Common Button callback
bool CbBtnCommon(void* pvGui,void *pvElemRef,gslc_teTouch eTouch,int16_t nX,int16_t nY)
{
  // Typecast the parameters to match the GUI and element types
  gslc_tsGui*     pGui     = (gslc_tsGui*)(pvGui);
  gslc_tsElemRef* pElemRef = (gslc_tsElemRef*)(pvElemRef);
  gslc_tsElem*    pElem    = gslc_GetElemFromRef(pGui,pElemRef);

  if ( eTouch == GSLC_TOUCH_UP_IN ) {
    // From the element's ID we can determine which button was pressed.
    switch (pElem->nId) {
//<Button Enums !Start!>
      case E_ELEM_BTN_SD_FMT:
        gslc_PopupShow(&m_gui, E_PG_SD_CONFIRM, false);
        popupOnScreen = true;
        break;
      case E_ELEM_SD_NO:
        gslc_PopupHide(&m_gui);
        popupOnScreen = false;
        break;
      case E_ELEM_SD_YES:
        gslc_PopupHide(&m_gui);
        popupOnScreen = false;
        break;
//<Button Enums !End!>
      default:
        break;
    }
  }
  return true;
}
//<Checkbox Callback !Start!>
//<Checkbox Callback !End!>
//<Keypad Callback !Start!>
//<Keypad Callback !End!>
//<Spinner Callback !Start!>
//<Spinner Callback !End!>
//<Listbox Callback !Start!>
//<Listbox Callback !End!>
//<Draw Callback !Start!>
//<Draw Callback !End!>
//<Slider Enums !Start!>
//<Slider Enums !End!>
//<Tick Callback !Start!>
//<Tick Callback !End!>

// Keyboard Input polling callback function
bool CbKbdPoll(void* pvGui, int16_t* pnPinInd, int16_t* pnPinVal) {
  // Check for new keyboard events just in case
  handlerKeyboard();

  // If the key buffer is empty no events to process
  if (uiKeyBuffer.isEmpty()) {
    return false;
  }

  // Process a key that's on the key buffer for the UI event(s)
  *pnPinInd = uiKeyBuffer.pop();
  *pnPinVal = 1;

  return true;
}

void setup() {

  Serial.begin(115200);

  // If GUI Slice debug of drivers is turned on, wait for serial so we get proper debug output
  #if defined(DBG_LOG) || defined(DBG_TOUCH) || defined(DBG_FRAME_RATE) || defined(DBG_DRAW_IMM) || defined(DBG_DRIVER)
  while (!Serial) {
    ;
  }
  #endif

  // GUI Slice debugging
  gslc_InitDebug(&DebugOut);

  // Setup red LED to indicate device is on (in case we disable NeoPixel battery level later)
  pinMode(3, OUTPUT);
  digitalWrite(3, HIGH);

  // Setup NeoPixels
  pixels_board.begin();
  pixels_wing.begin();
  pixels_board.clear();
  pixels_wing.clear();
  pixels_board.setBrightness(10);
  pixels_wing.setBrightness(5);

  // Setup neopixels to indicate we are initializing gui slice
  //    This can serve as an indication something has gone wrong like the sd card is missing
  pixels_wing.setPixelColor(0, pixels_board.Color(255, 0, 0));
  pixels_board.setPixelColor(0, pixels_board.Color(255, 0, 0));
  pixels_wing.show();
  pixels_board.show();

  // Setup BBQ10Keyboard
  Wire.begin();
  keyboard.begin();
  keyboard.writeRegister(_REG_CFG, CFG_OVERFLOW_INT | CFG_KEY_INT | CFG_USE_MODS | CFG_REPORT_MODS);
  keyboard.setBacklight(0.5f);

  // Init GUI Slicle
  InitGUIslice_gen();

  // Set the pin poll callback function
  gslc_SetPinPollFunc(&m_gui, CbKbdPoll);

  // Create the GUI input mapping (pin event to GUI action)
  gslc_InitInputMap(&m_gui, m_asInputMap, MAX_INPUT_MAP);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_UP, GSLC_ACTION_FOCUS_NEXT, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_DN,  GSLC_ACTION_FOCUS_PREV, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_LF, GSLC_ACTION_FOCUS_NEXT, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_RT, GSLC_ACTION_FOCUS_PREV, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_OK, GSLC_ACTION_SELECT, 0);

  // Set neopixels to standard start state
  pixels_wing.clear();
  pixels_wing.show();

  pixels_board.setPixelColor(0, pixels_board.Color(0, 0, 255));
  pixels_board.show();
}

// -----------------------------------
// Main event loop
// -----------------------------------
void loop() {
  // Reset text changed state (processing methods will update if needed)
  textChanged = false;

  // Handle keyboard events
  handlerKeyboard();

  // Update battery level as appropriate
  handlerBatteryLevel();

  // Update UI
  if (textChanged) {
    processRingBuffer();
  }
  
  gslc_Update(&m_gui);
}

// Move text from buffer -> text box on screen
void processRingBuffer() {
      // Add keycodes to text box
    char* textForDisplay = (char*) calloc(textBuffer.size(), sizeof(char));
    for (int i=0; i<textBuffer.size(); i++) {
      textForDisplay[i] = textBuffer[i];
    }
    gslc_ElemXTextboxAdd(&m_gui, m_pElemText, textForDisplay);
}

// Keyboard handler
void handlerKeyboard() {
  // Don't do anything if there are no key presses to process
  if (keyboard.keyCount() == 0) {
    return;
  }

  // Check if keys were pressed and drain queue of pressed keys
  while (keyboard.keyCount() > 0) {
    // Get keyboard event
    const BBQ10Keyboard::KeyEvent key = keyboard.keyEvent();

    // Process key press events
    if (key.state == BBQ10Keyboard::StatePress) {
      // Move right through screens
      if (key.key == KBD_BTN_4) {
        if (popupOnScreen) {
          // Do nothing -- need to clear popup first
        }
        // Main Screen Flip
        else if (gslc_GetPageCur(&m_gui) == E_PG_MAIN) {
          gslc_ElemSetTxtStr(&m_gui, m_pElemStatusText, "Raspberry Pi");
          gslc_SetPageCur(&m_gui, E_CONF_RPI);
        }
        // Raspberry Pi Screen Flip
        else if (gslc_GetPageCur(&m_gui) == E_CONF_RPI) {
          sdCardInfo();
          gslc_ElemSetTxtStr(&m_gui, m_pElemStatusText, "SD Card");
          gslc_SetPageCur(&m_gui, E_SD_CARD);
        }
        // SD Card Info screen Flip
        else if (gslc_GetPageCur(&m_gui) == E_SD_CARD) {
          gslc_ElemSetTxtStr(&m_gui, m_pElemStatusText, "Serial Console");
          gslc_SetPageCur(&m_gui, E_PG_MAIN);
        }
      }
      // Add keys to the key buffer that are mapped to UI functions (only for config screens)
      else if (key.key == KBD_SW_UP
              || key.key == KBD_SW_DN
              || key.key == KBD_SW_LF
              || key.key == KBD_SW_RT
              || key.key == KBD_SW_OK) {
        uiKeyBuffer.push(key.key);
      }
      // Process keys 'normally'
      else {
        char output[28];
        snprintf(output, 28, "key: '%c' (dec %d, hex %02x)", key.key, key.key, key.key);
        textChanged = true;
        for (int i=0; i<sizeof(output); i++) {
          char toPush = output[i];
          if (toPush != '\0') {
            textBuffer.push(toPush);
          }
          else if (toPush == '\0') {
            textBuffer.push('\n');
          }
        }
      }
    }

    // Pick color for signaling a key was pressed (short or long press)
    uint32_t keyboard_pixel_color = pixels_wing.Color(0, 0, 255);
    if (key.state == BBQ10Keyboard::StateLongPress) {
      keyboard_pixel_color = pixels_wing.Color(0, 255, 255);
    }

    // Display indicator accordingly
    if (key.state == BBQ10Keyboard::StatePress || key.state == BBQ10Keyboard::StateLongPress) {
      pixels_wing.setPixelColor(0, keyboard_pixel_color);
      pixels_wing.show(); 
    } 
    else {
      pixels_wing.clear();
      pixels_wing.show();
    }
  }
}

// Measure battery level and change NeoPixel accordingly
void handlerBatteryLevel() {
  measuredVBat = analogRead(VBATPIN);
  measuredVBat *= 2;    // we divided by 2, so multiply back
  measuredVBat *= 3.3;  // Multiply by 3.3V, our reference voltage
  measuredVBat /= 1024; // convert to voltage
  batteryPercent = (measuredVBat / 3.3) * 100;

  gslc_tsColor colorForHeaderElements;
  if (batteryPercent >= 75) {
    pixels_board.setPixelColor(0, pixels_board.Color(0, 255, 0));
    colorForHeaderElements = GSLC_COL_GREEN;
  }
  else if (batteryPercent >= 50) {
    colorForHeaderElements = GSLC_COL_YELLOW;
    pixels_board.setPixelColor(0, pixels_board.Color(255, 255, 0));
  }
  else if (batteryPercent >= 25) {
    colorForHeaderElements = GSLC_COL_ORANGE;
    pixels_board.setPixelColor(0, pixels_board.Color(255, 128, 0));
  }
  else {
    colorForHeaderElements = GSLC_COL_RED;
    pixels_board.setPixelColor(0, pixels_board.Color(255, 0, 0));
  }

  gslc_ElemXProgressSetVal(&m_gui, m_pElemBatteryLevel, batteryPercent);
  gslc_tsXProgress*  pGauge  = (gslc_tsXProgress*)gslc_GetXDataFromRef(&m_gui,m_pElemBatteryLevel,GSLC_TYPEX_PROGRESS,__LINE__);
  pGauge->colGauge = colorForHeaderElements;
  gslc_ElemSetTxtCol(&m_gui, m_pElemStatusText, colorForHeaderElements);

  // Update GUI and NeoPixel with battery status information
  pixels_board.show();
}

void sdCardInfo() {
  // Clear text box
  gslc_ElemXTextboxReset(&m_gui, m_pElemSDInfo);

  // Reasonably small buffer for filling text box
  char textForDisplay[64];

  // FAT Type
  snprintf(textForDisplay, 64, "Volume is FAT%d\n", int(SD.vol()->fatType()));
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);

  // Free Space
  snprintf(textForDisplay, 64, "Free Space (MB): N/A\n    (Performance Issues)\n");
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);
  
  // Total size
  float size = 0.000512*SD.card()->cardSize();
  snprintf(textForDisplay, 64, "Total Space (MB): %.2f\n", size);
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);

  // Blocks Per Cluster
  snprintf(textForDisplay, 64, "Blocks Per Cluster: %d\n", int(SD.vol()->blocksPerCluster()));
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);

  // Cluster Count
  snprintf(textForDisplay, 64, "Cluster Count: %d\n", SD.vol()->clusterCount());
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);
}
