#include <pebble.h>

#define MAX_ROWS 4
#define ANIMATION_INTERVAL_MS 33
#define ANIMATION_FRAMES 12

typedef struct {
  char label[24];
  char detail[28];
  int percent;
  int status_rank;
  bool has_bar;
} QuotaRow;

static Window *s_main_window;
static Layer *s_card_layer;
static AppTimer *s_animation_timer;

static int s_page_index = 0;
static int s_page_count = 0;
static int s_row_count = 0;
static int s_animation_frame = ANIMATION_FRAMES;
static int s_refresh_frame = 0;
static int s_animation_direction = 1;
static bool s_is_refreshing = false;

static char s_title_text[40] = "Pebblexus";
static char s_group_text[40] = "";
static char s_reset_text[40] = "Set URL and admin key";
static char s_status_text[16] = "Waiting";
static char s_updated_text[16] = "";
static QuotaRow s_rows[MAX_ROWS];

static uint32_t row_label_key(int row_index) {
  switch (row_index) {
  case 0:
    return MESSAGE_KEY_ROW_0_LABEL;
  case 1:
    return MESSAGE_KEY_ROW_1_LABEL;
  case 2:
    return MESSAGE_KEY_ROW_2_LABEL;
  default:
    return MESSAGE_KEY_ROW_3_LABEL;
  }
}

static uint32_t row_detail_key(int row_index) {
  switch (row_index) {
  case 0:
    return MESSAGE_KEY_ROW_0_DETAIL;
  case 1:
    return MESSAGE_KEY_ROW_1_DETAIL;
  case 2:
    return MESSAGE_KEY_ROW_2_DETAIL;
  default:
    return MESSAGE_KEY_ROW_3_DETAIL;
  }
}

static uint32_t row_percent_key(int row_index) {
  switch (row_index) {
  case 0:
    return MESSAGE_KEY_ROW_0_PERCENT;
  case 1:
    return MESSAGE_KEY_ROW_1_PERCENT;
  case 2:
    return MESSAGE_KEY_ROW_2_PERCENT;
  default:
    return MESSAGE_KEY_ROW_3_PERCENT;
  }
}

static uint32_t row_status_key(int row_index) {
  switch (row_index) {
  case 0:
    return MESSAGE_KEY_ROW_0_STATUS;
  case 1:
    return MESSAGE_KEY_ROW_1_STATUS;
  case 2:
    return MESSAGE_KEY_ROW_2_STATUS;
  default:
    return MESSAGE_KEY_ROW_3_STATUS;
  }
}

static uint32_t row_has_bar_key(int row_index) {
  switch (row_index) {
  case 0:
    return MESSAGE_KEY_ROW_0_HAS_BAR;
  case 1:
    return MESSAGE_KEY_ROW_1_HAS_BAR;
  case 2:
    return MESSAGE_KEY_ROW_2_HAS_BAR;
  default:
    return MESSAGE_KEY_ROW_3_HAS_BAR;
  }
}

static int clamp_percent(int percent) {
  if (percent < 0)
    return 0;
  if (percent > 100)
    return 100;
  return percent;
}

static GColor color_background(void) { return PBL_IF_COLOR_ELSE(GColorWhite, GColorWhite); }

static GColor color_card_outline(void) { return PBL_IF_COLOR_ELSE(GColorFromHEX(0xAAFFFF), GColorBlack); }

static GColor color_text_primary(void) { return GColorBlack; }

static GColor color_text_secondary(void) { return PBL_IF_COLOR_ELSE(GColorCobaltBlue, GColorBlack); }

static GColor color_text_muted(void) { return PBL_IF_COLOR_ELSE(GColorDarkGray, GColorBlack); }

static GColor color_text_soft(void) { return PBL_IF_COLOR_ELSE(GColorDukeBlue, GColorBlack); }

static GColor color_track(void) { return PBL_IF_COLOR_ELSE(GColorLightGray, GColorBlack); }

static GColor color_card_highlight(void) { return PBL_IF_COLOR_ELSE(GColorVividCerulean, GColorBlack); }

static GColor color_warning_soft(void) { return PBL_IF_COLOR_ELSE(GColorIcterine, GColorWhite); }

static GColor color_ok_soft(void) { return PBL_IF_COLOR_ELSE(GColorMintGreen, GColorWhite); }

