/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


// This app only makes sense on Snowy, as it uses addresses and sector sizes that only make sense
// on our parallel flash hardware
#if CAPABILITY_USE_PARALLEL_FLASH

#include "flash_test.h"

#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/simple_menu_layer.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window.h"
#include "applib/ui/window_stack.h"


#include "mfg/mfg_apps/mfg_flash_test.h"
#include "process_state/app_state/app_state.h"
#include "services/common/system_task.h"
#include "system/logging.h"

#include "kernel/pbl_malloc.h"

#include <stdio.h>

enum FlashTestCaseStatus {
  FLASH_TEST_STATUS_INIT = 0,
  FLASH_TEST_STATUS_RUNNING = 1,
  FLASH_TEST_STATUS_STOPPED = 2,
  FLASH_TEST_STATUS_PASSED = 3,
  FLASH_TEST_STATUS_FAILED = 4,
};

#define STATUS_TEXT_SIZE 18
struct FlashTestData {
  Window window;
  SimpleMenuLayer simple_menu_layer;
  SimpleMenuSection menu_sections[1];
  SimpleMenuItem menu_items[FLASH_TEST_CASE_NUM_MENU_ITEMS];
  Window test_window;
  TextLayer msg_text_layer[3];
  TextLayer status_text_layer;
  char status_text[STATUS_TEXT_SIZE];
  FlashTestCaseType test_case;
  uint8_t test_case_status;
};

/***********************************************************/
/******************* Window Related Functions **************/
/***********************************************************/
static void handle_timer(struct tm *tick_time, TimeUnits units_changed) {
  struct FlashTestData *data = app_state_get_user_data();
  // simply marking the window dirty will make everything update
  if (data && (data->test_case_status != FLASH_TEST_STATUS_INIT))
  {
    layer_mark_dirty(&data->test_window.layer);
  }
}

static void update_test_case_status(struct FlashTestData *data) {
  switch (data->test_case_status) {
    case FLASH_TEST_STATUS_INIT:
      snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Initialized");
      break;
    case FLASH_TEST_STATUS_RUNNING:
      snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Running");
      break;
    case FLASH_TEST_STATUS_STOPPED:
      snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Stopped");
      break;
    case FLASH_TEST_STATUS_PASSED:
      snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Passed");
      break;
    case FLASH_TEST_STATUS_FAILED:
      snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Failed");
      break;
    default:
      snprintf(data->status_text, STATUS_TEXT_SIZE, "Unknown Status");
      PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Unknown Test Case Selected");
      break;
  }

  text_layer_set_text(&data->status_text_layer, data->status_text);
}

