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

#include "clar_asserts.h"

#include "kernel/pbl_malloc.h"
#include "services/common/put_bytes/put_bytes_storage_internal.h"
#include "system/passert.h"

#define FAKE_STORAGE_MAX_SIZE (512 * 1024)

typedef struct FakePutBytesStorageData {
  PutBytesStorageInfo *info;
  bool last_is_success;
  uint32_t crc;
  uint32_t total_size;
  uint8_t buffer[FAKE_STORAGE_MAX_SIZE];
} FakePutBytesStorageData;

FakePutBytesStorageData s_storage_data;

bool pb_storage_raw_get_status(PutBytesObjectType obj_type,  PbInstallStatus *status) {
  return false;
}

static bool fake_pb_storage_mem_init(PutBytesStorage *storage, PutBytesObjectType object_type,
                                     uint32_t total_size, PutBytesStorageInfo *info,
                                     uint32_t append_offset) {
  // This fake only supports one put bytes storage to be init'd at a time.
  PBL_ASSERTN(!s_storage_data.total_size);
  size_t buffer_size = total_size + sizeof(FirmwareDescription);
  memset(s_storage_data.buffer, 0, sizeof(s_storage_data.buffer));
  s_storage_data.total_size = buffer_size;
  PutBytesStorageInfo *info_copy = NULL;
  if (info) {
    info_copy = (PutBytesStorageInfo *)kernel_malloc_check(sizeof(PutBytesStorageInfo));
    *info_copy = *info;
  }
  s_storage_data.info = info_copy;
  storage->impl_data = &s_storage_data;

  // put_bytes_storage_raw.c is weird, it reserves space at the beginning for FirmwareDescription:
  storage->current_offset = sizeof(FirmwareDescription);
  return true;
}

uint32_t fake_pb_storage_mem_get_max_size(PutBytesObjectType object_type) {
  return FAKE_STORAGE_MAX_SIZE;
}

static void(*s_do_before_write)(void) = NULL;
static void fake_pb_storage_mem_write(PutBytesStorage *storage, uint32_t offset,
                                      const uint8_t *buffer, uint32_t length) {
  PBL_ASSERTN(s_storage_data.total_size);
  PBL_ASSERTN(offset + length <= s_storage_data.total_size);

  if (s_do_before_write) {
    s_do_before_write();
    s_do_before_write = NULL;
  }

  memcpy(s_storage_data.buffer + offset, buffer, length);
}

static uint32_t fake_pb_storage_mem_calculate_crc(PutBytesStorage *storage, PutBytesCrcType crc_type) {
  PBL_ASSERTN(storage->impl_data == &s_storage_data);
  return s_storage_data.crc;
}

static void prv_cleanup(void) {
  kernel_free(s_storage_data.info);
  s_storage_data.info = NULL;
  s_storage_data.total_size = 0;
}

static void fake_pb_storage_mem_deinit(PutBytesStorage *storage, bool is_success) {
  PBL_ASSERTN(storage->impl_data == &s_storage_data);
  prv_cleanup();
  s_storage_data.last_is_success = is_success;
}

const PutBytesStorageImplementation s_raw_implementation = {
  .init = fake_pb_storage_mem_init,
  .get_max_size = fake_pb_storage_mem_get_max_size,
  .write = fake_pb_storage_mem_write,
  .calculate_crc = fake_pb_storage_mem_calculate_crc,
  .deinit = fake_pb_storage_mem_deinit,
};

const PutBytesStorageImplementation s_file_implementation = {
  .init = fake_pb_storage_mem_init,
  .get_max_size = fake_pb_storage_mem_get_max_size,
  .write = fake_pb_storage_mem_write,
  .calculate_crc = fake_pb_storage_mem_calculate_crc,
  .deinit = fake_pb_storage_mem_deinit,
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Fake manipulation

void fake_pb_storage_register_cb_before_write(void (*cb_before_write)(void)) {
  s_do_before_write = cb_before_write;
}

void fake_pb_storage_mem_reset(void) {
  prv_cleanup();
  s_storage_data = (FakePutBytesStorageData) {};
}

void fake_pb_storage_mem_set_crc(uint32_t crc) {
  s_storage_data.crc = crc;
}

bool fake_pb_storage_mem_get_last_success(void) {
  return s_storage_data.last_is_success;
}

void fake_pb_storage_mem_assert_contents_written(const uint8_t contents[], size_t size) {
  cl_assert_equal_m(contents, s_storage_data.buffer + sizeof(FirmwareDescription), size);
}

void fake_pb_storage_mem_assert_fw_description_written(const FirmwareDescription *fw_descr) {
  cl_assert_equal_m(fw_descr, s_storage_data.buffer, sizeof(*fw_descr));
}

#undef FAKE_STORAGE_MAX_SIZE