/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include "settings_activity_tracker.h"
#include "settings_menu.h"
#include "settings_window.h"

#include "applib/app.h"
#include "applib/app_timer.h"
#include "applib/ui/kino/kino_reel.h"
#include "applib/ui/option_menu_window.h"
#include "applib/ui/ui.h"
#include "applib/ui/window.h"
#include "applib/ui/window_stack.h"
#include "kernel/event_loop.h"
#include "kernel/pbl_malloc.h"
#include "popups/switch_worker_ui.h"
#include "process_management/app_menu_data_source.h"
#include "process_management/worker_manager.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "services/common/i18n/i18n.h"
#include "shell/normal/watchface.h"
#include "system/passert.h"

#include <string.h>

typedef struct SettingsActivityTrackerData {
  OptionMenu option_menu;
  MenuLayer menu_layer;
  AppMenuDataSource *data_source;
  TextLayer *text_layer;
  EventServiceInfo worker_launch_info;
} SettingsActivityTrackerData;

////////////////////
// AppMenuDataSource callbacks

static bool prv_app_filter_callback(struct AppMenuDataSource *const source,
                                    AppInstallEntry *entry) {
  if (!app_install_entry_is_hidden(entry) &&
      app_install_entry_has_worker(entry)) {
    return true;
  }
  return false;
}

static int16_t prv_get_chosen_row_index_for_id(SettingsActivityTrackerData *data,
                                               AppInstallId worker_id) {
  if (worker_id == INSTALL_ID_INVALID) {
    return 0;
  }

  const uint16_t current_worker_app_index =
      app_menu_data_source_get_index_of_app_with_install_id(data->data_source, worker_id);

  if (current_worker_app_index == MENU_INDEX_NOT_FOUND) {
    return 0;
  } else {
    return current_worker_app_index + 1;
  }
}

// Gets the current chosen row index; i.e., the row which was most recently chosen by the user.
static int16_t prv_get_chosen_row_index(SettingsActivityTrackerData *data) {
  const AppInstallId worker_id = worker_manager_get_current_worker_id();
  return prv_get_chosen_row_index_for_id(data, worker_id);
}

static int prv_num_rows(SettingsActivityTrackerData *data) {
  if (data->data_source) {
    return app_menu_data_source_get_count(data->data_source);
  } else {
    return 0;
  }
}

static void prv_reload_menu_data(void *context) {
  SettingsActivityTrackerData *data = context;
  const uint16_t count = prv_num_rows(data);
  const bool use_icons = (count != 0);
  option_menu_set_icons_enabled(&data->option_menu, use_icons /* icons_enabled */);

  option_menu_set_choice(&data->option_menu, prv_get_chosen_row_index(data));
  option_menu_reload_data(&data->option_menu);
}

// Settings Menu callbacks
///////////////////////////

static void prv_select_cb(OptionMenu *option_menu, int row, void *context) {
  SettingsActivityTrackerData *data = context;
  if (app_menu_data_source_get_count(data->data_source) == 0) {
    return;
  }
  if (row == 0) {
    // Killing current worker
    process_manager_put_kill_process_event(PebbleTask_Worker, true /* graceful */);
    worker_manager_set_default_install_id(INSTALL_ID_INVALID);
  } else {
    const uint16_t app_index = row - 1; // offset because of the "None" selection
    const AppMenuNode *app_node =
        app_menu_data_source_get_node_at_index(data->data_source, app_index);
    if (worker_manager_get_task_context()->install_id == INSTALL_ID_INVALID) {
      // No worker currently running, launch this one and make it the default
      worker_manager_put_launch_worker_event(app_node->install_id);
      worker_manager_set_default_install_id(app_node->install_id);
    } else if (worker_manager_get_task_context()->install_id != app_node->install_id) {
      // Undo the choice change that the OptionMenu does before we call select. We may decline
      // the change and therefore we don't want it to visually update yet. prv_worker_launch_handler
      // will update the choice if it fires.
      option_menu_set_choice(&data->option_menu, prv_get_chosen_row_index(data));

      // Switching to a different worker, display confirmation dialog
      switch_worker_confirm(app_node->install_id, true /* set as default */,
                            app_state_get_window_stack());
    } else {
      // User selected the option they already had, do nothing
    }
  }
}

static void prv_draw_no_activities_cell_rect(GContext *ctx, const Layer *cell_layer,
                                             const char *no_activities_string) {
  const GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
  GRect box = cell_layer->bounds;

  const GTextOverflowMode overflow = GTextOverflowModeTrailingEllipsis;
  const GTextAlignment alignment = GTextAlignmentCenter;

  const GSize text_size = graphics_text_layout_get_max_used_size(ctx, no_activities_string, font,
                                                                 box, overflow, alignment, NULL);

  // We want to position the text in the center of the cell veritically,
  // we divide the height of the cell by two and subtract half of the text size.
  // However, that just puts the TOP of a line vertically aligned.
  // So we also have to subtract half of a single line's width.
  box.origin.y = (box.size.h - text_size.h - fonts_get_font_height(font)/2) / 2;

  graphics_draw_text(ctx, no_activities_string, font, box, overflow, alignment, NULL);
}