static GColor color_for_status(int status_rank) {
#ifdef PBL_COLOR
  if (status_rank >= 4)
    return GColorRed;
  if (status_rank >= 3)
    return GColorOrange;
  return GColorJaegerGreen;
#else
  return GColorBlack;
#endif
}

static GColor color_status_fill(void) {
  if (strcmp(s_status_text, "CRITICAL") == 0)
    return PBL_IF_COLOR_ELSE(GColorMelon, GColorWhite);
  if (strcmp(s_status_text, "WARNING") == 0)
    return color_warning_soft();
  return color_ok_soft();
}

static GColor color_for_status_text(void) {
  if (strcmp(s_status_text, "CRITICAL") == 0)
    return color_for_status(4);
  if (strcmp(s_status_text, "WARNING") == 0)
    return color_for_status(3);
  return color_for_status(1);
}

static int current_slide_offset(void) { return 0; }

static int current_bar_percent(int percent) {
  int clamped = clamp_percent(percent);
  if (s_animation_frame >= ANIMATION_FRAMES)
    return clamped;
  int frame = s_animation_frame;
  int eased = ANIMATION_FRAMES - (((ANIMATION_FRAMES - frame) * (ANIMATION_FRAMES - frame)) / ANIMATION_FRAMES);
  return (clamped * eased) / ANIMATION_FRAMES;
}

static void mark_card_dirty(void) {
  if (s_card_layer != NULL)
    layer_mark_dirty(s_card_layer);
}

static void animation_timer_callback(void *data) {
  s_animation_timer = NULL;
  if (s_animation_frame < ANIMATION_FRAMES || s_is_refreshing) {
    if (s_animation_frame < ANIMATION_FRAMES) {
      s_animation_frame += 1;
    }
    s_refresh_frame = (s_refresh_frame + 1) % ANIMATION_FRAMES;
    mark_card_dirty();
    s_animation_timer = app_timer_register(ANIMATION_INTERVAL_MS, animation_timer_callback, NULL);
  }
}

static void start_card_animation(int direction) {
  if (s_animation_timer != NULL) {
    app_timer_cancel(s_animation_timer);
    s_animation_timer = NULL;
  }

  s_animation_direction = direction < 0 ? -1 : 1;
  s_animation_frame = 0;
  s_refresh_frame = 0;
  mark_card_dirty();
  s_animation_timer = app_timer_register(ANIMATION_INTERVAL_MS, animation_timer_callback, NULL);
}

static void request_quota(void) {
  DictionaryIterator *iter;
  AppMessageResult result = app_message_outbox_begin(&iter);
  if (result != APP_MSG_OK || iter == NULL) {
    snprintf(s_status_text, sizeof(s_status_text), "Send fail");
    s_is_refreshing = false;
    mark_card_dirty();
    return;
  }

  dict_write_uint8(iter, MESSAGE_KEY_REQUEST_QUOTA, 1);
  dict_write_int32(iter, MESSAGE_KEY_PAGE_INDEX, s_page_index);
  result = app_message_outbox_send();
  if (result == APP_MSG_OK) {
    snprintf(s_status_text, sizeof(s_status_text), "Refreshing");
    s_updated_text[0] = '\0';
    s_is_refreshing = true;
    start_card_animation(1);
  } else {
    snprintf(s_status_text, sizeof(s_status_text), "Send fail");
    s_is_refreshing = false;
    mark_card_dirty();
  }
}

static void request_page(int page_index) {
  if (s_page_count <= 0)
    return;
  int direction = page_index >= s_page_index ? 1 : -1;
  if (page_index < 0) {
    page_index = s_page_count - 1;
    direction = -1;
  }
  if (page_index >= s_page_count) {
    page_index = 0;
    direction = 1;
  }

  DictionaryIterator *iter;
  AppMessageResult result = app_message_outbox_begin(&iter);
  if (result != APP_MSG_OK || iter == NULL) {
    snprintf(s_status_text, sizeof(s_status_text), "Page fail");
    mark_card_dirty();
    return;
  }

  s_page_index = page_index;
  dict_write_int32(iter, MESSAGE_KEY_REQUEST_PAGE, page_index);
  result = app_message_outbox_send();
  if (result == APP_MSG_OK) {
    start_card_animation(direction);
  } else {
    snprintf(s_status_text, sizeof(s_status_text), "Send fail");
    mark_card_dirty();
  }
}

static void up_click_handler(ClickRecognizerRef recognizer, void *context) { request_page(s_page_index - 1); }

