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

#include "simple_dialog.h"

#include "applib/applib_malloc.auto.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/bitmap_layer.h"
#include "applib/ui/dialogs/dialog.h"
#include "applib/ui/dialogs/dialog_private.h"
#include "applib/ui/layer.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window.h"
#include "kernel/ui/kernel_ui.h"
#include "system/passert.h"

#include <limits.h>
#include <string.h>

#if (RECOVERY_FW || UNITTEST)
#define SIMPLE_DIALOG_ANIMATED false
#else
#define SIMPLE_DIALOG_ANIMATED true
#endif

// Layout Defines
#define TEXT_ALIGNMENT (GTextAlignmentCenter)
#define TEXT_OVERFLOW  (GTextOverflowModeWordWrap)
#define TEXT_FONT      (fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD))

#define TEXT_LEFT_MARGIN_PX  (PBL_IF_RECT_ELSE(6, 0))
#define TEXT_RIGHT_MARGIN_PX (PBL_IF_RECT_ELSE(6, 0))
#define TEXT_FLOW_INSET_PX   (PBL_IF_RECT_ELSE(0, 8))
#define TEXT_LINE_HEIGHT_PX  (fonts_get_font_height(TEXT_FONT))
#define TEXT_MAX_HEIGHT_PX   ((2 * TEXT_LINE_HEIGHT_PX) + 8) // 2 line + some space for descenders


static int prv_get_rendered_text_height(const char *text, const GRect *text_box) {
  GContext *ctx = graphics_context_get_current_context();
  TextLayoutExtended layout = { 0 };
  graphics_text_attributes_enable_screen_text_flow((GTextLayoutCacheRef) &layout,
                                                   TEXT_FLOW_INSET_PX);
  return graphics_text_layout_get_max_used_size(ctx,
                                                text,
                                                TEXT_FONT,
                                                *text_box,
                                                TEXT_OVERFLOW,
                                                TEXT_ALIGNMENT,
                                                (GTextLayoutCacheRef) &layout).h;
}

static int prv_get_icon_top_margin(bool has_status_bar, int icon_height, int window_height) {
  const uint16_t status_layer_offset = has_status_bar ? 6 : 0;
#if PLATFORM_ROBERT || PLATFORM_CALCULUS || PLATFORM_OBELIX
  const uint16_t icon_top_default_margin_px = 42 + status_layer_offset;
#else
  const uint16_t icon_top_default_margin_px = 18 + status_layer_offset;
#endif
  const uint16_t frame_height_claimed = icon_height + TEXT_MAX_HEIGHT_PX + status_layer_offset;
  const uint16_t icon_top_adjusted_margin_px = MAX(window_height - frame_height_claimed, 0);
  // Try and use the default value if possible.
  return (icon_top_adjusted_margin_px < icon_top_default_margin_px) ? icon_top_adjusted_margin_px :
                                                                      icon_top_default_margin_px;
}

static void prv_get_text_box(GSize frame_size, GSize icon_size,
                              int icon_top_margin_px, GRect *text_box_out) {
  const uint16_t icon_text_spacing_px = PBL_IF_ROUND_ELSE(2, 4);

  const uint16_t text_x = TEXT_LEFT_MARGIN_PX;
  const uint16_t text_y = icon_top_margin_px + MAX(icon_size.h, 6) + icon_text_spacing_px;
  const uint16_t text_w = frame_size.w - TEXT_LEFT_MARGIN_PX - TEXT_RIGHT_MARGIN_PX;
  // Limit to 2 lines if there is an icon
  const uint16_t text_h = icon_size.h ? TEXT_MAX_HEIGHT_PX : frame_size.h - text_y;

  *text_box_out = GRect(text_x, text_y, text_w, text_h);
}

