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

#include "timeline_pins_demo.h"

#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/option_menu_window.h"
#include "apps/system_app_ids.h"
#include "apps/system_apps/timeline/timeline.h"
#include "process_management/app_manager.h"
#include "services/common/i18n/i18n.h"
#include "services/normal/activity/activity_insights.h"
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/notifications/notification_storage.h"
#include "services/normal/timeline/calendar_layout.h"
#include "services/normal/timeline/event.h"
#include "services/normal/timeline/health_layout.h"
#include "services/normal/timeline/weather_layout.h"
#include "util/size.h"

#include <sys/cdefs.h>

#define StringListLiteral(str) { \
  .serialized_byte_length = ARRAY_LENGTH(str) - 1, \
  .data = str, \
} \

const char *timeline_demo_strings[TimelinePinsDemoCount] = {
    [TimelinePinsDemo_Default] = "Default Pins",
    [TimelinePinsDemo_OneDayAway] = "Pins One Day Away",
    [TimelinePinsDemo_OngoingEvent] = "Ongoing Event",
    [TimelinePinsDemo_Notifications] = "Notifications",
    [TimelinePinsDemo_TodayAndTomorrow] = "Today & Tomorrow",
};

static void prv_set_timeline_icon(AttributeList *list, TimelineResourceId timeline_res,
                                  TimelineResourceId card_res) {
  attribute_list_add_uint32(list, AttributeIdIconTiny, timeline_res);
  attribute_list_add_uint32(list, AttributeIdIconSmall, card_res ?: timeline_res);
  attribute_list_add_uint32(list, AttributeIdIconLarge, card_res ?: timeline_res);
}

#define ARRAY_RAND(arr) arr[rand() % ARRAY_LENGTH(arr)]

static void prv_add_notification(int32_t delta_time_s) {
  const time_t now = rtc_get_time();

  AttributeList list = {};

  TimelineResourceId icon_resources[] = {
      TIMELINE_RESOURCE_NOTIFICATION_FACEBOOK_MESSENGER,
      TIMELINE_RESOURCE_NOTIFICATION_FACEBOOK,
      TIMELINE_RESOURCE_NOTIFICATION_MAILBOX,
      TIMELINE_RESOURCE_NOTIFICATION_GENERIC,
  };
  char *titles[] = {
      "Angela Tam", "Liron Damir", "Heiko Behrens", "Kevin Conley", "Matt Hungerford",
  };
  char *bodies[] = {
      "Late again? Can you be on time ever? Seriosly? Dude!!!",
      "Late again. Sorry, I'll be there a few minutes. Meanwhile, I am just texting long messages.",
      "What's up for lunch?",
      "\xF0\x9F\x98\x83 \xF0\x9F\x92\xA9",
  };

  prv_set_timeline_icon(&list, ARRAY_RAND(icon_resources), 0);
  attribute_list_add_cstring(&list, AttributeIdTitle, ARRAY_RAND(titles));
  attribute_list_add_cstring(&list, AttributeIdBody, ARRAY_RAND(bodies));
  attribute_list_add_uint32(&list, AttributeIdLastUpdated, now);
  TimelineItem *item = timeline_item_create_with_attributes(now + delta_time_s, 0,
                                                            TimelineItemTypeNotification,
                                                            LayoutIdNotification,
                                                            &list, NULL);
  notification_storage_store(item);
  timeline_item_destroy(item);
  attribute_list_destroy_list(&list);
}

