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

#include "number_window.h"

#include "applib/fonts/fonts.h"
#include "applib/graphics/graphics.h"
#include "applib/applib_malloc.auto.h"
#include "kernel/ui/kernel_ui.h"
#include "kernel/ui/system_icons.h"
#include "util/size.h"

#include <stdio.h>
#include <limits.h>

#if RECOVERY_FW || MANUFACTURING_FW
#define NUMBER_FONT_KEY FONT_KEY_GOTHIC_24_BOLD
#else
#define NUMBER_FONT_KEY FONT_KEY_BITHAM_34_MEDIUM_NUMBERS
#endif

// updates the textual output value of the numberwindow to match the actual value
static void update_output_value(NumberWindow *nf) {
  layer_mark_dirty(&nf->window.layer);
}

static void up_click_handler(ClickRecognizerRef recognizer, NumberWindow *nf) {
  bool is_increased = false;
  int32_t new_val = nf->value + nf->step_size;
  if (new_val <= nf->max_val && new_val > nf->value) {
    nf->value = new_val;
    is_increased = true;
  }
  if (is_increased) {
    if (nf->callbacks.incremented != NULL) {
      nf->callbacks.incremented(nf, nf->callback_context);
    }
    update_output_value(nf);
  }
}

static void down_click_handler(ClickRecognizerRef recognizer, NumberWindow *nf) {
  bool is_decreased = false;
  int32_t new_val = nf->value - nf->step_size;
  if (new_val >= nf->min_val && new_val < nf->value) {
    nf->value = new_val;
    is_decreased = true;
  }
  if (is_decreased) {
    if (nf->callbacks.decremented != NULL) {
      nf->callbacks.decremented(nf, nf->callback_context);
    }
    update_output_value(nf);
  }
}

static void select_click_handler(ClickRecognizerRef recognizer, NumberWindow *nf) {
  if (nf->callbacks.selected != NULL) {
    nf->callbacks.selected(nf, nf->callback_context);
  }
}

static void click_config_provider(NumberWindow *nf) {
  window_single_repeating_click_subscribe(BUTTON_ID_UP, 50, (ClickHandler) up_click_handler);
  window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 50, (ClickHandler) down_click_handler);

  // Work-around: by using a multi-click setup for the select button,
  // the handler will get fired with a very short delay, so the inverted segment of
  // the action bar is visible for a short period of time as to give visual
  // feedback of the button press.
  window_multi_click_subscribe(BUTTON_ID_SELECT, 1, 2, 25, true, (ClickHandler)select_click_handler);
}

static GRect prv_get_text_frame(Layer *window_layer) {
  const int16_t x_margin = 5;
  const int16_t label_y_offset = PBL_IF_ROUND_ELSE(40, 16);
  const GEdgeInsets insets = PBL_IF_ROUND_ELSE(GEdgeInsets(ACTION_BAR_WIDTH + x_margin),
                                               GEdgeInsets(0, ACTION_BAR_WIDTH + x_margin, 0,
                                                           x_margin));
  GRect frame = grect_inset(window_layer->bounds, insets);
  frame.origin.y = label_y_offset;
  return frame;
}

