/*
 * 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 <stdio.h>
#include <string.h>

#include "applib/persist.h"
#include "flash_region/flash_region.h"
#include "process_management/app_install_manager.h"
#include "process_management/pebble_process_md.h"
#include "services/normal/filesystem/pfs.h"
#include "services/normal/persist.h"
#include "system/logging.h"

// Stubs
////////////////////////////////////
#include "fake_rtc.h"
#include "fake_spi_flash.h"
#include "stubs_analytics.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_system_reset.h"
#include "stubs_task_watchdog.h"

static PebbleProcessMd __pbl_app_info;

const PebbleProcessMd* sys_process_manager_get_current_process_md(void) {
  return &__pbl_app_info;
}

// Tests
////////////////////////////////////
#define TEST_UUID_A { 0x2F, 0xF7, 0xFA, 0x04, 0x60, 0x11, 0x4A, 0x98, 0x8A, 0x3B, 0xA8, 0x26, 0xA4, 0xB8, 0x99, 0xF8 }

static const int system_uuid_id = 0;
static const Uuid system_uuid = UUID_SYSTEM;

static const int test_uuid_a_id = 1;
static const Uuid test_uuid_a = TEST_UUID_A;

static const int test_uuid_b_id = 2;
static const Uuid test_uuid_b = { 0xC3, 0x0D, 0xBA, 0xF1, 0x5F, 0x6F, 0x4F, 0x22, 0xBA, 0xAA, 0x8C, 0x2A, 0x96, 0x8C, 0xFC, 0x28 };

static const int test_uuid_c_id = 3;
static const Uuid test_uuid_c = { 0x1D, 0x6C, 0x7F, 0x01, 0xD9, 0x48, 0x42, 0xA6, 0xAA, 0x4E, 0xB2, 0x08, 0x42, 0x10, 0xEB, 0xBC };

static PebbleProcessMd __pbl_app_info = {
  .uuid = TEST_UUID_A,
};

const char lipsum[] = "Lorem ipsum dolor sit amet, consectetur "
  "adipiscing elit. Nam dignissim ullamcorper sollicitudin. Suspendisse at "
  "urna suscipit, congue purus a, posuere eros. Nulla eros urna, vestibulum "
  "a dictum a, maximus sed nibh. Ut ut dui finibus, tincidunt ligula quis, "
  "ornare mi. Pellentesque sagittis suscipit lacus nec consectetur. Nunc et "
  "commodo neque. Vestibulum vitae dignissim sapien. Nulla scelerisque "
  "finibus nisl. Suspendisse ac massa lacus. In hac habitasse platea "
  "dictumst. Ut condimentum urna eros. Fusce ipsum metus, vehicula eu tortor "
  "sed, congue tempus mauris. Maecenas mollis lacus non cursus bibendum. "
  "Etiam id dolor lorem. Aenean scelerisque nulla sed tristique posuere. "
  "Proin dui magna, gravida faucibus ultricies non, tincidunt id metus. "
  "Integer a laoreet dolor, eu vulputate enim. Ut vitae hendrerit nunc, in "
  "bibendum eros. Pellentesque congue ut quam id sollicitudin. Cras "
  "malesuada arcu nec imperdiet cursus. Donec vitae ex eget mi imperdiet "
  "efficitur id eu velit. Proin pretium ipsum sed convallis efficitur. Morbi "
  "non feugiat erat. Ut ut efficitur massa. Sed eu auctor felis. Vestibulum "
  "magna orci, placerat nec risus nec, ultricies congue ex. Morbi in "
  "vestibulum leo. Nullam non dapibus lorem. Suspendisse blandit diam "
  "posuere suscipit malesuada. Maecenas vehicula felis eu posuere euismod. "
  "Fusce at velit ultrices, sagittis enim ac, ultrices lorem. Quisque "
  "tincidunt fringilla suscipit. Curabitur tempus lorem metus, sed venenatis "
  "augue maximus a. Duis venenatis tortor sit amet justo sodales suscipit. "
  "Morbi tincidunt rutrum nisl, eget placerat nisi condimentum a. Vestibulum "
  "ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia "
  "Curae; Cras varius sagittis mauris, in consequat sapien tincidunt vitae. "
  "Duis ipsum nunc, tristique sit amet blandit non, scelerisque non diam. "
  "Etiam condimentum aliquam dictum. Nam nisi ex, cursus in ligula sit amet, "
  "ultricies egestas libero. Aliquam luctus, metus quis ultricies sagittis, "
  "nisi orci viverra felis, vitae luctus massa dolor sit amet dolor. Cras "
  "mattis velit vitae pretium pulvinar. Pellentesque auctor, turpis at cras "
  "amet.";
_Static_assert(sizeof(lipsum) > PERSIST_STRING_MAX_LENGTH,
               "lipsum string is not long enough for persist tests");

void test_persist__initialize(void) {
  fake_spi_flash_init(0, 0x1000000);

  pfs_init(false);
  persist_service_init();

  persist_service_client_open(&test_uuid_a);
}

void test_persist__cleanup(void) {
  persist_service_client_close(&test_uuid_a);
}

void test_persist__int(void) {
  const uint32_t key = 0;
  const uint32_t value = ~0;
  cl_assert_equal_i(persist_read_int(key), 0);
  cl_assert_equal_i(persist_write_int(key, value), sizeof(int));
  cl_assert_equal_i(persist_get_size(key), sizeof(int));
  cl_assert_equal_i(persist_read_int(key), value);
}

void test_persist__bool(void) {
  const uint32_t key = 0;
  cl_assert_equal_i(persist_read_bool(key), false);
  cl_assert_equal_i(persist_write_bool(key, true), sizeof(bool));
  cl_assert_equal_i(persist_get_size(key), sizeof(bool));
  cl_assert_equal_i(persist_read_bool(key), true);
}

void test_persist__data(void) {
  const uint32_t key = 0;
  const int size = sizeof(test_uuid_a);
  Uuid uuid_buffer;
  cl_assert_equal_i(persist_read_data(key, &uuid_buffer, sizeof(uuid_buffer)), E_DOES_NOT_EXIST);

  cl_assert_equal_i(persist_write_data(key, &test_uuid_a, sizeof(test_uuid_a)), size);

  cl_assert_equal_i(persist_get_size(key), size);

  cl_assert_equal_i(persist_read_data(key, &uuid_buffer, sizeof(uuid_buffer)), size);

  cl_assert(uuid_equal(&test_uuid_a, &uuid_buffer));
}

void test_persist__data_too_big(void) {
  char buf[PERSIST_DATA_MAX_LENGTH+2];
  memset(buf, '~', sizeof(buf));

  cl_assert_equal_i(persist_write_data(0, lipsum, sizeof(lipsum)),
                    PERSIST_DATA_MAX_LENGTH);
  cl_assert_equal_i(persist_read_data(0, buf, sizeof(buf)),
                    PERSIST_DATA_MAX_LENGTH);
  cl_assert(memcmp(lipsum, buf, PERSIST_DATA_MAX_LENGTH) == 0);
  for (size_t i = PERSIST_DATA_MAX_LENGTH; i < sizeof(buf); ++i) {
    cl_assert_(buf[i] == '~',
               "persist_read_data writes past the end of destination buffer");
  }
}

void test_persist__string_does_not_exist(void) {
  char string_buffer[PERSIST_STRING_MAX_LENGTH];
  memset(string_buffer, '~', sizeof(string_buffer));

  cl_assert_equal_i(
      persist_read_string(0, string_buffer, sizeof(string_buffer)),
      E_DOES_NOT_EXIST);

  for (size_t i = 0; i < sizeof(string_buffer); ++i) {
    if (string_buffer[i] != '~') {
      char error_msg[132];
      snprintf(error_msg, sizeof(error_msg), "persist_read_string clobbers "
               "destination buffer at %zd when key does not exist", i);
      cl_fail(error_msg);
    }
  }
}

void test_persist__string_write_unterminated_string(void) {
  char string_buffer[PERSIST_STRING_MAX_LENGTH + 2];
  memset(string_buffer, '~', sizeof(string_buffer));

  cl_assert_equal_i(persist_write_string(0, lipsum), PERSIST_STRING_MAX_LENGTH);
  cl_assert_equal_i(persist_get_size(0), PERSIST_STRING_MAX_LENGTH);

  cl_assert_equal_i(
      persist_read_string(0, string_buffer, sizeof(string_buffer)),
      PERSIST_STRING_MAX_LENGTH);

  cl_assert_equal_i(string_buffer[PERSIST_STRING_MAX_LENGTH - 1], '\0');
  cl_assert(strncmp(lipsum, string_buffer, PERSIST_STRING_MAX_LENGTH - 1) == 0);

  for (size_t i = PERSIST_STRING_MAX_LENGTH; i < sizeof(string_buffer); ++i) {
    cl_assert_(string_buffer[i] == '~',
               "persist_read_string writes past the end of destination buffer");
  }
}

void test_persist__size_of_nonexistent_key(void) {
  cl_assert_equal_i(persist_get_size(0), E_DOES_NOT_EXIST);
}

void test_persist__size(void) {
  char data[] = { 1, 2, 3, 4, 5, 6 };
  cl_assert_equal_i(persist_write_data(0, data, sizeof(data)), sizeof(data));
  cl_assert_equal_i(persist_get_size(0), sizeof(data));
}

void test_persist__exists(void) {
  cl_assert_equal_i(persist_exists(0), S_FALSE);
  cl_assert(PASSED(persist_write_int(0, 0)));
  cl_assert_equal_i(persist_exists(0), S_TRUE);
}

void test_persist__delete(void) {
  cl_assert_equal_i(persist_delete(0), E_DOES_NOT_EXIST);
  cl_assert(PASSED(persist_write_int(0, 0)));
  cl_assert_equal_i(persist_delete(0), S_TRUE);
  cl_assert_equal_i(persist_delete(0), E_DOES_NOT_EXIST);
}

/*
 * Confirm that fields can be reassigned values.
 */
