#include <MCUFRIEND_kbv.h>                                              //If you use other display, replace this include.
#include "GUIuser.h"
#include "GUIdriver.h"                                                  //Make use of the driver functions.

const uint8_t*  screenInputParPtr = NULL;
char            numericInput[NUM_IN_LEN_MAX];                           //Input buffer for numerical input.
extern MCUFRIEND_kbv   tft;


/*****************************************************************************************************************************
 * \ingroup API
 * If you don't need numerical input (or want to change it) this is subject to be removed / adapted.
 * If a function uses an input buffer, it must be cleared by this function (otherwise you can remove it from the driver).
 * This function might also be replaced by the \ref input_buffer_set function that writes all zeroes to the input buffer.
 */
void
input_buffer_clear ()
{
  memset (numericInput, 0, sizeof (numericInput));                      //Clear input buffer on new open.
}






/*****************************************************************************************************************************
 * \ingroup API
 * If you don't need numerical input (or want to change it) this is subject to be removed / adapted.
 * If a function uses an input buffer (ex. \ref INPUT_DATA), the data to be displayed can be pre-set by a specific value.
 * This is possible via a function call in code, or directly from the \ref INPUT_DATA screen object (the choice is yours).
 * \param presetData          Text string that must be used as preset value. In this implementation, it can be locked or just preset.
 */
void
input_buffer_copy (char* presetData)
{
  if (presetData == NULL) return;
  uint16_t len = strlen (presetData);
  if (len >= (NUM_IN_LEN_MAX - 1)) len = NUM_IN_LEN_MAX - 1;
  if (len) strncpy (numericInput, presetData, len);
  if (++len >= (NUM_IN_LEN_MAX - 1)) len = NUM_IN_LEN_MAX - 1;
  numericInput[len] = '\0';                                             //Terminate the string by 0x00.
}



/*****************************************************************************************************************************
 * \ingroup API.
 * If you don't need numerical input (or want to change it) this is subject to be removed / adapted.
 * The demo has one input option: numerical input. It's separated from the driver (may not always be required),
 * How is this achieved:
 * - An object \ref INPUT_DATA is added to each screen to describe the input parameter. From this, we take the default text,
 *   the location where the user input is printed (and the touch area is used to wipe that data if a key is pressed).
 *   The \ref INPUT_DATA is required since it must be referenced by all buttons used to affect the data.
 * - Each button related to the input, uses callback function to determine what effect this has on the input data. This can
 *   append something to it, delete something from it, insert or remove the sign,... This function just buffers the FLASH
 *   location pointer of the \ref INPUT_DATA object, so we don't have to search it before we can start the key processing.
 * \param ptr                 Screen object that has the input buffer specific data.
 */
void
input_object_set (const SCR_DATA* ptr)
{
  screenInputParPtr = (uint8_t*) (ptr);
}






/*****************************************************************************************************************************
 * \ingroup API
 * A screen layout is defined by an array of \ref SCR_DATA elements. This function determines the 1st \ref SCR_DATA element of
 * such array and returns the memory address. It also computes the array size (in elements). When you add or remove screen
 * layouts, this function must be modified accordingly (although it's basically part of the driver, but differs per implementation).
 * \code
 *   SCR_NUMBER screenWanted = SCR_HOME;                                //Select any available \ref SCR_DATA.
 *   const SCR_DATA* sd;                                                //Pointer to the 1st element of an array of \ref SCR_DATA objects.
 *   uint8_t  sdCount;                                                  //Counts the amount of \ref SCR_DATA in the Screen Data array.
 *   if (screen_ptr_size_get (screenWanted, &sd, &sdCount) < 0) { error_handler (); }
 * \endcode
 * \param screenWanted        Screen Number (\ref SCR_NUMBER).
 * \param scrDataPtr          A pointer to a pointer that will point to the 1st \ref SCR_DATA object of an entire set of screen data.
 * \param objCountPtr         A pointer to a counter that reflects the amount of \ref SCR_DATA screen objects in the array.
 * \retval                    Error number (0 = no error; positive = warning; negative = error).
 * Note: another option is to put these sizes directly in an array and read the array size and \ref scrDataPtr from a FLASH array.
 */