static void prv_add_weather_pin_with_params(int32_t delta_time_s, bool has_timestamp,
                                            bool has_short_title, bool has_short_subtitle) {
  const time_t now = rtc_get_time();

  AttributeList list = {};
  prv_set_timeline_icon(&list, TIMELINE_RESOURCE_TIMELINE_WEATHER, 0);
  attribute_list_add_cstring(&list, AttributeIdTitle, "SUNRISE");
  attribute_list_add_cstring(&list, AttributeIdSubtitle, "11°/6°");
  attribute_list_add_cstring(&list, AttributeIdLocationName,
                             "SAN LOUIS OBISPO\n"
                             "CALIFORNIA, USA");
  attribute_list_add_cstring(&list, AttributeIdBody,
                             "Cloudy with rain and snow. High 1C. Winds light and variable. "
                             "Chance of precip 100%. 3-7cm of snow expected.");
  attribute_list_add_uint32(&list, AttributeIdLastUpdated, now);
  if (!has_timestamp) {
    attribute_list_add_uint8(&list, AttributeIdDisplayTime, WeatherTimeType_None);
  }
  if (has_short_title) {
    attribute_list_add_cstring(&list, AttributeIdShortTitle, "Sunrise");
  }
  if (has_short_subtitle) {
    attribute_list_add_cstring(&list, AttributeIdShortSubtitle, "Cloudy with rain and snow");
  }
  TimelineItem *item = timeline_item_create_with_attributes(now + delta_time_s, 0,
                                                            TimelineItemTypePin, LayoutIdWeather,
                                                            &list, NULL);

  pin_db_insert_item_without_event(item);
  timeline_item_destroy(item);
  attribute_list_destroy_list(&list);
}

static void prv_add_weather_pin(int32_t delta_time_s) {
  prv_add_weather_pin_with_params(delta_time_s, true, false, false);
}

static void prv_add_sports_pin(int32_t delta_time_s, GColor secondary_color, bool is_ingame,
                               bool has_broadcaster) {
  const time_t now = rtc_get_time();

  AttributeList list = {};
  prv_set_timeline_icon(&list, TIMELINE_RESOURCE_TIMELINE_SPORTS, 0);
  attribute_list_add_uint8(&list, AttributeIdSecondaryColor, secondary_color.argb);
  attribute_list_add_cstring(&list, AttributeIdTitle, "Avalanche at Sharks");
  attribute_list_add_cstring(&list, AttributeIdSubtitle, "Q4 • 1:25");
  attribute_list_add_uint32(&list, AttributeIdSportsGameState, is_ingame ? 1 : 0);
  attribute_list_add_cstring(&list, AttributeIdNameAway, "GSW");
  attribute_list_add_cstring(&list, AttributeIdRecordAway, "114-152");
  attribute_list_add_cstring(&list, AttributeIdScoreAway, "86");
  attribute_list_add_cstring(&list, AttributeIdNameHome, "CHI");
  attribute_list_add_cstring(&list, AttributeIdRecordHome, "110-15");
  attribute_list_add_cstring(&list, AttributeIdScoreHome, "103");
  if (has_broadcaster) {
    attribute_list_add_cstring(&list, AttributeIdBroadcaster, "ABC");
  }
  attribute_list_add_cstring(&list, AttributeIdBody,
                             "01:45\nJames 3pt Shot: Missed\n"
                             "03:15 | 22-29\nLeonard Free Throw 2 of 2 (8PTS)");
  attribute_list_add_uint32(&list, AttributeIdLastUpdated, now);
  TimelineItem *item = timeline_item_create_with_attributes(now + delta_time_s, 0,
                                                            TimelineItemTypePin, LayoutIdSports,
                                                            &list, NULL);

  pin_db_insert_item_without_event(item);
  timeline_item_destroy(item);
  attribute_list_destroy_list(&list);
}

