/*
 * 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 "weather_data_shared.h"

#include "clar_asserts.h"

#include "drivers/rtc.h"
#include "kernel/pbl_malloc.h"
#include "services/normal/blob_db/watch_app_prefs_db.h"
#include "services/normal/blob_db/weather_db.h"
#include "services/normal/weather/weather_service_private.h"

#define WEATHER_PREFS_DATA_SIZE (sizeof(SerializedWeatherAppPrefs) + \
                                (sizeof(Uuid) * WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES))
static uint8_t s_weather_app_prefs[WEATHER_PREFS_DATA_SIZE];

static const WeatherDBKey s_keys[] = {
  {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
  },
  {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
  },
  {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3
  },
  {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4
  },
  {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5
  },
};

static WeatherDBEntry *s_entries[WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES];
static size_t s_entry_sizes[WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES];

static char *s_entry_names[] = {
  TEST_WEATHER_DB_LOCATION_PALO_ALTO,
  TEST_WEATHER_DB_LOCATION_KITCHENER,
  TEST_WEATHER_DB_LOCATION_WATERLOO,
  TEST_WEATHER_DB_LOCATION_RWC,
  TEST_WEATHER_DB_LOCATION_SF,
};

static char *s_entry_phrases[] = {
  TEST_WEATHER_DB_SHORT_PHRASE_SUNNY,
  TEST_WEATHER_DB_SHORT_PHRASE_PARTLY_CLOUDY,
  TEST_WEATHER_DB_SHORT_PHRASE_HEAVY_SNOW,
  TEST_WEATHER_DB_SHORT_PHRASE_HEAVY_RAIN,
  TEST_WEATHER_DB_SHORT_PHRASE_PARTLY_CLOUDY,
};

static const WeatherDBEntry s_entry_bases[] = {
  {
    .version = WEATHER_DB_CURRENT_VERSION,
    .is_current_location = true,
    .current_temp = 68,
    .current_weather_type = WeatherType_Sun,
    .today_high_temp = 68,
    .today_low_temp = 52,
    .tomorrow_weather_type = WeatherType_CloudyDay,
    .tomorrow_high_temp = 70,
    .tomorrow_low_temp = 60,
  },
  {
    .version = WEATHER_DB_CURRENT_VERSION,
    .is_current_location = false,
    .current_temp = -10,
    .current_weather_type = WeatherType_PartlyCloudy,
    .today_high_temp = 0,
    .today_low_temp = -11,
    .tomorrow_weather_type = WeatherType_CloudyDay,
    .tomorrow_high_temp = 2,
    .tomorrow_low_temp = -3,
  },
  {
    .version = WEATHER_DB_CURRENT_VERSION,
      .is_current_location = false,
    .current_temp = -99,
    .current_weather_type = WeatherType_HeavySnow,
    .today_high_temp = -98,
    .today_low_temp = -99,
    .tomorrow_weather_type = WeatherType_Sun,
    .tomorrow_high_temp = 2,
    .tomorrow_low_temp = 1,
  },
  {
    .version = WEATHER_DB_CURRENT_VERSION,
    .is_current_location = true,
    .current_temp = 60,
    .current_weather_type = WeatherType_HeavyRain,
    .today_high_temp = 70,
    .today_low_temp = 50,
    .tomorrow_weather_type = WeatherType_PartlyCloudy,
    .tomorrow_high_temp = 70,
    .tomorrow_low_temp = 60,
  },
  {
    .version = WEATHER_DB_CURRENT_VERSION,
    .is_current_location = true,
    .current_temp = 60,
    .current_weather_type = WeatherType_PartlyCloudy,
    .today_high_temp = 70,
    .today_low_temp = 50,
    .tomorrow_weather_type = WeatherType_PartlyCloudy,
    .tomorrow_high_temp = 70,
    .tomorrow_low_temp = 60,
  }
};

// Fake out watch_app_prefs calls
void watch_app_prefs_destroy_weather(SerializedWeatherAppPrefs *prefs) {}

SerializedWeatherAppPrefs *watch_app_prefs_get_weather(void) {
  SerializedWeatherAppPrefs *prefs = (SerializedWeatherAppPrefs *) s_weather_app_prefs;
  prefs->num_locations = WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES;
  for (int idx = 0; idx < WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES; idx++) {
    prefs->locations[idx] = s_keys[idx];
  }
  return prefs;
}

static WeatherDBEntry *prv_create_entry(const WeatherDBEntry *base_entry, char *location,
                                        char *phrase, size_t *size_out) {
  PascalString16List pstring16_list;
  PascalString16 *location_name;
  PascalString16 *short_phrase;
  size_t data_size;

  location_name = pstring_create_pstring16_from_string(location);
  short_phrase = pstring_create_pstring16_from_string(phrase);

  data_size = strlen(location) +
              strlen(phrase) +
              sizeof(uint16_t) * 2; // One for each string

  const size_t entry_size = sizeof(WeatherDBEntry) + data_size;
  WeatherDBEntry *entry = task_zalloc_check(entry_size);
  *entry = *base_entry;
  entry->pstring16s.data_size = data_size;
  entry->last_update_time_utc = rtc_get_time();

  pstring_project_list_on_serialized_array(&pstring16_list, &entry->pstring16s);
  pstring_add_pstring16_to_list(&pstring16_list, location_name);
  pstring_add_pstring16_to_list(&pstring16_list, short_phrase);

  pstring_destroy_pstring16(location_name);
  pstring_destroy_pstring16(short_phrase);

  *size_out = entry_size;
  return entry;
}

static void prv_initialize_entries(void) {
  for (int idx = 0; idx < WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES; idx++) {
    WeatherDBEntry *entry = prv_create_entry(&s_entry_bases[idx],
                                             s_entry_names[idx],
                                             s_entry_phrases[idx],
                                             &s_entry_sizes[idx]);

    // Make the last entry contain a timestamp that is too old to be included in weather_service
    // forecast list
    if (idx == WEATHER_DATA_SHARED_NUM_VALID_TIMESTAMP_ENTRIES) {
      entry->last_update_time_utc = (time_start_of_today() - SECONDS_PER_DAY - 1);
    }
    cl_assert_equal_i(S_SUCCESS, weather_db_insert((uint8_t*)&s_keys[idx],
                                                   sizeof(WeatherDBKey),
                                                   (uint8_t*)entry,
                                                   s_entry_sizes[idx]));
    s_entries[idx] = entry;
  }
}

void weather_shared_data_init(void) {
  rtc_set_time(1461765790); // 2016-04-27T14:03:10+00:00
  prv_initialize_entries();
}

void weather_shared_data_cleanup(void) {
  for (int i = 0; i < WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES; i++) {
    if (s_entries[i]) {
      task_free(s_entries[i]);
      s_entries[i] = NULL;
    }
  }

  // Flush DB
  cl_assert_equal_i(S_SUCCESS, weather_db_flush());
}


const WeatherDBKey *weather_shared_data_get_key(int index) {
  return &s_keys[index];
}

WeatherDBEntry *weather_shared_data_get_entry(int index) {
  return s_entries[index];
}

size_t weather_shared_data_get_entry_size(int index) {
  return s_entry_sizes[index];
}

char *weather_shared_data_get_entry_name(int index) {
  return s_entry_names[index];
}

char *weather_shared_data_get_entry_phrase(int index) {
  return s_entry_phrases[index];
}

int weather_shared_data_get_index_of_key(const WeatherDBKey *key) {
  for (int idx = 0; idx < WEATHER_DATA_SHARED_WEATHER_DB_NUM_DB_ENTRIES; idx++) {
    if (uuid_equal(key, &s_keys[idx])) {
      return idx;
    }
  }
  return -1;
}

void weather_shared_data_assert_entries_equal(const WeatherDBKey *key, WeatherDBEntry *to_check,
                                              WeatherDBEntry *original) {
  cl_assert_equal_i(to_check->version, original->version);
  cl_assert_equal_b(to_check->is_current_location, original->is_current_location);
  cl_assert_equal_i(to_check->current_temp, original->current_temp);
  cl_assert_equal_i(to_check->current_weather_type, original->current_weather_type);
  cl_assert_equal_i(to_check->today_high_temp, original->today_high_temp);
  cl_assert_equal_i(to_check->today_low_temp, original->today_low_temp);
  cl_assert_equal_i(to_check->tomorrow_weather_type, original->tomorrow_weather_type);
  cl_assert_equal_i(to_check->tomorrow_high_temp, original->tomorrow_high_temp);
  cl_assert_equal_i(to_check->tomorrow_low_temp, original->tomorrow_low_temp);
  cl_assert_equal_i(to_check->last_update_time_utc, original->last_update_time_utc);

  PascalString16List pstring16_list;
  pstring_project_list_on_serialized_array(&pstring16_list, &to_check->pstring16s);
  cl_assert_equal_i(pstring16_list.count, 2);

  PascalString16 *pstring;

  pstring = pstring_get_pstring16_from_list(&pstring16_list, 0);

  int index = weather_shared_data_get_index_of_key(key);
  if (index == -1) {
    cl_fail("key not found!");
  }

  cl_assert_equal_i(pstring->str_length, strlen(s_entry_names[index]));
  char loc[WEATHER_SERVICE_MAX_WEATHER_LOCATION_BUFFER_SIZE];
  pstring_pstring16_to_string(pstring, loc);
  cl_assert_equal_s(loc, s_entry_names[index]);

  pstring = pstring_get_pstring16_from_list(&pstring16_list, 1);
  cl_assert_equal_i(pstring->str_length, strlen(s_entry_phrases[index]));
  char phrase[WEATHER_SERVICE_MAX_SHORT_PHRASE_BUFFER_SIZE];
  pstring_pstring16_to_string(pstring, phrase);
  cl_assert_equal_s(phrase, s_entry_phrases[index]);
}

bool weather_shared_data_get_key_exists(WeatherDBKey *key) {
  return weather_shared_data_get_index_of_key(key) != -1;
}

status_t weather_db_insert_stale(const uint8_t *key, int key_len, const uint8_t *val, int val_len);

size_t weather_shared_data_insert_stale_entry(WeatherDBKey *key) {
  const WeatherDBEntry stale_entry = {
    .version = WEATHER_DB_CURRENT_VERSION - 1,
    .is_current_location = true,
    .current_temp = 68,
    .current_weather_type = WeatherType_Sun,
    .today_high_temp = 68,
    .today_low_temp = 52,
    .tomorrow_weather_type = WeatherType_CloudyDay,
    .tomorrow_high_temp = 70,
    .tomorrow_low_temp = 60,
  };

  WeatherDBEntry *entry = prv_create_entry(&stale_entry,
                                           s_entry_names[0],
                                           s_entry_phrases[0],
                                           &s_entry_sizes[0]);

  cl_assert_equal_i(S_SUCCESS, weather_db_insert_stale((uint8_t*)key,
                                                       sizeof(WeatherDBKey),
                                                       (uint8_t*)entry,
                                                       s_entry_sizes[0]));
  return s_entry_sizes[0];
}