static void down_click_handler(ClickRecognizerRef recognizer, void *context) { request_page(s_page_index + 1); }

static void select_click_handler(ClickRecognizerRef recognizer, void *context) { request_quota(); }

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

static void draw_text(GContext *ctx, const char *text, GRect box, GFont font, GColor color, GTextAlignment alignment) {
  graphics_context_set_text_color(ctx, color);
  graphics_draw_text(ctx, text, font, box, GTextOverflowModeTrailingEllipsis, alignment, NULL);
}

static void draw_progress_bar(GContext *ctx, GRect frame, int percent, GColor color) {
  graphics_context_set_fill_color(ctx, color_track());
  graphics_fill_rect(ctx, frame, 3, GCornersAll);

  int fill_width = (frame.size.w * current_bar_percent(percent)) / 100;
  if (fill_width < 2 && percent > 0)
    fill_width = 2;
  graphics_context_set_fill_color(ctx, color);
  graphics_fill_rect(ctx, GRect(frame.origin.x, frame.origin.y, fill_width, frame.size.h), 3, GCornersAll);
}

static void draw_status_pill(GContext *ctx, GRect frame) {
  graphics_context_set_fill_color(ctx, color_status_fill());
  graphics_fill_rect(ctx, frame, 6, GCornersAll);
  graphics_context_set_stroke_color(ctx, color_for_status_text());
  graphics_draw_rect(ctx, frame);
  draw_text(ctx, s_status_text, GRect(frame.origin.x, frame.origin.y - 4, frame.size.w, frame.size.h + 4),
            fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD), color_text_primary(), GTextAlignmentCenter);
}

static void draw_refresh_dots(GContext *ctx, GRect bounds) {
  if (!s_is_refreshing)
    return;
  int phase = s_refresh_frame;
  int center_x = bounds.size.w - 23;
  int center_y = 18;

  graphics_context_set_fill_color(ctx, color_text_muted());
  graphics_fill_circle(ctx, GPoint(center_x - 7, center_y), phase < 4 ? 3 : 2);
  graphics_fill_circle(ctx, GPoint(center_x, center_y), phase >= 3 && phase < 7 ? 3 : 2);
  graphics_fill_circle(ctx, GPoint(center_x + 7, center_y), phase >= 6 ? 3 : 2);
}

static void draw_page_dots(GContext *ctx, GRect frame) {
  if (s_page_count <= 1)
    return;

  int dots = s_page_count > 7 ? 7 : s_page_count;
  int total_width = dots * 7 - 3;
  int x = frame.origin.x + (frame.size.w - total_width) / 2;
  int y = frame.origin.y + frame.size.h - 10;

  for (int i = 0; i < dots; i += 1) {
    graphics_context_set_fill_color(ctx, i == s_page_index || (s_page_count > 7 && i == 6 && s_page_index >= 6)
                                             ? color_card_highlight()
                                             : color_text_muted());
    int active = i == s_page_index || (s_page_count > 7 && i == 6 && s_page_index >= 6);
    int radius = active ? 2 : 1;
    if (active && s_animation_frame < ANIMATION_FRAMES / 2)
      radius = 3;
    graphics_fill_circle(ctx, GPoint(x + (i * 7), y), radius);
  }
}