static void prv_add_calendar_pin(int32_t delta_time_s, int32_t duration_m, bool is_all_day,
                                 bool recurring, TimelineResourceId icon,
                                 TimelineResourceId card_icon) {
  const time_t now = rtc_get_time();
  time_t target = now + delta_time_s;
  if (is_all_day) {
    target = time_util_get_midnight_of(target);
    duration_m = ((duration_m - 1) / MINUTES_PER_DAY + 1) * MINUTES_PER_DAY;
  }

  AttributeList list = {};
  prv_set_timeline_icon(&list, icon ?: TIMELINE_RESOURCE_TIMELINE_CALENDAR, card_icon);
  if (recurring) {
    attribute_list_add_uint8(&list, AttributeIdDisplayRecurring, CalendarRecurringTypeRecurring);
  }
  attribute_list_add_cstring(&list, AttributeIdTitle, "Weekly All Hands design stuff");
  attribute_list_add_cstring(&list, AttributeIdLocationName, "ConfRM-HIGH_Video Room");
  static StringList headings = StringListLiteral("Description\0Attendees\0Organizer");
  static StringList paragraphs = StringListLiteral(
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt "
      "ut labore et dolore magna aliqua.\0"
      "Ryan Case\nBrad Murray\0Sarah Otten");
  attribute_list_add_string_list(&list, AttributeIdHeadings, &headings);
  attribute_list_add_string_list(&list, AttributeIdParagraphs, &paragraphs);
  attribute_list_add_cstring(&list, AttributeIdBody,
                             "Topics for the week can be found here: "
                             "http://docs.google.com/u/1/#inbox/14b9fa5f872ebbc6\n\n"
                             "Will email before if we need to cancel");
  attribute_list_add_uint32(&list, AttributeIdLastUpdated, now);
  TimelineItem *item = timeline_item_create_with_attributes(target, duration_m,
                                                            TimelineItemTypePin, LayoutIdCalendar,
                                                            &list, NULL);

  pin_db_insert_item_without_event(item);
  timeline_item_destroy(item);
  attribute_list_destroy_list(&list);
}

static void prv_add_generic_pin(int32_t delta_time_s, bool has_subtitle) {
  const time_t now = rtc_get_time();

  AttributeList list = {};
  prv_set_timeline_icon(&list, TIMELINE_RESOURCE_NOTIFICATION_FLAG, 0);
  attribute_list_add_cstring(&list, AttributeIdTitle, "Delfina Pizza");
  if (has_subtitle) {
    attribute_list_add_cstring(&list, AttributeIdSubtitle, "Open Table Reservation");
  }
  attribute_list_add_cstring(&list, AttributeIdLocationName,
                             "145 Williams John\n"
                             "Palo Alto");
  static StringList headings = StringListLiteral("Attendees\0Organizer");
  static StringList paragraphs = StringListLiteral("Ryan Case\nBrad Murray\0Sarah Otten");
  attribute_list_add_string_list(&list, AttributeIdHeadings, &headings);
  attribute_list_add_string_list(&list, AttributeIdParagraphs, &paragraphs);
  attribute_list_add_cstring(&list, AttributeIdBody, "Body message");
  attribute_list_add_uint32(&list, AttributeIdLastUpdated, now);
  TimelineItem *item = timeline_item_create_with_attributes(now + delta_time_s, 0,
                                                            TimelineItemTypePin, LayoutIdGeneric,
                                                            &list, NULL);

  pin_db_insert_item_without_event(item);
  timeline_item_destroy(item);
  attribute_list_destroy_list(&list);
}

static void prv_add_activity_session_pin(int32_t delta_time_s, int32_t duration_m) {
  const time_t now = rtc_get_time();

  AttributeList list = {};
  attribute_list_add_uint32(&list, AttributeIdIconPin, TIMELINE_RESOURCE_RUN);
  attribute_list_add_uint32(&list, AttributeIdIconTiny, TIMELINE_RESOURCE_PACE);
  attribute_list_add_uint8(&list, AttributeIdHealthInsightType,
                           ActivityInsightType_ActivitySessionRun);
  attribute_list_add_cstring(&list, AttributeIdTitle, "3.3 Mile run");
  attribute_list_add_cstring(&list, AttributeIdSubtitle, "30M of activity");
  attribute_list_add_uint32(&list, AttributeIdLastUpdated, now);

  uint8_t buffer[Uint32ListSize(ActivitySessionMetricCount)];
  Uint32List *icons = (Uint32List *)buffer;
  icons->num_values = ActivitySessionMetricCount,
  icons->values[0] = TIMELINE_RESOURCE_PACE;
  icons->values[1] = TIMELINE_RESOURCE_DURATION;
  icons->values[2] = TIMELINE_RESOURCE_CALORIES;
  icons->values[3] = TIMELINE_RESOURCE_DISTANCE;
  static StringList names = StringListLiteral("Pace\0Run duration\0Calories burned\0Distance");
  static StringList values = StringListLiteral("7:45\0" "30M\0" "8384\0" "3.3 miles");
  attribute_list_add_string_list(&list, AttributeIdMetricNames, &names);
  attribute_list_add_string_list(&list, AttributeIdMetricValues, &values);
  attribute_list_add_uint32_list(&list, AttributeIdMetricIcons, icons);

  TimelineItem *item = timeline_item_create_with_attributes(now + delta_time_s, duration_m,
                                                            TimelineItemTypePin, LayoutIdHealth,
                                                            &list, NULL);

  pin_db_insert_item_without_event(item);
  timeline_item_destroy(item);
  attribute_list_destroy_list(&list);
}

