/*
 * Copyright (c) 2019, Erich Styger
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "platform.h"
#if PL_CONFIG_USE_GUI

#include "gui.h"
#include "lv.h"
#include "LittlevGL/lvgl/lvgl.h"
#include "McuRTOS.h"
#include "FreeRTOS.h"
#include "task.h"
#include "oled.h"
#include "McuSSD1306.h"
#include "McuGDisplaySSD1306.h"
#include "McuFontDisplay.h"
#include "McuUtility.h"
#if PL_CONFIG_USE_SHUTDOWN
  #include "shutdown.h"
#endif
#if PL_CONFIG_USE_SHT31
  #include "gui_tempHum.h"
#endif
#if PL_CONFIG_USE_UPS
  #include "gui_ups.h"
#endif
#if PL_CONFIG_USE_GATEWAY
  #include "gui_uart.h"
  #include "RaspyUART.h"
#endif
#include "toaster.h"

static TaskHandle_t GUI_TaskHndl;

/* task notification bits */
#define GUI_SET_ORIENTATION_LANDSCAPE    (1<<0)
#define GUI_SET_ORIENTATION_LANDSCAPE180 (1<<1)
#define GUI_SET_ORIENTATION_PORTRAIT     (1<<2)
#define GUI_SET_ORIENTATION_PORTRAIT180  (1<<3)

#if PL_CONFIG_USE_GUI_KEY_NAV
#define GUI_GROUP_NOF_IN_STACK   4
typedef struct {
  lv_group_t *stack[GUI_GROUP_NOF_IN_STACK]; /* stack of GUI groups */
  uint8_t sp; /* stack pointer, points to next free element */
} GUI_Group_t;
static GUI_Group_t groups;

/* style modification callback for the focus of an element */
static void style_mod_cb(struct _lv_group_t *grp, lv_style_t *style) {
#if LV_COLOR_DEPTH != 1
    /*Make the style to be a little bit orange*/
    style->body.border.opa = LV_OPA_COVER;
    style->body.border.color = LV_COLOR_ORANGE;

    /*If not empty or has border then emphasis the border*/
    if(style->body.empty == 0 || style->body.border.width != 0) style->body.border.width = LV_DPI / 50;

    style->body.main_color = lv_color_mix(style->body.main_color, LV_COLOR_ORANGE, LV_OPA_70);
    style->body.grad_color = lv_color_mix(style->body.grad_color, LV_COLOR_ORANGE, LV_OPA_70);
    style->body.shadow.color = lv_color_mix(style->body.shadow.color, LV_COLOR_ORANGE, LV_OPA_60);

    style->text.color = lv_color_mix(style->text.color, LV_COLOR_ORANGE, LV_OPA_70);
#else
    style->body.border.opa = LV_OPA_COVER;
    style->body.border.color = LV_COLOR_BLACK;
    style->body.border.width = 3;
#endif
}

void GUI_AddObjToGroup(lv_obj_t *obj) {
  lv_group_add_obj(GUI_GroupPeek(), obj);
}

void GUI_RemoveObjFromGroup_(lv_obj_t *obj) {
  lv_group_remove_obj(obj);
}

lv_group_t *GUI_GroupPeek(void) {
  if (groups.sp == 0) {
    return NULL;
  }
  return groups.stack[groups.sp-1];
}

void GUI_GroupPull(void) {
  if (groups.sp == 0) {
    return;
  }
  lv_group_del(groups.stack[groups.sp-1]);
  groups.sp--;
  lv_indev_set_group(LV_GetInputDevice(), groups.stack[groups.sp-1]); /* assign group to input device */
}

void GUI_GroupPush(void) {
  lv_group_t *gui_group;

  if (groups.sp >= GUI_GROUP_NOF_IN_STACK) {
    return;
  }
  gui_group = lv_group_create();
  lv_indev_set_group(LV_GetInputDevice(), gui_group); /* assign group to input device */
  /* change the default focus style which is an orange'ish thing */
  lv_group_set_style_mod_cb(gui_group, style_mod_cb);
  groups.stack[groups.sp] = gui_group;
  groups.sp++;
}
#endif /* PL_CONFIG_USE_GUI_KEY_NAV */

