#include <lvgl.h>
#include "applicationInternal/hardware/hardwarePresenter.h"
#include "applicationInternal/memoryUsage.h"
#include "applicationInternal/gui/guiMemoryOptimizer.h"
// for changing to scene Selection gui
#include "applicationInternal/commandHandler.h"
#include "scenes/scene__default.h"

lv_color_t color_primary = lv_color_hex(0x303030); // gray
lv_obj_t* MemoryUsageLabel = NULL;
lv_obj_t* WifiLabel = NULL;
lv_obj_t* BluetoothLabel = NULL;
lv_obj_t* BattPercentageLabel = NULL;
lv_obj_t* BattIconLabel = NULL;
lv_obj_t* SceneLabel = NULL;
uint32_t currentTabID = -1; // id of the current tab
uint32_t oldTabID = -1;

lv_obj_t* tabview = NULL;
// page indicator
lv_obj_t* panel = NULL;
lv_obj_t* img1 = NULL;
lv_obj_t* img2 = NULL;

static bool global_styles_already_initialized = false;
lv_style_t panel_style;
#ifdef drawRedBorderAroundMainWidgets
lv_style_t style_red_border;
#endif

void guis_doTabCreationAtStartup();
void guis_doAfterSliding(int oldTabID, int newTabID, bool newGuiList);

// Helper Functions -----------------------------------------------------------------------------------------------------------------------

// callback when sceneLabel or pageIndicator was clicked
void sceneLabel_or_pageIndicator_event_cb(lv_event_t* e) {
  Serial.println("- Scene selection: sceneLabel or pageIndicator clicked received for navigating to scene selection page");
  executeCommand(SCENE_SELECTION);
}

// callback for swipe down event to navigate to the scene selection page
void screen_gesture_event_cb(lv_event_t* e) {
  lv_obj_t* screen = lv_event_get_current_target(e);
  lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
  if (dir == LV_DIR_BOTTOM) {
    Serial.println("- Scene selection: swipe down received for navigating to scene selection page");
    executeCommand(SCENE_SELECTION);
  }    
}

// Set the page indicator (panel) scroll position relative to the tabview content scroll position
// this is a callback if the CONTENT of the tabview is scrolled (LV_EVENT_SCROLL)
void tabview_content_is_scrolling_event_cb(lv_event_t* e){
  if (panel == NULL) { return;}

  float bias = float(150.0 + 8.0) / SCR_WIDTH;
  //           screenwidth    indicator    ??    2 small hidden buttons  ??
  int offset = SCR_WIDTH / 2  - 150 / 2    - 8 - 2*50 / 2                -4;
  // get the object to which the event is sent
  lv_obj_t* tabviewContent = lv_event_get_target(e);
  
  // Get the x coordinate of object and scroll panel accordingly
  int16_t tabviewX = lv_obj_get_scroll_x(tabviewContent);
  // the last events we receive are 0, 238 and 476. But should be 0, 240 and 480. Otherwise page indicator jumps a litte bit after recreation of tabs.
  if ((tabviewX >= 237) && (tabviewX <= 240)) {tabviewX = 240;}
  if ((tabviewX >= 475) && (tabviewX <= 480)) {tabviewX = 480;}
  // we need 158 more (the size of one page indicator), because we always have one more page indicator at the beginning and at the end (so normally 5 when having 3 tabs)
  int16_t panelX = tabviewX * bias - offset + 158;
  // Serial.printf("scroll %d to %d\r\n", tabviewX, panelX);
  lv_obj_scroll_to_x(panel, panelX, LV_ANIM_OFF);
  // lv_obj_scroll_to_x(panel, lv_obj_get_scroll_x(tabviewContent) * bias - offset, LV_ANIM_OFF);
}

// -----------------------
static bool waitBeforeActionAfterSlidingAnimationEnded = false;
static unsigned long waitBeforeActionAfterSlidingAnimationEnded_timerStart;
// This is the callback when the animation of the tab sliding ended
static void tabview_animation_ready_cb(lv_anim_t* a) {
  // Unfortunately, the animation has not completely ended here. We cannot do the recreation of the tabs here.
  // We have to wait some more milliseconds or at least one cycle of lv_timer_handler();
  // calling lv_timer_handler(); here does not help
  // lv_timer_handler();
  // guis_doAfterSliding(oldTabID, currentTabID, false);

  waitBeforeActionAfterSlidingAnimationEnded = true;
  waitBeforeActionAfterSlidingAnimationEnded_timerStart = millis();
}