#if PBL_ROUND
static void prv_draw_no_activities_cell_round(GContext *ctx, const Layer *cell_layer,
                                              const char *no_activities_string) {
  menu_cell_basic_draw(ctx, cell_layer, no_activities_string, NULL, NULL);
}
#endif

static uint16_t prv_get_num_rows_cb(OptionMenu *option_menu, void *context) {
  SettingsActivityTrackerData *data = context;
  const uint16_t count = prv_num_rows(data);
  return count + 1;
}

static void prv_draw_row_cb(OptionMenu *option_menu, GContext *ctx, const Layer *cell_layer,
                            const GRect *text_frame, uint32_t row, bool selected, void *context) {
  SettingsActivityTrackerData *data = context;

  if (prv_num_rows(data) == 0) {
    // Draw "No background apps" box and exit
    const char *no_background_apps_string = i18n_get("No background apps", data);
    PBL_IF_RECT_ELSE(prv_draw_no_activities_cell_rect,
                     prv_draw_no_activities_cell_round)
                     (ctx, cell_layer, no_background_apps_string);
    return;
  }

  const char *title = NULL;
  if (row == 0) {
    title = i18n_get("None", data);
  } else {
    AppMenuNode *node = app_menu_data_source_get_node_at_index(data->data_source, row - 1);
    title = node->name;
  }

  option_menu_system_draw_row(option_menu, ctx, cell_layer, text_frame, title, false, NULL);
}

static uint16_t prv_row_height_cb(OptionMenu *option_menu, uint16_t row, bool is_selected,
                                  void *context) {
  const int16_t cell_height =
      option_menu_default_cell_height(option_menu->content_type, is_selected);
#if PBL_RECT
  if (prv_num_rows(context) == 0) {
    // When we have no background apps, we want a double height row to display the
    // 'No background apps' line, so that translations can fit and we stop wasting so much screen
    // space.
    return 2 * cell_height;
  }
#endif
  return cell_height;
}

static void prv_worker_launch_handler(PebbleEvent *event, void *context) {
  // Our worker changed while we were visible, update the selected choice
  SettingsActivityTrackerData *data = context;

  const AppInstallId worker_id = event->launch_app.id;
  const int16_t chosen_row = prv_get_chosen_row_index_for_id(data, worker_id);

  option_menu_set_choice(&data->option_menu, chosen_row);
}

static void prv_unload_cb(OptionMenu *option_menu, void *context) {
  SettingsActivityTrackerData *data = context;

  event_service_client_unsubscribe(&data->worker_launch_info);

  app_menu_data_source_deinit(data->data_source);

  app_free(data->data_source);
  data->data_source = NULL;

  option_menu_deinit(&data->option_menu);

  i18n_free_all(data);
  app_free(data);
}

static Window *prv_init(void) {
  SettingsActivityTrackerData *data = app_zalloc_check(sizeof(SettingsActivityTrackerData));

  const OptionMenuCallbacks option_menu_callbacks = {
      .unload = prv_unload_cb,
      .draw_row = prv_draw_row_cb,
      .select = prv_select_cb,
      .get_num_rows = prv_get_num_rows_cb,
      .get_cell_height = prv_row_height_cb,
  };

  data->data_source = app_zalloc_check(sizeof(AppMenuDataSource));
  app_menu_data_source_init(data->data_source, &(AppMenuDataSourceCallbacks) {
    .changed = prv_reload_menu_data,
    .filter = prv_app_filter_callback,
  }, data);

  option_menu_init(&data->option_menu);
  // Not using option_menu_configure because prv_reload_menu_data already sets
  // icons_enabled and chosen row index
  option_menu_set_status_colors(&data->option_menu, GColorWhite, GColorBlack);
  option_menu_set_highlight_colors(&data->option_menu, shell_prefs_get_settings_menu_highlight_color(), GColorWhite);
  option_menu_set_title(&data->option_menu, i18n_get("Background App", data));
  option_menu_set_content_type(&data->option_menu, OptionMenuContentType_SingleLine);
  option_menu_set_callbacks(&data->option_menu, &option_menu_callbacks, data);
  prv_reload_menu_data(data);

  data->worker_launch_info = (EventServiceInfo) {
    .type = PEBBLE_WORKER_LAUNCH_EVENT,
    .handler = prv_worker_launch_handler,
    .context = data
  };
  event_service_client_subscribe(&data->worker_launch_info);

  return &data->option_menu.window;
}

const SettingsModuleMetadata *settings_activity_tracker_get_info(void) {
  static const SettingsModuleMetadata s_module_info = {
    .name = i18n_noop("Background App"),
    .init = prv_init,
  };

  return &s_module_info;
}