void GUI_ChangeOrientation(McuSSD1306_DisplayOrientation orientation) {
  switch(orientation) {
    case McuSSD1306_ORIENTATION_LANDSCAPE:
      (void)xTaskNotify(GUI_TaskHndl, GUI_SET_ORIENTATION_LANDSCAPE, eSetBits);
      break;
    case McuSSD1306_ORIENTATION_LANDSCAPE180:
      (void)xTaskNotify(GUI_TaskHndl, GUI_SET_ORIENTATION_LANDSCAPE180, eSetBits);
      break;
    case McuSSD1306_ORIENTATION_PORTRAIT:
      (void)xTaskNotify(GUI_TaskHndl, GUI_SET_ORIENTATION_PORTRAIT, eSetBits);
      break;
    case McuSSD1306_ORIENTATION_PORTRAIT180:
      (void)xTaskNotify(GUI_TaskHndl, GUI_SET_ORIENTATION_PORTRAIT180, eSetBits);
      break;
    default:
      break;
  }
}

#if PL_CONFIG_USE_UPS
static void btn_click_ups_action(struct _lv_obj_t *obj, lv_event_t event) {
  if(event == LV_EVENT_CLICKED) {
    GUI_UPS_CreateView();
  }
}
#endif

#if PL_CONFIG_USE_GATEWAY
static void btn_click_uart_action(struct _lv_obj_t *obj, lv_event_t event) {
  if(event == LV_EVENT_CLICKED) {
    GUI_UART_CreateView();
  }
}
#endif

#if PL_CONFIG_USE_SHT31
static void btn_click_sht31_action(struct _lv_obj_t *obj, lv_event_t event) {
  if(event == LV_EVENT_CLICKED) {
    GUI_TEMPHUM_CreateView();
  }
}
#endif

#if PL_CONFIG_USE_SHUTDOWN
static lv_obj_t *mboxShutdown, *mboxShutdownWait;

static void mbox_shutdownWait_apply_action(lv_obj_t *mbox, lv_event_t event) {
  if (event == LV_EVENT_DELETE && mbox==mboxShutdownWait) {
    #if PL_CONFIG_USE_GUI_KEY_NAV
      GUI_GroupPull();
    #endif
      mboxShutdownWait = NULL;
  } else if (event == LV_EVENT_VALUE_CHANGED) {
    const char *txt = lv_mbox_get_active_btn_text(mbox);
    if (McuUtility_strcmp(txt, "\221OK")==0) {
      lv_obj_del(mboxShutdownWait);
      mboxShutdownWait = NULL;
    }
  }
}

static void Btn_shutdown_CreateWaitBox(void) {
  mboxShutdownWait = lv_mbox_create(lv_scr_act(), NULL);
#if PL_CONFIG_USE_POWER_DOWN_RED_LED
  lv_mbox_set_text(mboxShutdownWait, "Shutdown in progress. Wait for RED LED.");  /* Set the text */
#else
  lv_mbox_set_text(mboxShutdownWait, "Shutdown in progress.");  /* Set the text */
#endif
  /*Add OK button*/
  static const char * btns[] ={"\221OK", ""}; /*Button description. '\221' lv_btnm like control char*/
  lv_mbox_add_btns(mboxShutdownWait, btns);
  lv_obj_set_event_cb(mboxShutdownWait, mbox_shutdownWait_apply_action);
  lv_obj_set_width(mboxShutdownWait, 100);
  lv_obj_align(mboxShutdownWait, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); /*Align to center */

#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_GroupPush();
  GUI_AddObjToGroup(mboxShutdownWait);
#endif
  lv_group_focus_obj(mboxShutdownWait);
}

static void mbox_shutdown_apply_action(lv_obj_t *mbox, lv_event_t event) {
  if (event == LV_EVENT_DELETE && mbox==mboxShutdown) {
    #if PL_CONFIG_USE_GUI_KEY_NAV
      GUI_GroupPull();
    #endif
      mboxShutdown = NULL;
  } else if (event == LV_EVENT_VALUE_CHANGED) {
    const char *txt = lv_mbox_get_active_btn_text(mbox);
    if (McuUtility_strcmp(txt, "\221Yes")==0) {
      SHUTDOWN_RequestPowerOff(); /* request shutdown to Linux */
      Btn_shutdown_CreateWaitBox();
    } else if (McuUtility_strcmp(txt, "\221Cancel")==0) {
      lv_obj_del(mboxShutdown);
    }
  }
}

static void btn_click_shutdown_action(struct _lv_obj_t *obj, lv_event_t event) {
  if(event == LV_EVENT_CLICKED) {
    mboxShutdown = lv_mbox_create(lv_scr_act(), NULL);
    lv_mbox_set_text(mboxShutdown, "Shutdown Raspy?");  /*Set the text*/
    /*Add two buttons*/
    static const char * btns[] ={"\221Yes", "\221Cancel", ""}; /*Button description. '\221' lv_btnm like control char*/
    lv_mbox_add_btns(mboxShutdown, btns);
    lv_obj_set_event_cb(mboxShutdown, mbox_shutdown_apply_action);
    lv_obj_set_width(mboxShutdown, 100);
    lv_obj_align(mboxShutdown, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); /*Align to center */

  #if PL_CONFIG_USE_GUI_KEY_NAV
    GUI_GroupPush();
    GUI_AddObjToGroup(mboxShutdown);
  #endif
    lv_group_focus_obj(mboxShutdown);
  }
}
#endif /* PL_CONFIG_USE_SHUTDOWN */