int8_t
screen_ptr_size_get (SCR_NUMBER       screenWanted,
                     const SCR_DATA** scrDataPtr,
                     uint8_t*         objCountPtr)
{
  if (scrDataPtr == NULL) return (-128);
  if (objCountPtr == NULL) return (-127);
  switch (screenWanted)
  {
  case SCR_HOME:          *scrDataPtr = SCREEN_HOME;
                          *objCountPtr =  sizeof (SCREEN_HOME) / sizeof (SCR_DATA);
                          return (0);
                          break;
  case SCR_SETUP:         *scrDataPtr = SCREEN_SETUP;
                          *objCountPtr = sizeof (SCREEN_SETUP) / sizeof (SCR_DATA);
                          return (0);
                          break;
  case SCR_NUM_IN:        *scrDataPtr = SCREEN_NUM_IN;
                          *objCountPtr = sizeof (SCREEN_NUM_IN) /sizeof (SCR_DATA);
                          return (0);
                          break;
  case SCR_NUM_IN_ROUND:  *scrDataPtr = SCREEN_NUM_IN_ROUND;
                          *objCountPtr = sizeof (SCREEN_NUM_IN_ROUND) / sizeof (SCR_DATA);
                          return (0);
                          break;
  default:                break;
  }
  *scrDataPtr = NULL;
  *objCountPtr = 0;
  return (-126);                                                        //Screen object pointer & size cannot be determined.
}








/*****************************************************************************************************************************
 * \ingroup demo
 * Function to (re)draw the demo home screen.
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
scr_home (__attribute__((unused)) int8_t p)
{
  DEBUGPRINTLLN ((__func__));
  screen_draw (SCR_HOME);
}




/*****************************************************************************************************************************
 * \ingroup demo
 * Function to (re)draw the numeric input screen with rectangular keys & some 3D dept faking.
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
scr_num (__attribute__((unused)) int8_t p)
{
  DEBUGPRINTLLN ((__func__));
  screen_draw (SCR_NUM_IN);
}




/*****************************************************************************************************************************
 * \ingroup demo
 * Function to (re)draw the demo numeric input screen with round keys.
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
scr_num_round (__attribute__((unused)) int8_t p)
{
  DEBUGPRINTLLN ((__func__));
  screen_draw (SCR_NUM_IN_ROUND);
}




/*****************************************************************************************************************************
 * \ingroup demo
 * Function to (re)draw the demo setup screen.
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
scr_setup (__attribute__((unused)) int8_t p)
{
  DEBUGPRINTLLN ((__func__));
  screen_draw (SCR_SETUP);
}



uint8_t cbState;                                                        //Up to 8 checkboxes can be stored in here (this is the memory saving variant).



/*****************************************************************************************************************************
 * \ingroup demo
 * Function to handle pressing checkbox #0.
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
s_home_cb0 (__attribute__((unused)) int8_t p)
{
//static uint8_t cbState;                                               //Each function can have its dedicated cbState, but this requires more RAM.
  cbState ^= CHECKBOX0;
  checkbox_update (&SCREEN_HOME[5], cbState);                           //This checkbox is the 5th element (counting from zero) in the actual SCREEN_HOME array.
}




/*****************************************************************************************************************************
 * \ingroup demo
 * Function to handle pressing checkbox #1.
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
s_home_cb1 (__attribute__((unused)) int8_t p)
{
//static uint8_t cbState;
  cbState ^= CHECKBOX1;                                                 //OK if each EXOR uses other power-of-2 (1, 2, 4, 8, 16, 32, 64, 128).
  checkbox_update (&SCREEN_HOME[6], cbState);
}




/*****************************************************************************************************************************
 * \ingroup demo
 * Function to handle pressing \ref SCR_HOME test button.
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
s_home_test (__attribute__((unused)) int8_t p)
{                                                                       //Example how to preset a horizontal slider bar to a specific value.
//#if !defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328PB__)
  slider_h_fill_pct (&SCREEN_HOME[ 9], 75);                             //SLIDERBAR_H (9th array element).
  slider_h_fill_pct (&SCREEN_HOME[10], -50);                            //SLIDERBAR_H (10th array element).
//#endif
  button_sl_h_pct (&SCREEN_HOME[8], 40);                                //BUTTON_SLV is 8th array element.
}




/*****************************************************************************************************************************
 * \ingroup demo
 * Function to handle pressing numeric key input. There are 2 numeric input demo screens, but both are duplicated with exception:
 *    - object shapes are different.
 *    - \ref INPUT_DATA declaration for rectangular keys has preset "1234" that can be edited; round keys have "F = " preset that is fixed.
 * This function is in this example only called by numeric keys and <DELETE>. The undefined button has in the example NULL but is here
 * foreseen to be added (if we would have any use to it).
 * \param                     p can be percentage (for bar / slider functions),...
 * \retval                    None
 */