static void prv_simple_dialog_load(Window *window) {
  SimpleDialog *simple_dialog = window_get_user_data(window);
  Dialog *dialog = &simple_dialog->dialog;

  // Ownership of icon is taken over by KinoLayer in dialog_init_icon_layer() call below
  KinoReel *icon = dialog_create_icon(dialog);
  const GSize icon_size = icon ? kino_reel_get_size(icon) : GSizeZero;

  GRect frame = window->layer.bounds;

  // Status Layer
  if (dialog->show_status_layer) {
    dialog_add_status_bar_layer(dialog, &GRect(0, 0, frame.size.w, STATUS_BAR_LAYER_HEIGHT));
  }

  uint16_t icon_top_margin_px = prv_get_icon_top_margin(dialog->show_status_layer,
                                                        icon_size.h, frame.size.h);

  // Text
  GRect text_box;
  prv_get_text_box(frame.size, icon_size, icon_top_margin_px, &text_box);

  const uint16_t text_height = prv_get_rendered_text_height(dialog->buffer, &text_box);

  if (text_height <= TEXT_LINE_HEIGHT_PX) {
    const int additional_icon_top_offset_for_single_line_text_px = 13;
    // Move the icon down by increasing the margin to vertically center things
    icon_top_margin_px += additional_icon_top_offset_for_single_line_text_px;
    // Move the text down as well to preserve spacing
    // The -1 is there to preserve prior functionality ¯\_(ツ)_/¯
    text_box.origin.y += additional_icon_top_offset_for_single_line_text_px - 1;
  }

  TextLayer *text_layer = &dialog->text_layer;
  text_layer_init_with_parameters(text_layer, &text_box, dialog->buffer, TEXT_FONT,
                                  dialog->text_color, GColorClear, TEXT_ALIGNMENT, TEXT_OVERFLOW);
  layer_add_child(&window->layer, &text_layer->layer);

#if PBL_ROUND
  text_layer_enable_screen_text_flow_and_paging(text_layer, TEXT_FLOW_INSET_PX);
#endif

  // Icon
  const GPoint icon_origin = GPoint((grect_get_max_x(&frame) - icon_size.w) / 2,
                                    icon_top_margin_px);

  if (dialog_init_icon_layer(dialog, icon, icon_origin, !simple_dialog->icon_static)) {
    layer_add_child(&dialog->window.layer, &dialog->icon_layer.layer);
  }

  dialog_load(dialog);
}

static void prv_simple_dialog_appear(Window *window) {
  SimpleDialog *simple_dialog = window_get_user_data(window);
  Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
  dialog_appear(dialog);
}

static void prv_simple_dialog_unload(Window *window) {
  SimpleDialog *simple_dialog = window_get_user_data(window);
  dialog_unload(&simple_dialog->dialog);
  if (simple_dialog->dialog.destroy_on_pop) {
    applib_free(simple_dialog);
  }
}

static void prv_click_handler(ClickRecognizerRef recognizer, void *context) {
  SimpleDialog *simple_dialog = context;
  if (!simple_dialog->buttons_disabled) {
    dialog_pop(&simple_dialog->dialog);
  }
}

static void prv_config_provider(void *context) {
  // Simple dialogs are dimissed when any button is pushed.
  window_single_click_subscribe(BUTTON_ID_SELECT, prv_click_handler);
  window_single_click_subscribe(BUTTON_ID_UP, prv_click_handler);
  window_single_click_subscribe(BUTTON_ID_DOWN, prv_click_handler);
}

Dialog *simple_dialog_get_dialog(SimpleDialog *simple_dialog) {
  return &simple_dialog->dialog;
}

void simple_dialog_push(SimpleDialog *simple_dialog, WindowStack *window_stack) {
  dialog_push(&simple_dialog->dialog, window_stack);
}

void app_simple_dialog_push(SimpleDialog *simple_dialog) {
  app_dialog_push(&simple_dialog->dialog);
}

void simple_dialog_init(SimpleDialog *simple_dialog, const char *dialog_name) {
  PBL_ASSERTN(simple_dialog);
  *simple_dialog = (SimpleDialog) {
    .icon_static = !SIMPLE_DIALOG_ANIMATED,
  };

  dialog_init(&simple_dialog->dialog, dialog_name);
  Window *window = &simple_dialog->dialog.window;
  window_set_window_handlers(window, &(WindowHandlers) {
    .load = prv_simple_dialog_load,
    .unload = prv_simple_dialog_unload,
    .appear = prv_simple_dialog_appear,
  });
  window_set_click_config_provider_with_context(window, prv_config_provider, simple_dialog);
  window_set_user_data(window, simple_dialog);
}

SimpleDialog *simple_dialog_create(const char *dialog_name) {
  SimpleDialog *simple_dialog = applib_malloc(sizeof(SimpleDialog));
  if (simple_dialog) {
    simple_dialog_init(simple_dialog, dialog_name);
  }
  return simple_dialog;
}

void simple_dialog_set_buttons_enabled(SimpleDialog *simple_dialog, bool enabled) {
  simple_dialog->buttons_disabled = !enabled;
}

void simple_dialog_set_icon_animated(SimpleDialog *simple_dialog, bool animated) {
  // This cannot be set after the window has been loaded
  PBL_ASSERTN(!window_is_loaded(&simple_dialog->dialog.window));
  simple_dialog->icon_static = !animated;
}

bool simple_dialog_does_text_fit(const char *text, GSize window_size,
                                 GSize icon_size, bool has_status_bar) {
  const uint16_t icon_top_margin_px = prv_get_icon_top_margin(has_status_bar, icon_size.h,
                                                              window_size.h);
  GRect text_box;
  prv_get_text_box(window_size, icon_size, icon_top_margin_px, &text_box);
  return prv_get_rendered_text_height(text, &text_box) <= TEXT_MAX_HEIGHT_PX;
}