#if PL_CONFIG_USE_POWER_ON
static lv_obj_t *mboxPoweron, *mboxPoweronWait;

static void mbox_poweronWait_apply_action(lv_obj_t *mbox, lv_event_t event) {
  if (event == LV_EVENT_DELETE && mbox==mboxPoweronWait) {
    #if PL_CONFIG_USE_GUI_KEY_NAV
      GUI_GroupPull();
    #endif
      mboxPoweronWait = NULL;
  } else if (event == LV_EVENT_VALUE_CHANGED) {
    const char *txt = lv_mbox_get_active_btn_text(mbox);
    if (McuUtility_strcmp(txt, "\221OK")==0) {
      lv_obj_del(mboxPoweronWait);
    }
  }
}

static void Btn_poweron_CreateWaitBox(void) {
  mboxPoweronWait = lv_mbox_create(lv_scr_act(), NULL);
  lv_mbox_set_text(mboxPoweronWait, "Poweron in progress.");  /* Set the text */
  /*Add OK button*/
  static const char * btns[] ={"\221OK", ""}; /*Button description. '\221' lv_btnm like control char*/
  lv_mbox_add_btns(mboxPoweronWait, btns);
  lv_obj_set_event_cb(mboxPoweronWait, mbox_poweronWait_apply_action);
  lv_obj_set_width(mboxPoweronWait, 100);
  lv_obj_align(mboxPoweronWait, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); /*Align to center */

#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_GroupPush();
  GUI_AddObjToGroup(mboxPoweronWait);
#endif
  lv_group_focus_obj(mboxPoweronWait);
}

static void mbox_poweron_apply_action(lv_obj_t *mbox, lv_event_t event) {
  if (event == LV_EVENT_DELETE && mbox==mboxPoweron) {
    #if PL_CONFIG_USE_GUI_KEY_NAV
      GUI_GroupPull();
    #endif
      mboxPoweron = NULL;
  } else if (event == LV_EVENT_VALUE_CHANGED) {
    const char *txt = lv_mbox_get_active_btn_text(mbox);
    if (McuUtility_strcmp(txt, "\221Yes")==0) {
      SHUTDOWN_RequestPowerOn(); /* request power-on to Linux */
      Btn_poweron_CreateWaitBox();
    } else if (McuUtility_strcmp(txt, "\221Cancel")==0) {
      lv_obj_del(mboxPoweron);
    }
  }
}

static void btn_click_poweron_action(struct _lv_obj_t *obj, lv_event_t event) {
  if(event == LV_EVENT_CLICKED) {
    mboxPoweron = lv_mbox_create(lv_scr_act(), NULL);
    lv_mbox_set_text(mboxPoweron, "Poweron Raspy?");  /*Set the text*/
    /*Add two buttons*/
    static const char * btns[] ={"\221Yes", "\221Cancel", ""}; /*Button description. '\221' lv_btnm like control char*/
    lv_mbox_add_btns(mboxPoweron, btns);
    lv_obj_set_event_cb(mboxPoweron, mbox_poweron_apply_action);
    lv_obj_set_width(mboxPoweron, 100);
    lv_obj_align(mboxPoweron, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); /*Align to center */

#if PL_CONFIG_USE_GUI_KEY_NAV
    GUI_GroupPush();
    GUI_AddObjToGroup(mboxPoweron);
#endif
    lv_group_focus_obj(mboxPoweron);
  }
}

#endif /* #if PL_CONFIG_USE_POWER_ON */

