/*
 * 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 "clar.h"
#include "fixtures/load_test_resources.h"

// FW headers
#include "applib/graphics/text_resources.h"
#include "resource/resource.h"
#include "resource/resource_ids.auto.h"
#include "resource/system_resource.h"
#include "util/size.h"

// Fakes
#include "fake_app_manager.h"

// Stubs
#include "stubs_analytics.h"
#include "stubs_bootbits.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_passert.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_queue.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_syscalls.h"
#include "stubs_prompt.h"
#include "stubs_task_watchdog.h"
#include "stubs_memory_layout.h"

#define WILDCARD_CODEPOINT 0x25AF

static FontCache s_font_cache;
static FontInfo s_font_info;

#define FONT_COMPRESSION_FIXTURE_PATH "font_compression"

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

static uint8_t glyph_get_size_bytes(const GlyphData *glyph) {
  return ((glyph->header.width_px * glyph->header.height_px) + (8 - 1)) / 8;
}

void test_text_resources__initialize(void) {
  fake_spi_flash_init(0, 0x1000000);
  pfs_init(false);
  pfs_format(true /* write erase headers */);
  load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME, false /* is_next */);
  load_resource_fixture_on_pfs(RESOURCES_FIXTURE_PATH, CHINESE_FIXTURE_NAME, "lang");
  //cl_assert(resource_has_valid_system_resources());

  memset(&s_font_info, 0, sizeof(s_font_info));
  memset(&s_font_cache, 0, sizeof(s_font_cache));

  FontCache *font_cache = &s_font_cache;
  memset(font_cache->cache_keys, 0, sizeof(font_cache->cache_keys));
  memset(font_cache->cache_data, 0, sizeof(font_cache->cache_data));
  keyed_circular_cache_init(&font_cache->line_cache, font_cache->cache_keys,
                            font_cache->cache_data, sizeof(LineCacheData), LINE_CACHE_SIZE);

  resource_init();
}

void test_text_resources__cleanup(void) {

}

void test_text_resources__init_font(void) {
  uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
  cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
  cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3);
  cl_assert_equal_i(s_font_info.base.md.wildcard_codepoint, WILDCARD_CODEPOINT);
  cl_assert_equal_i(s_font_info.base.md.codepoint_bytes, 2);
}

void test_text_resources__horiz_advance(void) {
  uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
  cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));

  int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info);
  cl_assert(horiz_advance != 0);
  cl_assert_equal_i(horiz_advance,
      text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info));
  cl_assert_equal_i(horiz_advance,
      text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info));
}

void test_text_resources__horiz_advance_multiple(void) {
  uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
  cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));

  int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info);
  cl_assert(horiz_advance != 0);
  cl_assert_equal_i(horiz_advance,
      text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info));

  horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'b', &s_font_info);
  cl_assert(horiz_advance != 0);
  cl_assert_equal_i(horiz_advance,
     text_resources_get_glyph_horiz_advance(&s_font_cache, 'b', &s_font_info));

  horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'c', &s_font_info);
  cl_assert(horiz_advance != 0);
  cl_assert_equal_i(horiz_advance,
     text_resources_get_glyph_horiz_advance(&s_font_cache, 'c', &s_font_info));
}

void test_text_resources__get_glyph_multiple(void) {
  const uint8_t a_glyph_data_bytes[] = {0x2e, 0x42, 0x2e, 0x63, 0xb6};
  const uint8_t b_glyph_data_bytes[] = {0x21, 0x84, 0x36, 0x63, 0x8c, 0x71, 0x36};
  const uint8_t c_glyph_data_bytes[] = {0x2e, 0x86, 0x10, 0x42, 0x74};

  uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
  cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));

  uint8_t glyph_size_bytes;
  const GlyphData *glyph;

  glyph = text_resources_get_glyph(&s_font_cache, 'a', &s_font_info);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(a_glyph_data_bytes, glyph->data, glyph_size_bytes);

  glyph = text_resources_get_glyph(&s_font_cache, 'b', &s_font_info);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(b_glyph_data_bytes, glyph->data, glyph_size_bytes);

  glyph = text_resources_get_glyph(&s_font_cache, 'c', &s_font_info);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(c_glyph_data_bytes, glyph->data, glyph_size_bytes);
}

void test_text_resources__init_backup_font(void) {
  // load the built in fallback font
  uint32_t font_fallback = RESOURCE_ID_FONT_FALLBACK_INTERNAL;
  cl_assert(text_resources_init_font(0, font_fallback, 0, &s_font_info));
  cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3);
  cl_assert_equal_i(s_font_info.base.md.wildcard_codepoint, WILDCARD_CODEPOINT);
}

