/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "system_theme.h"

#include "applib/fonts/fonts.h"
#include "apps/system_apps/settings/settings_notifications_private.h"
#include "process_management/process_manager.h"
#include "services/common/analytics/analytics.h"
#include "shell/prefs.h"
#include "syscall/syscall_internal.h"
#include "system/passert.h"
#include "util/size.h"

#include <string.h>

typedef struct SystemThemeTextStyle {
  const char *fonts[TextStyleFontCount];
} SystemThemeTextStyle;

////////////////////
// Themes

static const SystemThemeTextStyle s_text_styles[NumPreferredContentSizes] = {
  [PreferredContentSizeSmall] = {
    .fonts = {
      [TextStyleFont_Header] = FONT_KEY_GOTHIC_18_BOLD,
#if !RECOVERY_FW
      [TextStyleFont_Title] = FONT_KEY_GOTHIC_18_BOLD,
      [TextStyleFont_Body] = FONT_KEY_GOTHIC_18,
#endif
      [TextStyleFont_Subtitle] = FONT_KEY_GOTHIC_18_BOLD,
      [TextStyleFont_Caption] = FONT_KEY_GOTHIC_14,
      [TextStyleFont_Footer] = FONT_KEY_GOTHIC_14,
      //! @note this is the same as the Title key (as that's what it's cloned from) until Small
      //!       is designed
      [TextStyleFont_MenuCellTitle] = FONT_KEY_GOTHIC_18_BOLD,
      //! @note this is the same as Medium until Small is designed
      [TextStyleFont_MenuCellSubtitle] = FONT_KEY_GOTHIC_18,
#if !RECOVERY_FW
      //! @note this is the same as Medium until Small is designed
      [TextStyleFont_TimeHeaderNumbers] = FONT_KEY_LECO_20_BOLD_NUMBERS,
#endif
      //! @note this is the same as Medium until Small is designed
      [TextStyleFont_TimeHeaderWords] = FONT_KEY_GOTHIC_14_BOLD,
      //! @note this is the same as Medium until Small is designed
      [TextStyleFont_PinSubtitle] = FONT_KEY_GOTHIC_18,
      //! @note this is the same as Medium until Small is designed
      [TextStyleFont_ParagraphHeader] = FONT_KEY_GOTHIC_14,
    },
  },
  [PreferredContentSizeMedium] = {
    .fonts = {
      [TextStyleFont_Header] = FONT_KEY_GOTHIC_18_BOLD,
#if !RECOVERY_FW
      [TextStyleFont_Title] = FONT_KEY_GOTHIC_24_BOLD,
      [TextStyleFont_Body] = FONT_KEY_GOTHIC_24_BOLD,
#endif
      [TextStyleFont_Subtitle] = FONT_KEY_GOTHIC_24_BOLD,
      [TextStyleFont_Caption] = FONT_KEY_GOTHIC_14,
      [TextStyleFont_Footer] = FONT_KEY_GOTHIC_18,
      [TextStyleFont_MenuCellTitle] = FONT_KEY_GOTHIC_24_BOLD,
      [TextStyleFont_MenuCellSubtitle] = FONT_KEY_GOTHIC_18,
#if !RECOVERY_FW
      [TextStyleFont_TimeHeaderNumbers] = FONT_KEY_LECO_20_BOLD_NUMBERS,
#endif
      [TextStyleFont_TimeHeaderWords] = FONT_KEY_GOTHIC_14_BOLD,
      [TextStyleFont_PinSubtitle] = FONT_KEY_GOTHIC_18,
      [TextStyleFont_ParagraphHeader] = FONT_KEY_GOTHIC_14,
    },
  },
  [PreferredContentSizeLarge] = {
    .fonts = {
      [TextStyleFont_Header] = FONT_KEY_GOTHIC_24_BOLD,
#if !RECOVERY_FW
      [TextStyleFont_Title] = FONT_KEY_GOTHIC_28_BOLD,
      [TextStyleFont_Body] = FONT_KEY_GOTHIC_28,
#endif
      [TextStyleFont_Subtitle] = FONT_KEY_GOTHIC_28,
      [TextStyleFont_Caption] = FONT_KEY_GOTHIC_18,
      [TextStyleFont_Footer] = FONT_KEY_GOTHIC_18,
      [TextStyleFont_MenuCellTitle] = FONT_KEY_GOTHIC_28,
      [TextStyleFont_MenuCellSubtitle] = FONT_KEY_GOTHIC_24_BOLD,
#if !RECOVERY_FW
      [TextStyleFont_TimeHeaderNumbers] = FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM,
#endif
      [TextStyleFont_TimeHeaderWords] = FONT_KEY_GOTHIC_18_BOLD,
      [TextStyleFont_PinSubtitle] = FONT_KEY_GOTHIC_24,
      [TextStyleFont_ParagraphHeader] = FONT_KEY_GOTHIC_18_BOLD,
    },
  },
  [PreferredContentSizeExtraLarge] = {
    .fonts = {
      [TextStyleFont_Header] = FONT_KEY_GOTHIC_28_BOLD,
#if !RECOVERY_FW
      [TextStyleFont_Title] = FONT_KEY_GOTHIC_36_BOLD,
      [TextStyleFont_Body] = FONT_KEY_GOTHIC_36,
#endif
      //! @note this is the same as Large until ExtraLarge is designed
      [TextStyleFont_Subtitle] = FONT_KEY_GOTHIC_28,
      [TextStyleFont_Caption] = FONT_KEY_GOTHIC_24,
      [TextStyleFont_Footer] = FONT_KEY_GOTHIC_24,
      //! @note this is the same as Large until ExtraLarge is designed
      [TextStyleFont_MenuCellTitle] = FONT_KEY_GOTHIC_28,
      //! @note this is the same as Large until ExtraLarge is designed
      [TextStyleFont_MenuCellSubtitle] = FONT_KEY_GOTHIC_24_BOLD,
#if !RECOVERY_FW
      //! @note this is the same as Large until ExtraLarge is designed
      [TextStyleFont_TimeHeaderNumbers] = FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM,
#endif
      //! @note this is the same as Large until ExtraLarge is designed
      [TextStyleFont_TimeHeaderWords] = FONT_KEY_GOTHIC_18_BOLD,
      //! @note this is the same as Large until ExtraLarge is designed
      [TextStyleFont_PinSubtitle] = FONT_KEY_GOTHIC_24,
      [TextStyleFont_ParagraphHeader] = FONT_KEY_GOTHIC_18_BOLD,
    },
  },
};