void GUI_MainMenuCreate(void) {
  lv_obj_t *gui_win;
#define AUTO_POS (1) /* automatically place GUI elements */

#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_GroupPush();
#endif
  /* create window */
  gui_win = lv_win_create(lv_scr_act(), NULL);
  lv_win_set_title(gui_win, "Main Menu");
  lv_win_set_btn_size(gui_win, 15);
  lv_win_set_sb_mode(gui_win, LV_SB_MODE_OFF); /* no scroll bar */

  /* Make the window content responsive */
#if AUTO_POS
  lv_win_set_layout(gui_win, LV_LAYOUT_PRETTY); /* this will arrange the buttons */
#endif
#if PL_CONFIG_USE_SHUTDOWN || PL_CONFIG_USE_POWER_ON || PL_CONFIG_USE_SHT31 || PL_CONFIG_USE_UPS || PL_CONFIG_USE_GATEWAY
  lv_obj_t *btn;
  lv_obj_t *label;
#endif

#if PL_CONFIG_USE_SHUTDOWN
  btn = lv_btn_create(gui_win, NULL);
#if !AUTO_POS
  lv_obj_set_size(btn, 45, 20);
  lv_obj_set_pos(btn, 5, 0);
#endif
  lv_obj_set_event_cb(btn, btn_click_shutdown_action);
  lv_btn_set_fit(btn, LV_FIT_TIGHT);

  label = lv_label_create(btn, NULL);
  lv_label_set_text(label, "Shutdown");
#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_AddObjToGroup(btn);
#endif
#endif /* PL_CONFIG_USE_SHUTDOWN */

#if PL_CONFIG_USE_POWER_ON
  btn = lv_btn_create(gui_win, NULL);
  lv_obj_set_event_cb(btn, btn_click_poweron_action);
  lv_btn_set_fit(btn, LV_FIT_TIGHT);

  label = lv_label_create(btn, NULL);
  lv_label_set_text(label, "Poweron");
#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_AddObjToGroup(btn);
#endif
#endif /* PL_CONFIG_USE_SHUTDOWN */

#if PL_CONFIG_USE_SHT31
  btn = lv_btn_create(gui_win, NULL);
#if !AUTO_POS
  lv_obj_set_size(btn, 40, 20);
  lv_obj_set_pos(btn, 60, 0);
#endif
  label = lv_label_create(btn, NULL);
  lv_label_set_text(label, "SHT31");
  //lv_obj_set_free_num(btn, 1);   /*Set a unique number for the button*/
  lv_obj_set_event_cb(btn, btn_click_sht31_action);
  lv_btn_set_fit(btn, LV_FIT_TIGHT);
#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_AddObjToGroup(btn);
#endif
#endif /* PL_CONFIG_USE_SHT31 */

#if PL_CONFIG_USE_UPS
  btn = lv_btn_create(gui_win, NULL);
#if !AUTO_POS
  lv_obj_set_size(btn, 40, 20);
  lv_obj_set_pos(btn, 5, 30);
#endif
  label = lv_label_create(btn, NULL);
  lv_label_set_text(label, "UPS");
  lv_obj_set_event_cb(btn, btn_click_ups_action);
  lv_btn_set_fit(btn, LV_FIT_TIGHT);
#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_AddObjToGroup(btn);
#endif
#endif /* PL_CONFIG_USE_UPS */

#if PL_CONFIG_USE_GATEWAY
  btn = lv_btn_create(gui_win, NULL);
#if !AUTO_POS
  lv_obj_set_size(btn, 60, 20);
  lv_obj_set_pos(btn, 5, 30);
#endif
  label = lv_label_create(btn, NULL);
  lv_label_set_text(label, "UART");
  lv_obj_set_event_cb(btn, btn_click_uart_action);
  lv_btn_set_fit(btn, LV_FIT_TIGHT);
#if PL_CONFIG_USE_GUI_KEY_NAV
  GUI_AddObjToGroup(btn);
#endif
#endif /* PL_CONFIG_USE_GATEWAY */
}