static void card_update_proc(Layer *layer, GContext *ctx) {
  GRect bounds = layer_get_bounds(layer);
  graphics_context_set_fill_color(ctx, color_background());
  graphics_fill_rect(ctx, bounds, 0, GCornerNone);

  const int margin = 10;
  int offset = current_slide_offset();
  int content_x = margin;
  int content_w = bounds.size.w - (margin * 2);
  int y = 8 + offset;

  draw_text(ctx, s_title_text, GRect(content_x, y, content_w - 50, 29), fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
            color_text_primary(), GTextAlignmentLeft);

  char page_text[16];
  if (s_page_count > 0) {
    int display_page_index = s_page_index + 1;
    if (display_page_index < 1)
      display_page_index = 1;
    if (display_page_index > 99)
      display_page_index = 99;
    int display_page_count = s_page_count;
    if (display_page_count < 1)
      display_page_count = 1;
    if (display_page_count > 99)
      display_page_count = 99;
    snprintf(page_text, sizeof(page_text), "%d/%d", display_page_index, display_page_count);
  } else {
    snprintf(page_text, sizeof(page_text), "--");
  }
  draw_text(ctx, page_text, GRect(content_x + content_w - 46, y + 2, 46, 24),
            fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD), color_text_secondary(), GTextAlignmentRight);
  draw_refresh_dots(ctx, bounds);

  y += 26;
  if (s_status_text[0] != '\0') {
    draw_status_pill(ctx, GRect(content_x + content_w - 60, y + 1, 60, 18));
  }

  y += s_status_text[0] == '\0' ? 8 : 26;
  if (s_row_count <= 0) {
    graphics_context_set_fill_color(ctx, color_card_highlight());
    graphics_fill_circle(ctx, GPoint(content_x + (content_w / 2), y + 26), 15);
    graphics_context_set_fill_color(ctx, color_background());
    graphics_fill_circle(ctx, GPoint(content_x + (content_w / 2), y + 26), 9);
    draw_text(ctx, "Quota unavailable", GRect(content_x, y + 48, content_w, 34),
              fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD), color_text_primary(), GTextAlignmentCenter);
    draw_text(ctx, s_reset_text, GRect(content_x, y + 86, content_w, 46), fonts_get_system_font(FONT_KEY_GOTHIC_24),
              color_text_secondary(), GTextAlignmentCenter);
  } else {
    for (int i = 0; i < s_row_count && i < MAX_ROWS; i += 1) {
      QuotaRow *row = &s_rows[i];
      GColor accent = color_for_status(row->status_rank);
      int detail_width = 62;
      draw_text(ctx, row->label, GRect(content_x, y, content_w - detail_width - 8, 22),
                fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD), color_text_primary(), GTextAlignmentLeft);
      draw_text(ctx, row->detail, GRect(content_x + content_w - detail_width, y, detail_width, 22),
                fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD), row->has_bar ? color_text_soft() : color_text_primary(),
                GTextAlignmentRight);
      y += 22;
      if (row->has_bar) {
        draw_progress_bar(ctx, GRect(content_x, y, content_w, 6), row->percent, accent);
        y += 16;
      } else {
        y += 6;
      }
      if (i + 1 < s_row_count && i + 1 < MAX_ROWS) {
        graphics_context_set_stroke_color(ctx, color_card_outline());
        graphics_draw_line(ctx, GPoint(content_x, y), GPoint(content_x + content_w, y));
        y += 3;
      }
    }
    if (s_row_count > MAX_ROWS) {
      draw_text(ctx, "+ more", GRect(content_x, y, content_w, 22), fonts_get_system_font(FONT_KEY_GOTHIC_18),
                color_text_muted(), GTextAlignmentCenter);
    }
  }

  draw_page_dots(ctx, bounds);

  draw_text(ctx, s_reset_text, GRect(content_x, bounds.size.h - 55, content_w, 24),
            fonts_get_system_font(FONT_KEY_GOTHIC_18), color_text_muted(), GTextAlignmentCenter);

  char footer[32];
  if (s_page_count > 0 && s_status_text[0] != '\0') {
    snprintf(footer, sizeof(footer), "%.10s %.12s", s_status_text, s_updated_text);
  } else if (s_page_count > 0) {
    snprintf(footer, sizeof(footer), "%.31s", s_updated_text);
  } else {
    snprintf(footer, sizeof(footer), "%.31s", s_status_text);
  }
  draw_text(ctx, footer, GRect(content_x, bounds.size.h - 33, content_w, 24), fonts_get_system_font(FONT_KEY_GOTHIC_18),
            color_text_secondary(), GTextAlignmentCenter);
}

static void copy_string_tuple(DictionaryIterator *iterator, uint32_t key, char *buffer, size_t buffer_size) {
  Tuple *tuple = dict_find(iterator, key);
  if (tuple == NULL)
    return;
  snprintf(buffer, buffer_size, "%s", tuple->value->cstring);
}

static void read_row(DictionaryIterator *iterator, int row_index) {
  QuotaRow *row = &s_rows[row_index];
  copy_string_tuple(iterator, row_label_key(row_index), row->label, sizeof(row->label));
  copy_string_tuple(iterator, row_detail_key(row_index), row->detail, sizeof(row->detail));

  Tuple *percent_tuple = dict_find(iterator, row_percent_key(row_index));
  row->percent = percent_tuple != NULL ? clamp_percent((int)percent_tuple->value->int32) : 0;

  Tuple *status_tuple = dict_find(iterator, row_status_key(row_index));
  row->status_rank = status_tuple != NULL ? (int)status_tuple->value->int32 : 1;

  Tuple *has_bar_tuple = dict_find(iterator, row_has_bar_key(row_index));
  row->has_bar = has_bar_tuple != NULL && has_bar_tuple->value->int32 != 0;
}