void test_text_resources__test_backup_wildcard(void) {
  const uint8_t wildcard_bytes[] = {0x3f, 0xc6, 0x18, 0x63, 0x8c, 0x31, 0xc6, 0x0f};

  uint32_t font_fallback = RESOURCE_ID_FONT_FALLBACK_INTERNAL;
  cl_assert(text_resources_init_font(0, font_fallback, 0, &s_font_info));

  int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache,
                                                                WILDCARD_CODEPOINT, &s_font_info);
  cl_assert(horiz_advance != 0);
  cl_assert_equal_i(horiz_advance,
      text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info));

  const GlyphData *glyph = text_resources_get_glyph(&s_font_cache,
                                                    WILDCARD_CODEPOINT, &s_font_info);
  cl_assert_equal_i(glyph->header.width_px, 5);
  cl_assert_equal_i(glyph->header.height_px, 12);
  uint8_t glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(wildcard_bytes, glyph->data, glyph_size_bytes);
}

void test_text_resources__test_gothic_wildcard(void) {
  uint8_t wildcard_bytes[] = {0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x83, 0xc1, 0x60, 0x30, 0x18, 0x0c, 0xfe, 0x01};

  uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
  cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));

  int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache,
                                                                WILDCARD_CODEPOINT,
                                                                &s_font_info);
  cl_assert(horiz_advance != 0);
  cl_assert_equal_i(horiz_advance,
      text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info));

  const GlyphData *glyph = text_resources_get_glyph(&s_font_cache,
                                                    WILDCARD_CODEPOINT,
                                                    &s_font_info);
  cl_assert_equal_i(glyph->header.width_px, 7);
  cl_assert_equal_i(glyph->header.height_px, 15);
  uint8_t glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(wildcard_bytes, glyph->data, glyph_size_bytes);
}

void test_text_resources__extended_font(void) {
  const uint8_t chinese_wildcard_bytes[] = {0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x83, 0xc1, 0x60, 0x30, 0x18, 0x0c, 0xfe, 0x01};
  const uint8_t a_glyph_data_bytes[] = {0x2e, 0x42, 0x2e, 0x63, 0xb6};
  const uint8_t chinese_glyph_data_bytes[] = {0x00, 0x0C, 0xE2, 0x01, 0x0F, 0x80, 0x30, 0x40,
                                              0x08, 0x10, 0x04, 0x08, 0x82, 0xFC, 0xFF, 0x80,
                                              0x00, 0x44, 0x00, 0x26, 0x01, 0x11, 0x41, 0x08,
                                              0x11, 0x84, 0x04, 0x82, 0xC0, 0x01, 0x40, 0x00};

  uint32_t gothic_18_bold_handle = RESOURCE_ID_GOTHIC_18;
  uint32_t gothic_18_bold_extended_handle = RESOURCE_ID_GOTHIC_18_EXTENDED;
  cl_assert(text_resources_init_font(0, gothic_18_bold_handle, gothic_18_bold_extended_handle, &s_font_info));
  cl_assert(s_font_info.loaded);
  cl_assert(s_font_info.extended);

  uint8_t glyph_size_bytes;
  const GlyphData *glyph;

  glyph = text_resources_get_glyph(&s_font_cache, 'a', &s_font_info);
  cl_assert(glyph != NULL);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(a_glyph_data_bytes, glyph->data, glyph_size_bytes);

  glyph = text_resources_get_glyph(&s_font_cache, 0x4E50 /* 乐 */, &s_font_info);
  // the chinese pbpack contains the letter 你, it should succeed
  cl_assert(glyph != NULL);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(chinese_glyph_data_bytes, glyph->data, glyph_size_bytes);

  glyph = text_resources_get_glyph(&s_font_cache, 0x8888 /* 袈 */, &s_font_info);
  // the chinese pbpack does not contain the letter 袈, it should return the wildcard
  cl_assert(glyph != NULL);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(chinese_wildcard_bytes, glyph->data, glyph_size_bytes);
}

void test_text_resources__test_emoji_font(void) {
  const uint8_t phone_bytes[] = {0xfe, 0x81, 0x81, 0x3c, 0x66, 0x42, 0xc3, 0xe7, 0xff, 0x00, 0x00, 0x00};

  uint32_t gothic_18_emoji_handle = RESOURCE_ID_GOTHIC_18_EMOJI;
  cl_assert(text_resources_init_font(0, gothic_18_emoji_handle, 0, &s_font_info));

  uint8_t glyph_size_bytes;
  const GlyphData *glyph;

  const Codepoint PHONE_CODEPOINT = 0x260E;
  glyph = text_resources_get_glyph(&s_font_cache, PHONE_CODEPOINT, &s_font_info);
  cl_assert(glyph != NULL);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(phone_bytes, glyph->data, glyph_size_bytes);
}