static void GuiTask(void *pv) {
 // uint32_t notifcationValue;
  bool init = true;

  (void)pv; /* not used */
  vTaskDelay(pdMS_TO_TICKS(1000)); /* give hardware time to power up */
  if (init) {
	  McuSSD1306_Init(); /* requires I2C interrupts enabled if using HW I2C! */
	//  McuSSD1306_SetDisplayClockDiv(0xA0); /* to increase refresh rate and reduce acoustic noise with white background */
	  OLED_Init(); /* initializes the needed McuLib components for the OLED */

	  /* quick test if LCD is working properly */
	  McuSSD1306_Clear();
	  McuSSD1306_PrintString(1, 0, (uint8_t*)"hello robot!");
  }
  GUI_MainMenuCreate();
  for(;;) {
#if 0
    (void)xTaskNotifyWait(0UL, GUI_SET_ORIENTATION_LANDSCAPE|GUI_SET_ORIENTATION_LANDSCAPE180|GUI_SET_ORIENTATION_PORTRAIT|GUI_SET_ORIENTATION_PORTRAIT180, &notifcationValue, 0); /* check flags */
    if (notifcationValue!=0) {
      lv_area_t area;

      if (notifcationValue&GUI_SET_ORIENTATION_LANDSCAPE) {
        McuGDisplaySSD1306_SetDisplayOrientation(McuGDisplaySSD1306_ORIENTATION_LANDSCAPE);
        area.x1 = 0;
        area.y1 = 0;
        area.x2 = McuGDisplaySSD1306_GetWidth()-1;
        area.y2 = McuGDisplaySSD1306_GetHeight()-1;
        lv_inv_area(NULL, &area);
        lv_inv_area(NULL, &area);
      } else if (notifcationValue&GUI_SET_ORIENTATION_LANDSCAPE180) {
        McuGDisplaySSD1306_SetDisplayOrientation(McuGDisplaySSD1306_ORIENTATION_LANDSCAPE180);
        area.x1 = 0;
        area.y1 = 0;
        area.x2 = McuGDisplaySSD1306_GetWidth()-1;
        area.y2 = McuGDisplaySSD1306_GetHeight()-1;
        lv_inv_area(NULL, &area);
      } else if (notifcationValue&GUI_SET_ORIENTATION_PORTRAIT) {
        McuGDisplaySSD1306_SetDisplayOrientation(McuGDisplaySSD1306_ORIENTATION_PORTRAIT);
        area.x1 = 0;
        area.y1 = 0;
        area.x2 = McuGDisplaySSD1306_GetWidth()-1;
        area.y2 = McuGDisplaySSD1306_GetHeight()-1;
        lv_inv_area(NULL, &area);
      } else if (notifcationValue&GUI_SET_ORIENTATION_PORTRAIT180) {
        McuGDisplaySSD1306_SetDisplayOrientation(McuGDisplaySSD1306_ORIENTATION_PORTRAIT180);
        area.x1 = 0;
        area.y1 = 0;
        area.x2 = McuGDisplaySSD1306_GetWidth()-1;
        area.y2 = McuGDisplaySSD1306_GetHeight()-1;
        lv_inv_area(NULL, &area);
        lv_obj_invalidate(lv_scr_act());
      }
    }
#endif

//    SEGGER_SYSVIEW_MarkStart(1);
//    TOASTER_DisplayToasters();
//    SEGGER_SYSVIEW_MarkStop(1);

    LV_Task(); /* call this every 1-20 ms */
    vTaskDelay(pdMS_TO_TICKS(20));
  }
}

#define APP_PERIODIC_TIMER_PERIOD_MS   10
static TimerHandle_t timerHndl;

static void vTimerGuiTickCallbackExpired(TimerHandle_t pxTimer) {
  (void)pxTimer; /* not used */
  lv_tick_inc(APP_PERIODIC_TIMER_PERIOD_MS);
}

void GUI_Init(void) {
  LV_Init(); /* initialize GUI library */
  //lv_theme_set_current(lv_theme_night_init(128, NULL));
  //lv_theme_set_current(lv_theme_alien_init(128, NULL));
  //lv_theme_set_current(lv_theme_default_init(128, NULL));
  //lv_theme_set_current(lv_theme_material_init(128, NULL));
  lv_theme_set_current(lv_theme_mono_init(128, NULL));
  //lv_theme_set_current(lv_theme_zen_init(128, NULL));
  //lv_theme_set_current(lv_theme_nemo_init(128, NULL));

  //lv_theme_t *th = lv_theme_get_current();
	/* change default button style */
  // lv_style_btn_rel.body.radius = LV_DPI / 15;
  // lv_style_btn_rel.body.padding.hor = LV_DPI / 8;
  // lv_style_btn_rel.body.padding.ver = LV_DPI / 12;

  if (xTaskCreate(GuiTask, "Gui", 3*1024/sizeof(StackType_t), NULL, tskIDLE_PRIORITY+1, &GUI_TaskHndl) != pdPASS) {
    for(;;){} /* error */
  }
  timerHndl = xTimerCreate(  /* timer to handle periodic things */
        "guiTick", /* name */
        pdMS_TO_TICKS(APP_PERIODIC_TIMER_PERIOD_MS), /* period/time */
        pdTRUE, /* auto reload */
        (void*)0, /* timer ID */
        vTimerGuiTickCallbackExpired); /* callback */
  if (timerHndl==NULL) {
    for(;;); /* failure! */
  }
  if (xTimerStart(timerHndl, 0)!=pdPASS) { /* start the timer */
    for(;;); /* failure!?! */
  }
#if PL_CONFIG_USE_GUI_KEY_NAV
  groups.sp = 0;
#endif
}
#endif /* PL_CONFIG_HAS_GUI */