// Update currentTabID when a new tab is selected
// this is a callback if the tabview is changed (LV_EVENT_VALUE_CHANGED)
// Sent when a new tab is selected by sliding or clicking the tab button. lv_tabview_get_tab_act(tabview) returns the zero based index of the current tab.
void tabview_tab_changed_event_cb(lv_event_t* e) {
  if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
    
    oldTabID = currentTabID;
    currentTabID = lv_tabview_get_tab_act(lv_event_get_target(e));

    // Wait until the animation ended, then call "guis_doAfterSliding(oldTabID, currentTabID, false);"
    // https://forum.lvgl.io/t/delete-a-tab-after-the-tabview-scroll-animation-is-complete/3155/4
    lv_obj_t* myTabview = lv_event_get_target(e);
    lv_obj_t* tabContainer = lv_tabview_get_content(myTabview);
    // https://docs.lvgl.io/8.3/overview/animation.html?highlight=lv_anim_get#_CPPv411lv_anim_getPv18lv_anim_exec_xcb_t
    // (lv_anim_exec_xcb_t) lv_obj_set_x does not find an animation. NULL is good as well.
    lv_anim_t* anim = lv_anim_get(tabContainer, NULL); // (lv_anim_exec_xcb_t) lv_obj_set_x);
    if(anim) {
      // Swipe is not yet complete. User released the touch screen, an animation will bring it to the end.
      // That's the normal (and only?) case for the tft touchscreen
      lv_anim_set_ready_cb(anim, tabview_animation_ready_cb);
    } else {
      // Swipe is complete, no additional animation is needed. Most likely only possible in simulator
      Serial.println("Change of tab detected, without animation at the end. Will directly do my job after sliding.");
      guis_doAfterSliding(oldTabID, currentTabID, false);
    }
  }
}

void tabview_tab_changed_manually(uint32_t current_tabID) {
  oldTabID = currentTabID;
  currentTabID = current_tabID;

  // Wait until the animation ended, then call "guis_doAfterSliding(oldTabID, currentTabID, false);"
  // https://forum.lvgl.io/t/delete-a-tab-after-the-tabview-scroll-animation-is-complete/3155/4
  lv_obj_t* tabContainer = lv_tabview_get_content(tabview);
  // https://docs.lvgl.io/8.3/overview/animation.html?highlight=lv_anim_get#_CPPv411lv_anim_getPv18lv_anim_exec_xcb_t
  // (lv_anim_exec_xcb_t) lv_obj_set_x does not find an animation. NULL is good as well.
  lv_anim_t* anim = lv_anim_get(tabContainer, NULL); // (lv_anim_exec_xcb_t) lv_obj_set_x);
  if(anim) {
    // Swipe is not yet complete. User released the touch screen, an animation will bring it to the end.
    // That's the normal (and only?) case for the tft touchscreen
    lv_anim_set_ready_cb(anim, tabview_animation_ready_cb);
  } else {
    // Swipe is complete, no additional animation is needed. Most likely only possible in simulator
    Serial.println("Change of tab detected, without animation at the end. Will directly do my job after sliding.");
    guis_doAfterSliding(oldTabID, currentTabID, false);
  }
}

void setMainWidgetsHeightAndPosition();
void init_gui_status_bar();
void init_gui_memoryUsage_bar();
void init_gui(void) {

  // Setup LVGL ---------------------------------------------------------------------------------------------
  lv_init();

  init_lvgl_hardware();

  // -- draw red border around the main widgets -------------------------------------------------------------
  if (!global_styles_already_initialized) {
    // Style the panel background. Will be initialized only once and reused in fillPanelWithPageIndicator_strategyMax3
    lv_style_init(&panel_style);
    lv_style_set_pad_all(&panel_style, 3);
    lv_style_set_border_width(&panel_style, 0);
    lv_style_set_bg_opa(&panel_style, LV_OPA_TRANSP);
    #ifdef drawRedBorderAroundMainWidgets
    lv_style_init(&style_red_border);
    lv_style_set_border_side(&style_red_border, LV_BORDER_SIDE_FULL);
    lv_style_set_border_color(&style_red_border, lv_color_hex(0xff0000));
    lv_style_set_border_width(&style_red_border, 1);
    global_styles_already_initialized = true;
    #endif
  }
  // Set the background color -------------------------------------------------------------------------------
  lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN);
  // set default height and position of main widgets
  setMainWidgetsHeightAndPosition();
  // At startup, set current GUI according to get_currentGUIname(), and create the content of that tab (and the previous and the next) for the first time
  guis_doTabCreationAtStartup();
  // memoryUsage bar
  init_gui_memoryUsage_bar();
  // status bar
  init_gui_status_bar();

  // register callback for swipe down event to navigate to the scene selection page
  lv_obj_add_event_cb(lv_scr_act(), screen_gesture_event_cb, LV_EVENT_GESTURE, NULL);

}

int panelHeight;
int memoryUsageBarTop;
int memoryUsageBarHeight;
int statusbarTop;
int statusbarHeight;
int tabviewTop;
int tabviewHeight;
int labelsPositionTop;