static void prv_launch_timeline(void) {
  app_manager_put_launch_app_event(&(AppLaunchEventConfig) { .id = APP_ID_TIMELINE });
}

static void prv_launch_notifications(void) {
  app_manager_put_launch_app_event(&(AppLaunchEventConfig) { .id = APP_ID_NOTIFICATIONS });
}

void timeline_pins_demo_add_pins(TimelinePinsDemoSet pin_set) {
  const bool has_broadcaster = true;
  const bool has_short_subtitle = true;
  const bool has_short_title = true;
  const bool has_subtitle = true;
  const bool has_timestamp = true;
  const bool is_all_day = true;
  const bool is_ingame = true;
  const bool recurring = true;
  switch (pin_set) {
    case TimelinePinsDemo_Default: {
      // Past pins (deprecated)
      prv_add_calendar_pin(-4 * 60 * 60, 60, !is_all_day, !recurring, 0, 0);
      prv_add_generic_pin(-6 * 60 * 60, !has_subtitle);
      prv_add_generic_pin(-5 * 60 * 60, has_subtitle);
      prv_add_sports_pin(-4 * 60 * 60, GColorBlack, is_ingame, !has_broadcaster);
      prv_add_activity_session_pin(-3 * 60 * 60, 30);
      prv_add_weather_pin_with_params(-2 * 60 * 60, !has_timestamp, !has_short_title,
                                      !has_short_subtitle);
      prv_add_weather_pin_with_params(-60 * 60, has_timestamp, has_short_title, has_short_subtitle);

      // Peek pins
      prv_add_calendar_pin(5 * 60, 60, !is_all_day, recurring, 0, 0);
      prv_add_weather_pin(10 * 60 + 15);
      prv_add_sports_pin(10 * 60 + 17, GColorWhite, !is_ingame, has_broadcaster);

      // Future pins
      prv_add_calendar_pin(30 * 60, 3 * 24 * 60, is_all_day, !recurring,
                           TIMELINE_RESOURCE_SCHEDULED_EVENT, 0);
      prv_add_calendar_pin(60 * 60, 3 * 24 * 60, is_all_day, recurring,
                           TIMELINE_RESOURCE_RADIO_SHOW, TIMELINE_RESOURCE_STOCKS_EVENT);
      prv_add_calendar_pin(90 * 60, 60, !is_all_day, !recurring, 0, 0);
      prv_add_weather_pin(50 * 60);
      prv_add_sports_pin(2 * 60 * 60, GColorWhite, !is_ingame, has_broadcaster);
      prv_add_sports_pin(3 * 60 * 60, GColorWhite, is_ingame, has_broadcaster);
      prv_add_calendar_pin(4 * 60 * 60, 60, !is_all_day, recurring, 0, 0);
      prv_add_calendar_pin(6 * 60 * 60, 60, !is_all_day, recurring, 0, 0);
      prv_add_generic_pin(7 * 60 * 60, has_subtitle);
      prv_add_generic_pin(8 * 60 * 60, !has_subtitle);
      prv_add_weather_pin(1 * 24 * 60 * 60);
      prv_add_weather_pin(2 * 24 * 60 * 60);
      prv_add_weather_pin(3 * 24 * 60 * 60);
      // Fallthrough
    }
    case TimelinePinsDemo_OneDayAway:
      prv_add_weather_pin(-2 * 24 * 60 * 60);
      prv_add_weather_pin(2 * 24 * 60 * 60);
      goto timeline;
    case TimelinePinsDemo_OngoingEvent: {
      prv_add_calendar_pin(-(3 * SECONDS_PER_DAY) / 2, 3 * MINUTES_PER_DAY, is_all_day,
                           !recurring, 0, 0);
      goto timeline;
    }
    case TimelinePinsDemo_TodayAndTomorrow:
      prv_add_generic_pin(-24 * 60 * 60, has_subtitle);
      prv_add_weather_pin(-60 * 60);
      prv_add_weather_pin(60 * 60);
      prv_add_generic_pin(24 * 60 * 60, has_subtitle);
      goto timeline;
    case TimelinePinsDemo_Notifications:
      prv_add_notification(-60 * 60 * 24);
      prv_add_notification(-60 * 60);
      prv_add_notification(-60 * 30);
      prv_add_notification(-60 * 5);
      prv_add_notification(-60);
      prv_add_notification(-1);
      goto notifications;
    default:
      // do nothing
      break;
  }
  return;

timeline:
  timeline_event_refresh();
  prv_launch_timeline();
  return;

notifications:
  prv_launch_notifications();
  return;
}