static void test_window_load(Window *window) {
  struct FlashTestData *data = app_state_get_user_data();

  window_set_background_color(window, GColorBlack);

  TextLayer *msg_text_layer = &data->msg_text_layer[0];
  text_layer_init(msg_text_layer, &GRect(0, 12, window->layer.bounds.size.w, 18));
  switch (data->test_case) {
    case FLASH_TEST_CASE_RUN_DATA_TEST:
      text_layer_set_text(msg_text_layer, "Data Bus Test");
      break;
    case FLASH_TEST_CASE_RUN_ADDR_TEST:
      text_layer_set_text(msg_text_layer, "Addr Bus Test");
      break;
    case FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST:
      text_layer_set_text(msg_text_layer, "Stress Addr Test");
      break;
    case FLASH_TEST_CASE_RUN_PERF_DATA_TEST:
      text_layer_set_text(msg_text_layer, "Perf Data Test");
      break;
    case FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC:
    case FLASH_TEST_CASE_RUN_SWITCH_MODE_SYNC_BURST:
      text_layer_set_text(msg_text_layer, "Switch Mode");
      break;
    default:
      text_layer_set_text(msg_text_layer, "Unknown Test");
      break;
  }

  text_layer_set_text_alignment(msg_text_layer, GTextAlignmentCenter);
  text_layer_set_background_color(msg_text_layer, GColorBlack);
  text_layer_set_text_color(msg_text_layer, GColorWhite);
  text_layer_set_font(msg_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
  layer_add_child(&window->layer, &msg_text_layer->layer);

  msg_text_layer = &data->msg_text_layer[1];
  text_layer_init(msg_text_layer, &GRect(0, 32, window->layer.bounds.size.w, 18));
  if (data->test_case >= FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC) {
    text_layer_set_text(msg_text_layer, "Select To Confirm Switch");
  }
  else {
    text_layer_set_text(msg_text_layer, "Press Select To Start");
  }
  text_layer_set_text_alignment(msg_text_layer, GTextAlignmentCenter);
  text_layer_set_background_color(msg_text_layer, GColorBlack);
  text_layer_set_text_color(msg_text_layer, GColorWhite);
  text_layer_set_font(msg_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
  layer_add_child(&window->layer, &msg_text_layer->layer);

  msg_text_layer = &data->msg_text_layer[2];
  text_layer_init(msg_text_layer, &GRect(0, 64, window->layer.bounds.size.w, 40));
  if (data->test_case >= FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC) {
    text_layer_set_text(msg_text_layer, "Press Back To Exit");
  }
  else {
    text_layer_set_text(msg_text_layer, "Press Up to Exit, Down to Stop Test");
  }
  text_layer_set_text_alignment(msg_text_layer, GTextAlignmentCenter);
  text_layer_set_background_color(msg_text_layer, GColorBlack);
  text_layer_set_text_color(msg_text_layer, GColorWhite);
  text_layer_set_font(msg_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
  layer_add_child(&window->layer, &msg_text_layer->layer);

  TextLayer *status_text_layer = &data->status_text_layer;
  text_layer_init(status_text_layer, &GRect(0, 106, window->layer.bounds.size.w, 40));
  text_layer_set_text_alignment(status_text_layer, GTextAlignmentCenter);
  text_layer_set_background_color(status_text_layer, GColorBlack);
  text_layer_set_text_color(status_text_layer, GColorWhite);
  text_layer_set_font(status_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
  layer_add_child(&window->layer, &status_text_layer->layer);

  update_test_case_status(data);
}

void flash_test_dismiss_window(void) {
  struct FlashTestData *data = app_state_get_user_data();

  const bool animated = true;
  window_stack_remove(&data->test_window, animated);
}

static void up_click_handler(ClickRecognizerRef recognizer, void *unusued) {
  struct FlashTestData *data = app_state_get_user_data();

  if (data->test_case_status != FLASH_TEST_STATUS_RUNNING) {
    flash_test_dismiss_window(); 
  }
}

static void down_click_handler(ClickRecognizerRef recognizer, void *unusued) {
  struct FlashTestData *data = app_state_get_user_data();

  // Only stop stress test
  if ((data->test_case == FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST) && (data->test_case_status == FLASH_TEST_STATUS_RUNNING)) {
    data->test_case_status = FLASH_TEST_STATUS_STOPPED;
    stop_flash_test_case();
    update_test_case_status(data);
  }
}

static void run_test(void* context) {
  struct FlashTestData *data = app_state_get_user_data();
  FlashTestErrorType status = FLASH_TEST_SUCCESS;

  // Execute test - pass in 0 by default for iterations
  status = run_flash_test_case(data->test_case, 0);

  if (status == FLASH_TEST_SUCCESS) {
    data->test_case_status = FLASH_TEST_STATUS_PASSED;
  }
  else {
    PBL_LOG(LOG_LEVEL_DEBUG, ">>>>>FAILED TEST CASE<<<<<");
    data->test_case_status = FLASH_TEST_STATUS_FAILED;
  }

  update_test_case_status(data);
}

static void select_click_handler(ClickRecognizerRef recognizer, void *unusued) {
  struct FlashTestData *data = app_state_get_user_data();

  if ((data->test_case == FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST) && (data->test_case_status == FLASH_TEST_STATUS_RUNNING)) {
    data->test_case_status = FLASH_TEST_STATUS_STOPPED;
    stop_flash_test_case();
    update_test_case_status(data);
  }
  else if (data->test_case_status == FLASH_TEST_STATUS_INIT) {
    data->test_case_status = FLASH_TEST_STATUS_RUNNING;
    update_test_case_status(data);
    system_task_add_callback(run_test, NULL);
  }
}

static void click_config_provider(void *context) {
  window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler)up_click_handler);
  window_single_click_subscribe(BUTTON_ID_DOWN, (ClickHandler)down_click_handler);
  window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler)select_click_handler);
}