void
s_num_in_key (__attribute__((unused)) int8_t p)
{
  DEBUGPRINTLLN ((__func__));
  uint8_t  numKeyStatus = 0;
  uint8_t  fixLen = (uint8_t) (FRwu (screenInputParPtr + OF_OE));       //Length allowed.
  const uint8_t DISPLAY_UPDATE =   0x80;
  const uint8_t AREA_ERASE =       0x40;
  if (screenInputParPtr) switch (p)
  {
  case 1 ... 10:                                                        //Buttons '0' ... '9'.
                  {
                    int8_t l = strlen (numericInput);
                    if (l < (NUM_IN_LEN_MAX - 1))
                    {
                      numericInput[l] = '/' + touch_index_get ();       //Make '0' to '9'.
                      numericInput[l + 1] = '\0';
                      numKeyStatus |= DISPLAY_UPDATE;
                    }
                  }
                  break;
  case 11:        {                                                     //Button Delete.
                    int8_t l = strlen (numericInput);
                    if (l > fixLen)
                    {
                      numericInput[l - 1] = '\0';
                      numKeyStatus |= (  DISPLAY_UPDATE
                                       | AREA_ERASE);
                    }
                  }
                  break;
  case 12:        //TBD.                                                //Button Undefined.
                  break;
  case 13:        {                                                     //Button +/-.
                    uint8_t l = strlen (numericInput);
                    char* rp = &numericInput[fixLen];
                    if (l == fixLen)
                    {
                      numericInput[fixLen] = '-';                       //Leading '-' but no other data in buffer..
                      numericInput[fixLen + 1] = '\0';
                    }
                    else if (numericInput[fixLen] == '-') { do { *rp = *(rp + 1); } while (*++rp); }//Remove leading '-'.
                    else if (   (l < (NUM_IN_LEN_MAX - 1))              //Insert leading '-'.
                             && (l >= fixLen))
                    {
                      rp += l - fixLen;
                      *(rp + 1) = '\0';
                      do { *rp = *(rp - 1); --rp; } while (l-- > fixLen);
                      numericInput[fixLen] = '-';
                    }
                    numKeyStatus |= (  DISPLAY_UPDATE
                                     | AREA_ERASE);
                  }
                  break;
  default:        break;
  }
  if (   (numKeyStatus & DISPLAY_UPDATE)
      && (screenInputParPtr))
  {
    numKeyStatus &= ~DISPLAY_UPDATE;
    if (numKeyStatus & AREA_ERASE) tft.fillRect (FRwu (screenInputParPtr + OF_OX0), FRwu (screenInputParPtr + OF_OY0), FRwu (screenInputParPtr + OF_OW), FRwu (screenInputParPtr + OF_OH), background_color_get ());
    tft.setCursor (FRwu (screenInputParPtr + OF_LX0), FRwu (screenInputParPtr + OF_LY0));
    tft.setTextSize (FRwu (screenInputParPtr + OF_FS));
    tft.setTextColor (FRwu (screenInputParPtr + OF_LC));
    tft.print (numericInput);
  }
}







/*****************************************************************************************************************************
 * \ingroup demo
 * This function is called when <Enter> is pressed on the numerical keypads. There is nothing real behind it, as example we compute
 * the text value as a numerical value and print it to the console.
 * \param                     p can be percentage (for bar / slider functions),...
 */