static void prv_menu_select(OptionMenu *option_menu, int selection, void *context) {
  timeline_pins_demo_add_pins(selection);
  app_window_stack_pop(true);
}

static uint16_t prv_menu_get_num_rows(OptionMenu *option_menu, void *context) {
  return ARRAY_LENGTH(timeline_demo_strings);
}

static void prv_menu_draw_row(OptionMenu *option_menu, GContext *ctx, const Layer *cell_layer,
                              const GRect *text_frame, uint32_t row, bool selected, void *context) {
  option_menu_system_draw_row(option_menu, ctx, cell_layer, text_frame,
                              timeline_demo_strings[row], selected, context);
}

static void prv_menu_unload(OptionMenu *option_menu, void *context) {
  option_menu_destroy(option_menu);
}

static void prv_handle_init(void) {
  // add CFLAGS="-DTIMELINE_PIN_SET=OneDayAway" before ./waf configure to skip menu
#ifdef TIMELINE_PIN_SET
  #define PREFIX_PIN_SET(set) (__CONCAT(TimelinePinsDemo, set))
  timeline_pins_demo_add_pins(PREFIX_PIN_SET(TIMELINE_PIN_SET));
#else
  OptionMenu *option_menu = option_menu_create();

  const OptionMenuConfig config = {
    .title = "Select Type of Pins to Add",
    .choice = OPTION_MENU_CHOICE_NONE,
    .status_colors = { GColorDarkGray, GColorWhite },
    .highlight_colors = { GColorLightGray, GColorBlack },
  };
  option_menu_configure(option_menu, &config);
  option_menu_set_callbacks(option_menu, &(OptionMenuCallbacks) {
    .select = prv_menu_select,
    .get_num_rows = prv_menu_get_num_rows,
    .draw_row = prv_menu_draw_row,
    .unload = prv_menu_unload,
  }, option_menu);

  const bool animated = true;
  app_window_stack_push(&option_menu->window, animated);
#endif
}

static void prv_main(void) {
  prv_handle_init();
  app_event_loop();
}

const PebbleProcessMd* timeline_pins_get_app_info(void) {
  static const PebbleProcessMdSystem s_app_info = {
      .common = {
          .main_func = prv_main,
          // UUID: c53a79d7-3472-4062-a7d0-39ada9bfa415
          .uuid = {0xc5, 0x3a, 0x79, 0xd7, 0x34, 0x72, 0x40, 0x62,
                   0xa7, 0xd0, 0x39, 0xad, 0xa9, 0xbf, 0xa4, 0x15},
      },
      .name = "Timeline Pins Demo",
  };
  return (const PebbleProcessMd*) &s_app_info;
}