////////////////////
// Helpers

static const char *prv_get_font_for_size(PreferredContentSize content_size, TextStyleFont font) {
  if (content_size >= NumPreferredContentSizes) {
    PBL_LOG(LOG_LEVEL_ERROR, "Requested a content size that is out of bounds (%d)", content_size);
    goto fail;
  } else if (font >= TextStyleFontCount) {
    PBL_LOG(LOG_LEVEL_ERROR, "Requested a style font that is out of bounds (%d)", font);
    goto fail;
  }
  return s_text_styles[content_size].fonts[font];
fail:
  PRIVILEGE_WAS_ELEVATED ? syscall_failed() : WTF;
}

////////////////////
// Public API

// *** WARNING WARNING WARNING ***
// Be very careful when modifying this syscall. It currently returns a pointer
// to constant data in flash, which unprivileged apps are allowed to read. But
// if the data pointed to is ever moved to RAM, the syscall will need to be
// changed to copy the data into a caller-provided buffer. Unprivileged apps
// are not allowed to read kernel RAM, so they will fault if they attempt to
// dereference a pointer into kernel RAM.
DEFINE_SYSCALL(const char *, system_theme_get_font_key, TextStyleFont font) {
  return prv_get_font_for_size(system_theme_get_content_size(), font);
}

// *** WARNING WARNING WARNING ***
// Be very careful when modifying this syscall. It currently returns a pointer
// to constant data in flash, which unprivileged apps are allowed to read.
DEFINE_SYSCALL(const char *, system_theme_get_font_key_for_size, PreferredContentSize content_size,
               TextStyleFont font) {
  const PreferredContentSize size_on_runtime_platform =
      system_theme_convert_host_content_size_to_runtime_platform(content_size);
  return prv_get_font_for_size(size_on_runtime_platform, font);
}

GFont system_theme_get_font(TextStyleFont font) {
  return fonts_get_system_font(system_theme_get_font_key(font));
}

GFont system_theme_get_font_for_size(PreferredContentSize size, TextStyleFont font) {
  return fonts_get_system_font(system_theme_get_font_key_for_size(size, font));
}

GFont system_theme_get_font_for_default_size(TextStyleFont font) {
  return fonts_get_system_font(system_theme_get_font_key_for_size(PreferredContentSizeDefault,
                                                                  font));
}

static const PreferredContentSize s_platform_default_content_sizes[] = {
  [PlatformTypeAplite] = PreferredContentSizeMedium,
  [PlatformTypeBasalt] = PreferredContentSizeMedium,
  [PlatformTypeChalk] = PreferredContentSizeMedium,
  [PlatformTypeDiorite] = PreferredContentSizeMedium,
  [PlatformTypeEmery] = PreferredContentSizeLarge,
};

T_STATIC PreferredContentSize prv_convert_content_size_between_platforms(PreferredContentSize size,
                                                                         PlatformType from_platform,
                                                                         PlatformType to_platform) {
  const size_t num_platform_default_content_sizes = ARRAY_LENGTH(s_platform_default_content_sizes);
  PBL_ASSERTN(from_platform < num_platform_default_content_sizes);
  PBL_ASSERTN(to_platform < num_platform_default_content_sizes);

  const PreferredContentSize from_platform_default_size =
      s_platform_default_content_sizes[from_platform];
  const PreferredContentSize to_platform_default_size =
      s_platform_default_content_sizes[to_platform];
  const int resulting_size = size + (to_platform_default_size - from_platform_default_size);
  return (PreferredContentSize)CLIP(resulting_size, 0, (NumPreferredContentSizes - 1));
}

PreferredContentSize system_theme_get_default_content_size_for_runtime_platform(void) {
  const PlatformType runtime_platform = process_manager_current_platform();
  return prv_convert_content_size_between_platforms(PreferredContentSizeDefault,
                                                    PBL_PLATFORM_TYPE_CURRENT,
                                                    runtime_platform);
}

PreferredContentSize system_theme_convert_host_content_size_to_runtime_platform(
    PreferredContentSize size) {
  const PlatformType runtime_platform = process_manager_current_platform();
  return prv_convert_content_size_between_platforms(size, PBL_PLATFORM_TYPE_CURRENT,
                                                    runtime_platform);
}

void analytics_external_collect_system_theme_settings(void) {
  const SettingsContentSize content_size =
      settings_content_size_from_preferred_size(system_theme_get_content_size());
  analytics_set(ANALYTICS_DEVICE_METRIC_SYSTEM_THEME_TEXT_STYLE, content_size,
                AnalyticsClient_System);
}