void setMainWidgetsHeightAndPosition() {
  panelHeight          = 30;
  memoryUsageBarTop    = 0;
  memoryUsageBarHeight = getShowMemoryUsage() ? 14 : 0;
  statusbarTop         = memoryUsageBarTop + memoryUsageBarHeight;
  statusbarHeight      = 20;
  tabviewTop           = statusbarTop + statusbarHeight;
  tabviewHeight        = SCR_HEIGHT - memoryUsageBarHeight - statusbarHeight - panelHeight;
  labelsPositionTop    = -2;
}

lv_obj_t* memoryUsageBar;
lv_obj_t* statusbar;

void showMemoryUsageBar(bool showBar) {
  setMainWidgetsHeightAndPosition();

  lv_obj_set_size(memoryUsageBar, SCR_WIDTH, memoryUsageBarHeight);
  lv_obj_align(memoryUsageBar, LV_ALIGN_TOP_MID, 0, memoryUsageBarTop);
  lv_obj_set_size(statusbar, SCR_WIDTH, statusbarHeight);
  lv_obj_align(statusbar, LV_ALIGN_TOP_MID, 0, statusbarTop);
  lv_obj_set_size(tabview, SCR_WIDTH, tabviewHeight);
  lv_obj_align(tabview, LV_ALIGN_TOP_MID, 0, tabviewTop);

  return;

  if (showBar) {
    // lv_obj_clear_flag(memoryUsageBar, LV_OBJ_FLAG_HIDDEN);
    lv_obj_set_size(memoryUsageBar, SCR_WIDTH, memoryUsageBarHeight);
    lv_obj_align(memoryUsageBar, LV_ALIGN_TOP_MID, 0, memoryUsageBarTop);
    lv_obj_set_size(statusbar, SCR_WIDTH, statusbarHeight);
    lv_obj_align(statusbar, LV_ALIGN_TOP_MID, 0, statusbarTop);
    lv_obj_set_size(tabview, SCR_WIDTH, tabviewHeight);
    lv_obj_align(tabview, LV_ALIGN_TOP_MID, 0, tabviewTop);
  } else {
    // lv_obj_add_flag(memoryUsageBar, LV_OBJ_FLAG_HIDDEN);

  }
}

void init_gui_memoryUsage_bar() {
  // Create a memory status text bar at the top -------------------------------------------------------------
  memoryUsageBar = lv_btn_create(lv_scr_act());
  lv_obj_set_size(memoryUsageBar, SCR_WIDTH, memoryUsageBarHeight);
  lv_obj_set_style_shadow_width(memoryUsageBar, 0, LV_PART_MAIN);
  lv_obj_set_style_bg_color(memoryUsageBar, lv_color_black(), LV_PART_MAIN);
  #ifdef drawRedBorderAroundMainWidgets
  lv_obj_add_style(memoryUsageBar, &style_red_border, LV_PART_MAIN);
  #endif
  lv_obj_set_style_radius(memoryUsageBar, 0, LV_PART_MAIN);
  lv_obj_set_style_pad_all(memoryUsageBar, 0, LV_PART_MAIN);
  lv_obj_align(memoryUsageBar, LV_ALIGN_TOP_MID, 0, memoryUsageBarTop);

  MemoryUsageLabel = lv_label_create(memoryUsageBar);
  lv_label_set_text(MemoryUsageLabel, "");
  lv_obj_align(MemoryUsageLabel, LV_ALIGN_TOP_LEFT, 0, labelsPositionTop);
  lv_obj_set_style_text_font(MemoryUsageLabel, &lv_font_montserrat_12, LV_PART_MAIN);
  lv_label_set_recolor(MemoryUsageLabel, true);
}