//! Drawing function for our Window's base Layer. Draws the background, the label, and the value,
//! which is everything on screen with the exception of the child ActionBarLayer
void prv_update_proc(Layer *layer, GContext* ctx) {
  graphics_context_set_fill_color(ctx, GColorWhite);
  graphics_fill_rect(ctx, &layer->bounds);

  // This is safe becase Layer is the first member in Window and Window is the first member in
  // NumberWindow.
  _Static_assert(offsetof(Window, layer) == 0, "");
  _Static_assert(offsetof(NumberWindow, window) == 0, "");
  NumberWindow *nw = (NumberWindow*) layer;

  graphics_context_set_text_color(ctx, GColorBlack);

  GRect frame = prv_get_text_frame(layer);
  frame.size.h = 54;

  TextLayoutExtended cached_label_layout = {};
  graphics_draw_text(ctx, nw->label, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
                     frame, GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
                     (TextLayout*) &cached_label_layout);

  char value_output_buffer[12];
  snprintf(value_output_buffer, ARRAY_LENGTH(value_output_buffer), "%"PRId32, nw->value);

  frame.origin.y += cached_label_layout.max_used_size.h;
#if PBL_RECT
  const int16_t output_offset_from_label = 15;
  frame.origin.y += output_offset_from_label;
#endif
  frame.size.h = 48;

  graphics_draw_text(ctx, value_output_buffer,
                     fonts_get_system_font(NUMBER_FONT_KEY), frame,
                     GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}

void number_window_set_label(NumberWindow *nw, const char *label) {
  nw->label = label;
  layer_mark_dirty(&nw->window.layer);
}

void number_window_set_max(NumberWindow *nf, int32_t max) {
  nf->max_val = max;
  if (nf->value > max) {
    nf->value = max;
    update_output_value(nf);
  }
  if (nf->min_val > max) {
    nf->min_val = max;
  }
}

void number_window_set_min(NumberWindow *nf, int32_t min) {
  nf->min_val = min;
  if (nf->value < min) {
    nf->value = min;
    update_output_value(nf);
  }
  if (nf->max_val < min) {
    nf->max_val = min;
  }
}

void number_window_set_value(NumberWindow *nf, int32_t value) {
  nf->value = value;
  if (nf->value > nf->max_val) {
    nf->value = nf->max_val;
  }
  if (nf->value < nf->min_val) {
    nf->value = nf->min_val;
  }
  update_output_value(nf);
}

void number_window_set_step_size(NumberWindow *nf, int32_t step) {
  nf->step_size = step;
}

int32_t number_window_get_value(const NumberWindow *nf) {
  return nf->value;
}

static void number_window_load(NumberWindow *nw) {
  ActionBarLayer *action_bar = &nw->action_bar;
  action_bar_layer_set_context(action_bar, nw);
  action_bar_layer_set_icon(action_bar, BUTTON_ID_UP, &s_bar_icon_up_bitmap);
  action_bar_layer_set_icon(action_bar, BUTTON_ID_DOWN, &s_bar_icon_down_bitmap);
  action_bar_layer_set_icon(action_bar, BUTTON_ID_SELECT, &s_bar_icon_check_bitmap);
  action_bar_layer_add_to_window(action_bar, &nw->window);
  action_bar_layer_set_click_config_provider(action_bar, (ClickConfigProvider) click_config_provider);
}

void number_window_init(NumberWindow *nw, const char *label, NumberWindowCallbacks callbacks, void *callback_context) {
  *nw = (NumberWindow) {
    .label = label,
    .value = 0,
    .max_val = INT_MAX,
    .min_val = INT_MIN,
    .step_size = 1,
    .callbacks = callbacks,
    .callback_context = callback_context
  };

  window_init(&nw->window, WINDOW_NAME(label));
  window_set_window_handlers(&nw->window, &(WindowHandlers) {
    .load = (WindowHandler) number_window_load,
  });
  layer_set_update_proc(&nw->window.layer, prv_update_proc);

  ActionBarLayer *action_bar = &nw->action_bar;
  action_bar_layer_init(action_bar);
}

NumberWindow* number_window_create(const char *label, NumberWindowCallbacks callbacks, void *callback_context) {
  NumberWindow* window = applib_type_malloc(NumberWindow);
  if (window) {
    number_window_init(window, label, callbacks, callback_context);
  }
  return window;
}

static void number_window_deinit(NumberWindow *number_window) {
  action_bar_layer_deinit(&number_window->action_bar);
  window_deinit(&number_window->window);
}

void number_window_destroy(NumberWindow *number_window) {
  if (number_window == NULL) {
    return;
  }
  number_window_deinit(number_window);
  applib_free(number_window);
}

Window *number_window_get_window(NumberWindow *numberwindow) {
  return (&numberwindow->window);
}