static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
  Tuple *error_tuple = dict_find(iterator, MESSAGE_KEY_ERROR);
  if (error_tuple != NULL) {
    s_is_refreshing = false;
    s_row_count = 0;
    s_page_index = 0;
    s_page_count = 0;
    snprintf(s_title_text, sizeof(s_title_text), "Pebblexus");
    snprintf(s_reset_text, sizeof(s_reset_text), "%s", error_tuple->value->cstring);
    snprintf(s_status_text, sizeof(s_status_text), "Select retries");
    s_updated_text[0] = '\0';
    s_animation_frame = ANIMATION_FRAMES;
    mark_card_dirty();
    return;
  }

  s_is_refreshing = false;
  copy_string_tuple(iterator, MESSAGE_KEY_QUOTA_TITLE, s_title_text, sizeof(s_title_text));
  copy_string_tuple(iterator, MESSAGE_KEY_QUOTA_LABEL, s_group_text, sizeof(s_group_text));
  copy_string_tuple(iterator, MESSAGE_KEY_QUOTA_RESET, s_reset_text, sizeof(s_reset_text));
  copy_string_tuple(iterator, MESSAGE_KEY_QUOTA_STATUS, s_status_text, sizeof(s_status_text));
  copy_string_tuple(iterator, MESSAGE_KEY_UPDATED_AT, s_updated_text, sizeof(s_updated_text));

  Tuple *page_index_tuple = dict_find(iterator, MESSAGE_KEY_PAGE_INDEX);
  if (page_index_tuple != NULL)
    s_page_index = (int)page_index_tuple->value->int32;

  Tuple *page_count_tuple = dict_find(iterator, MESSAGE_KEY_PAGE_COUNT);
  if (page_count_tuple != NULL)
    s_page_count = (int)page_count_tuple->value->int32;
  if (s_page_count < 0)
    s_page_count = 0;
  if (s_page_index >= s_page_count)
    s_page_index = s_page_count - 1;
  if (s_page_index < 0)
    s_page_index = 0;

  Tuple *row_count_tuple = dict_find(iterator, MESSAGE_KEY_ROW_COUNT);
  int row_count = row_count_tuple != NULL ? (int)row_count_tuple->value->int32 : 0;
  if (row_count < 0)
    row_count = 0;
  s_row_count = row_count;

  int rows_to_read = row_count > MAX_ROWS ? MAX_ROWS : row_count;
  for (int i = 0; i < rows_to_read; i += 1) {
    s_rows[i].label[0] = '\0';
    s_rows[i].detail[0] = '\0';
    s_rows[i].percent = 0;
    s_rows[i].status_rank = 1;
    s_rows[i].has_bar = false;
    read_row(iterator, i);
  }

  start_card_animation(s_animation_direction);
}

static void inbox_dropped_callback(AppMessageResult reason, void *context) {
  s_is_refreshing = false;
  snprintf(s_status_text, sizeof(s_status_text), "Dropped");
  mark_card_dirty();
}

static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
  s_is_refreshing = false;
  snprintf(s_status_text, sizeof(s_status_text), "No phone");
  mark_card_dirty();
}

static void main_window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  s_card_layer = layer_create(bounds);
  layer_set_update_proc(s_card_layer, card_update_proc);
  layer_add_child(window_layer, s_card_layer);
}

static void main_window_unload(Window *window) {
  if (s_animation_timer != NULL) {
    app_timer_cancel(s_animation_timer);
    s_animation_timer = NULL;
  }
  layer_destroy(s_card_layer);
}

static void init(void) {
  s_main_window = window_create();
  window_set_background_color(s_main_window, color_background());
  window_set_click_config_provider(s_main_window, click_config_provider);
  window_set_window_handlers(s_main_window, (WindowHandlers){.load = main_window_load, .unload = main_window_unload});

  app_message_register_inbox_received(inbox_received_callback);
  app_message_register_inbox_dropped(inbox_dropped_callback);
  app_message_register_outbox_failed(outbox_failed_callback);
  app_message_open(2048, 256);

  window_stack_push(s_main_window, true);
  request_quota();
}

static void deinit(void) { window_destroy(s_main_window); }

int main(void) {
  init();
  app_event_loop();
  deinit();
}