void DISABLED_test_text_resources__test_emoji_fallback(void) {
  const uint8_t phone_bytes[] = {0xfe, 0x81, 0x81, 0x3c, 0x66, 0x42, 0xc3, 0xe7, 0xff, 0x00, 0x00, 0x00};

  uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
  cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));

  uint8_t glyph_size_bytes;
  const GlyphData *glyph;

  const Codepoint PHONE_CODEPOINT = 0x260E;
  glyph = text_resources_get_glyph(&s_font_cache, PHONE_CODEPOINT, &s_font_info);
  cl_assert(glyph != NULL);
  glyph_size_bytes = glyph_get_size_bytes(glyph);
  cl_assert_equal_m(phone_bytes, glyph->data, glyph_size_bytes);
}


void test_text_resources__test_glyph_decompression(void) {
  // There is no way to get the list of glyphs present in a font with the existing API. This list
  // of ranges lists the 371 glyphs currently in fontname.ttf
  typedef struct Codepoint_Range {
    uint16_t start;
    uint16_t end;
  } CodePoint_Range;
  const CodePoint_Range codepoint_range[] = {
    { 0x0020, 0x007E }, { 0x00A0, 0x00AC }, { 0x00AE, 0x00D6 }, { 0x00D9, 0x017F },
    { 0x0192, 0x0192 }, { 0x01FC, 0x01FF }, { 0x0218, 0x021B }, { 0x02C6, 0x02DD },
    { 0x03C0, 0x03C0 }, { 0x2013, 0x2014 }, { 0x2018, 0x201A }, { 0x201C, 0x201E },
    { 0x2020, 0x2022 }, { 0x2026, 0x2026 }, { 0x2030, 0x2030 }, { 0x2039, 0x203A },
    { 0x2044, 0x2044 }, { 0x20AC, 0x20AC }, { 0x2122, 0x2122 }, { 0x2126, 0x2126 },
    { 0x2202, 0x2202 }, { 0x2206, 0x2206 }, { 0x220F, 0x220F }, { 0x2211, 0x2212 },
    { 0x221A, 0x221A }, { 0x221E, 0x221E }, { 0x222B, 0x222B }, { 0x2248, 0x2248 },
    { 0x2260, 0x2260 }, { 0x2264, 0x2265 }, { 0x25AF, 0x25AF }, { 0x25CA, 0x25CA },
    { 0xF6C3, 0xF6C3 }, { 0xFB01, 0xFB02 }
  };

  // Create a second FontInfo for the compressed font.
  // The uncompressed font will use the global.
  FontInfo font_info_compressed;
  memset(&font_info_compressed, 0, sizeof(font_info_compressed));

  // Load GOTHIC_18
  uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
  cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
  cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3);
  cl_assert(!HAS_FEATURE(s_font_info.base.md.version, VERSION_FIELD_FEATURE_RLE4));

  // Load GOTHIC_18_COMPRESSED. This is the same font, added by hand to the system resource pack.
  // To do this, simply copy the GOTHIC_18 stanza in resource/normal/base/resource_map.json, change
  // the name to include _COMPRESSED, and add the field: "compress": "RLE4". Rebuild, and run
  // ./tools/update_system_pbpack.sh
  uint32_t gothic_18_compressed_handle = RESOURCE_ID_GOTHIC_18_COMPRESSED; // Read source to fix
  cl_assert(text_resources_init_font(0, gothic_18_compressed_handle, 0, &font_info_compressed));
  cl_assert_equal_i(FONT_VERSION(font_info_compressed.base.md.version), 3);
  cl_assert(HAS_FEATURE(font_info_compressed.base.md.version, VERSION_FIELD_FEATURE_RLE4));

  // For each glyph in the font, get both the compressed and uncompressed bit field & header, and
  // assert that they are identical (ignoring any possible garbage after the bitmap).
  // Load the uncompressed glyph into this local glyph_buffer, and use the font cache for the
  // compressed glyph.
  uint8_t glyph_buffer[sizeof(GlyphHeaderData) + CACHE_GLYPH_SIZE];
  for (unsigned index = 0; index < ARRAY_LENGTH(codepoint_range); ++index) {
    for (unsigned codepoint = codepoint_range[index].start;
         codepoint <= codepoint_range[index].end; ++codepoint) {

      const GlyphData *glyph = text_resources_get_glyph(&s_font_cache, codepoint, &s_font_info);
      cl_assert(glyph);

      unsigned glyph_size = sizeof(GlyphHeaderData) + glyph_get_size_bytes(glyph);
      memcpy(glyph_buffer, glyph->data, glyph_size);

      glyph = text_resources_get_glyph(&s_font_cache, codepoint, &font_info_compressed);
      cl_assert(glyph);

      cl_assert_equal_m(glyph->data, glyph_buffer, glyph_size);
    }
  }
}