static void flash_test_select_callback(int index, void *context) {
  struct FlashTestData *data = (struct FlashTestData *) context;

  data->test_case = index;
  data->test_case_status = FLASH_TEST_STATUS_INIT;

  // Display window for running test case
  // Init the window
  Window *test_window = &data->test_window;
  window_init(test_window, WINDOW_NAME("Test Case"));
  window_set_window_handlers(test_window, &(WindowHandlers) {
    .load = test_window_load,
  });
  window_set_user_data(test_window, data);
  window_set_click_config_provider(test_window, click_config_provider);

  const bool animated = true;
  app_window_stack_push(test_window, animated);
}

static void flash_test_window_load(Window *window) {
  struct FlashTestData *data = (struct FlashTestData*) window_get_user_data(window);

  // Configure menu items:
  uint8_t num_items = 0;
  data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Data Test",
                                                     .callback = flash_test_select_callback};
  data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Address Test",
                                                     .callback = flash_test_select_callback};
  data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Stress Test",
                                                     .callback = flash_test_select_callback};
  data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Perf Data Test",
                                                     .callback = flash_test_select_callback};
  data->menu_items[num_items++] = (SimpleMenuItem) { .title = "-->Async Mode",
                                                     .callback = flash_test_select_callback};
  data->menu_items[num_items++] = (SimpleMenuItem) { .title = "-->Sync Burst Mode",
                                                     .callback = flash_test_select_callback};

  data->menu_sections[0].num_items = num_items;
  data->menu_sections[0].items = data->menu_items;

  // Configure simple menu:
  const GRect *bounds = &data->window.layer.bounds;
  simple_menu_layer_init(&data->simple_menu_layer, bounds, window, data->menu_sections, 1, data);
  layer_add_child(&data->window.layer, simple_menu_layer_get_layer(&data->simple_menu_layer));
}

static void handle_init(void) {
  struct FlashTestData* data_ptr = app_malloc_check(sizeof(struct FlashTestData));

  memset(data_ptr, 0, sizeof(struct FlashTestData));

  app_state_set_user_data(data_ptr);

  window_init(&data_ptr->window, WINDOW_NAME("Flash Test"));
  window_set_user_data(&data_ptr->window, data_ptr);
  window_set_window_handlers(&data_ptr->window, &(WindowHandlers){
    .load = flash_test_window_load,
  });
  
  const bool animated = true;
  app_window_stack_push(&data_ptr->window, animated);

  tick_timer_service_subscribe(SECOND_UNIT, handle_timer);
}

static void handle_deinit(void) {
  struct FlashTestData *data = app_state_get_user_data();
  simple_menu_layer_deinit(&data->simple_menu_layer);
  app_free(data);
  
  stop_flash_test_case();
}


static void s_main(void) {
  handle_init();

  app_event_loop();

  handle_deinit();
}

const PebbleProcessMd* flash_test_demo_get_app_info(void) {
  static const PebbleProcessMdSystem s_app_info = {
    .common.main_func = s_main,
    .name = "Flash Test"
  };
  return (const PebbleProcessMd*) &s_app_info;
}

#endif // CAPABILITY_USE_PARALLEL_FLASH