void test_persist__overwrite(void) {
  const uint32_t key = 0;
  cl_assert_equal_i(persist_write_int(key, 1), sizeof(int));
  cl_assert_equal_i(persist_read_int(key), 1);
  cl_assert_equal_i(persist_write_int(key, 2), sizeof(int));
  cl_assert_equal_i(persist_read_int(key), 2);
}

/*
 * Confirm that overwriting with a smaller data size does not break tuple finding.
 */
void test_persist__overwrite_shrink(void) {
  cl_assert(PASSED(persist_write_int(0, 1)));
  cl_assert(PASSED(persist_write_bool(0, false)));
  cl_assert(PASSED(persist_write_int(1, 2)));
  cl_assert(persist_read_int(1) == 2);
}

/*
 * Confirm that loading a smaller amount of data, then a larger amount of data,
 * always returns the appropriate amount of data.
 */
void test_persist__partial_read_extension(void) {
  char buffer[] = "Hello thar";

  // Write out data
  cl_assert_equal_i(persist_write_string(0, buffer), sizeof(buffer));
  // Clear the cache (which has the entire string right now)
  persist_service_client_close(&test_uuid_a);
  persist_service_client_open(&test_uuid_a);
  // Reset out buffer so we can check the read's honesty.
  memset(buffer, 0, strlen(buffer));
  // Read part of the data we wrote
  cl_assert_equal_i(persist_read_string(0, buffer, 2), 2);
  cl_assert_equal_i(strlen(buffer), 2 - 1); // -1 because null termination
  cl_assert_equal_i(buffer[0], 'H');
  // Then attempt to read back the entire thing
  cl_assert_equal_i(persist_read_string(0, buffer, sizeof(buffer)), sizeof(buffer));
  cl_assert_equal_i(strlen(buffer), 10);
  cl_assert(strcmp(buffer, "Hello thar") == 0);
}

void test_persist__legacy2_max_usage(void) {
  uint8_t buffer[256];
  memset(buffer, 1, sizeof(buffer));

  // The maximum amount of 'buffer' sized fields allowed by the old persist
  // storage backend.
  int n = (4 * 1024) / (9 + sizeof(buffer));

  PBL_LOG_VERBOSE("n = %d", n);
  for (int i = 0; i < n; ++i) {
    PBL_LOG(LOG_LEVEL_DEBUG, "i = %d", i);
    cl_assert_equal_i(persist_write_data(i, &buffer, sizeof(buffer)), sizeof(buffer));
  }

  // Don't be too strict about preventing apps from using more persist than they
  // had available under the old implementation.
  // cl_assert_equal_i(persist_write_data(n + 1, &buffer, sizeof(buffer)),
  //                  E_OUT_OF_STORAGE);
}