void init_gui_status_bar() {
  // Create a status bar at the top -------------------------------------------------------------------------
  statusbar = lv_btn_create(lv_scr_act());
  lv_obj_clear_flag(statusbar, LV_OBJ_FLAG_CLICKABLE);
  lv_obj_set_size(statusbar, SCR_WIDTH, statusbarHeight);
  lv_obj_set_style_shadow_width(statusbar, 0, LV_PART_MAIN);
  lv_obj_set_style_bg_color(statusbar, lv_color_black(), LV_PART_MAIN);
  #ifdef drawRedBorderAroundMainWidgets
  lv_obj_add_style(statusbar, &style_red_border, LV_PART_MAIN);
  #endif
  lv_obj_set_style_radius(statusbar, 0, LV_PART_MAIN);
  lv_obj_set_style_pad_all(statusbar, 0, LV_PART_MAIN);
  lv_obj_align(statusbar, LV_ALIGN_TOP_MID, 0, statusbarTop);

  int labelsPositionTopStatusbar = labelsPositionTop + 3;
  // WiFi -------------------------------------------------------------------------
  WifiLabel = lv_label_create(statusbar);
  lv_label_set_text(WifiLabel, "");
  lv_obj_align(WifiLabel, LV_ALIGN_TOP_LEFT, 0, labelsPositionTopStatusbar);
  lv_obj_set_style_text_font(WifiLabel, &lv_font_montserrat_12, LV_PART_MAIN);
  // Bluetooth --------------------------------------------------------------------
  BluetoothLabel = lv_label_create(statusbar);
  lv_label_set_text(BluetoothLabel, "");
  lv_obj_align(BluetoothLabel, LV_ALIGN_TOP_LEFT, 20, labelsPositionTopStatusbar);
  lv_obj_set_style_text_font(BluetoothLabel, &lv_font_montserrat_12, LV_PART_MAIN);
  // Scene ------------------------------------------------------------------------
  SceneLabel = lv_label_create(statusbar);
  lv_label_set_text(SceneLabel, "");
  lv_obj_align(SceneLabel, LV_ALIGN_TOP_MID, 0, labelsPositionTopStatusbar);
  lv_obj_set_style_text_font(SceneLabel, &lv_font_montserrat_12, LV_PART_MAIN);
  lv_obj_add_flag(SceneLabel, LV_OBJ_FLAG_CLICKABLE);
  lv_obj_add_event_cb(SceneLabel, sceneLabel_or_pageIndicator_event_cb, LV_EVENT_CLICKED, NULL);

  // Battery ----------------------------------------------------------------------
  BattPercentageLabel = lv_label_create(statusbar);
  lv_label_set_text(BattPercentageLabel, "");
  lv_obj_align(BattPercentageLabel, LV_ALIGN_TOP_RIGHT, -28, labelsPositionTopStatusbar);
  lv_obj_set_style_text_font(BattPercentageLabel, &lv_font_montserrat_12, LV_PART_MAIN);
  BattIconLabel = lv_label_create(statusbar);
  lv_label_set_text(BattIconLabel, LV_SYMBOL_BATTERY_EMPTY);
  lv_obj_align(BattIconLabel, LV_ALIGN_TOP_RIGHT, 0, labelsPositionTopStatusbar -1);
  lv_obj_set_style_text_font(BattIconLabel, &lv_font_montserrat_16, LV_PART_MAIN);

}

static bool waitOneLoop = false;
void gui_loop(void) {
  // after the sliding animation ended, we have to wait one cycle of gui_loop() before we can do the recreation of the tabs
  if (waitBeforeActionAfterSlidingAnimationEnded) {
    waitOneLoop = true;
    waitBeforeActionAfterSlidingAnimationEnded = false;
  } else if (waitOneLoop) {
    waitOneLoop = false;
    guis_doAfterSliding(oldTabID, currentTabID, false);
  };
  // // as alternative, we could wait some milliseconds. But one cycle of gui_loop() works well.
  // if (waitBeforeActionAfterSlidingAnimationEnded) {
  //   if (millis() - waitBeforeActionAfterSlidingAnimationEnded_timerStart >= 5) {
  //     guis_doAfterSliding(oldTabID, currentTabID, false);
  //     waitBeforeActionAfterSlidingAnimationEnded = false;
  //   }
  // }

  lv_timer_handler();
}

void guis_doTabCreationAtStartup() {
  gui_memoryOptimizer_prepare_startup();

  guis_doAfterSliding(-1, -1, false);
}

void guis_doAfterSliding(int oldTabID, int newTabID, bool newGuiList) {
  // With parameter newGuiList it is signaled that we are changing from a scene specific list to the main list or vice versa
  // In that case, we have to do special treatment because we are not simply sliding to the left or to the right, but we start newly with a different gui list.
  gui_memoryOptimizer_doAfterSliding_deletionAndCreation(&tabview, oldTabID, newTabID, newGuiList, &panel, &img1, &img2);

  doLogMemoryUsage();
}

void setActiveTab(uint32_t index, lv_anim_enable_t anim_en) {
  // unsigned long startTime = millis();
  if (anim_en == LV_ANIM_ON) {
    lv_tabview_set_act(tabview, index, LV_ANIM_ON);
    // startTime = millis();
    // while (millis() - startTime < 1000) {
    //   lv_timer_handler();
    // }
  } else {
    lv_tabview_set_act(tabview, index, LV_ANIM_OFF);
    // lv_timer_handler();
    // log_memory();
  }
}

void showWiFiConnected(bool connected) {
  if (connected) {
    if (WifiLabel != NULL) {lv_label_set_text(WifiLabel, LV_SYMBOL_WIFI);}
  } else {
    if (WifiLabel != NULL) {lv_label_set_text(WifiLabel, "");}
  }
}