void
s_num_in_cr (__attribute__((unused)) int8_t p)
{
  DEBUGPRINTLLN ((__func__));
//To be determined what happens when you press <Enter> in this screen.
  uint8_t firstPos = FRwu (screenInputParPtr + OF_OE);//Read the lock position of the \ref INPUT_DATA object.
  int32_t myInput = atoi (&numericInput[firstPos]);
  DEBUGPRINT (("Input: "));
  DEBUGPRINT ((myInput));                             //Print the input as numerical value.
  DEBUGPRINT ((" = "));
  DEBUGPRINTN ((myInput, HEX));                       //Print the input as numerical value (hexadecimal).
}




/*****************************************************************************************************************************
 * \ingroup demo
 * If you keep track what objects are where on the screen, you can also simply print other text just anywhere on the screen
 * not being used by the GUI. This function just prints a 8bit signed percentage value to a specific spot on the screen
 * (in this case the touchIndex determines what object was hit and a correlating screen position is hardcoded).
 * \param                     p can be percentage (for bar / slider functions).
 */
void
s_print (int8_t pct)
{
  DEBUGPRINTLLN ((__func__));
  tft.setTextSize (1);
  tft.setTextColor (WHITE);
  switch (touch_index_get ())
  {
  case 8:       tft.fillRect (0, 0, 50, 25, background_color_get ());
                tft.setCursor (5, 20);
                break;
  case 9:       tft.fillRect (20, 200, 50, 30, background_color_get ());
                tft.setCursor (21, 225);
                break;
  case 10:      tft.fillRect (20, 230, 50, 30, background_color_get ());
                tft.setCursor (21, 245);
                break;
  case 11:      tft.fillRect (400, 10, 30, 25, background_color_get ());
                tft.setCursor (401, 25);
                break;
  case 12:      tft.fillRect (400, 35, 50, 25, background_color_get ());
                tft.setCursor (401, 50);
                break;
  default:      return;
                break;
  }
  tft.print (pct);
}







/*****************************************************************************************************************************
 * \ingroup demo
 * Example how you might use a LABEL to display a random value. In this case, LABEL on touchIndex 6, 7 and 8 are used
 * to display values from objects on touchIndex 2, 4 and 5. The touch area declared for the labels is cleared and the object
 * area is used to write the percentage. Since we know the text is drawn from the bottom up relative to the cursor, the
 * label text Y-position is only 1 pixel above the bottom of the touch zone. Similar, the text X position is to the right of
 * the cursor, so the cursor position taken from the \ref SCR_DATA object is set just to the right of the touch zone.
 * \param pct                      Percentage, provided by the \ref touch_scan function.
 */
void
s_setup_prscr (int8_t pct)                                              //Buffering is not perfect: if we switch screen and go back,
{                                                                       //this function is unaware of possible changes.
  static int8_t val[8];                                                 //Storage for each touchindex in use (2, 4 and 5).
  uint8_t* p = (uint8_t*) (&SCREEN_SETUP[0]) + (sizeof (SCR_DATA) * 6); //touchIndex 6 as output for #2.
  int8_t touchIndex = touch_index_get ();
  if (touchIndex == 5) p += sizeof (SCR_DATA);                          //touchIndex 8 as output for #5.
  if (touchIndex >= 4) p += sizeof (SCR_DATA);                          //touchIndex 7 as output for #4.
  if (val[touchIndex] == pct) return;
  val[touchIndex] = pct;
  tft.fillRect (FRwu (p + OF_TX0), FRwu (p + OF_TY0), FRwu (p+ OF_OW), FRwu (p + OF_OH), background_color_get ());
  tft.setCursor (FRwu (p + OF_OX0), FRwu (p + OF_OY0));
  tft.setTextSize (FRwu (p + OF_FS));
  tft.setTextColor (FRwu (p + OF_LC));
  tft.print (pct);
}






/*****************************************************************************************************************************
 * \ingroup demo
 * Example how you can force a slider in a specific position.
 * This is the response to the Round test key in the demo setup screen. Pressing this button will increase the slider position
 * from zero to 90 (regardless the actual position) and than back from zero.
 * \param pct                      A button returns touchIndex; not used.
 */
void
s_setup_tst (__attribute__((unused)) int8_t i)
{
  static int8_t myPct = 0;
  button_sl_v_pct (&SCREEN_SETUP[2], myPct);                            //BUTTON_SLV is 2nd array element.
  s_setup_prscr (myPct);
  myPct += 10;
  if (myPct > 90) myPct = 0;
}



