// SPDX-License-Identifier: MIT
#include "blisp_easy.h"
#include "blisp_struct.h"
#include "blisp_util.h"

#include <inttypes.h>
#include <string.h>

static int64_t blisp_easy_transport_read(struct blisp_easy_transport* transport,
                                         void* buffer,
                                         uint32_t size) {
  if (transport->type == 0) {
    // TODO: Implement reading more than available
    memcpy(buffer, (uint8_t*)transport->data.memory.data_location + transport->data.memory.current_position, size);
    transport->data.memory.current_position += size;
    return size;
  } else {
    return fread(buffer, size, 1, transport->data.file_handle);
  }
}

static int64_t blisp_easy_transport_size(struct blisp_easy_transport* transport) {
  if (transport->type == 0) {
    return transport->data.memory.data_size;
  } else {
    // TODO: Implement
	  printf("%s() Warning: calling non-implemented function\n", __func__);
	  return -1;
  }
}

static void blisp_easy_report_progress(blisp_easy_progress_callback callback,
                                       uint32_t current_value,
                                       uint32_t max_value) {
  if (callback != NULL) {
    callback(current_value, max_value);
  }
}

struct blisp_easy_transport blisp_easy_transport_new_from_file(FILE* file) {
  struct blisp_easy_transport transport = {.type = 1, .data.file_handle = file};
  return transport;
}

struct blisp_easy_transport blisp_easy_transport_new_from_memory(
    void* data_location,
    uint32_t data_size) {
  struct blisp_easy_transport transport = {
      .type = 0,
      .data.memory.data_location = data_location,
      .data.memory.data_size = data_size,
      .data.memory.current_position = 0};
  return transport;
}

int32_t blisp_easy_load_segment_data(
    struct blisp_device* device,
    uint32_t segment_size,
    struct blisp_easy_transport* segment_transport,
    blisp_easy_progress_callback progress_callback) {
  int32_t ret;
#ifdef __APPLE__
  const uint16_t buffer_max_size = 252 * 16;
#else
  const uint16_t buffer_max_size = 4092;
#endif


  uint32_t sent_data = 0;
  uint32_t buffer_size = 0;
#ifdef _WIN32
  uint8_t buffer[4092];
#else
  uint8_t buffer[buffer_max_size];
#endif

  blisp_easy_report_progress(progress_callback, 0, segment_size);

  while (sent_data < segment_size) {
    buffer_size = segment_size - sent_data;
    if (buffer_size > buffer_max_size) {
      buffer_size = buffer_max_size;
    }
    blisp_easy_transport_read(segment_transport, buffer,
                              buffer_size);  // TODO: Error Handling
    ret = blisp_device_load_segment_data(device, buffer, buffer_size);
    if (ret < BLISP_OK) {
      // TODO: Error logging fprintf(stderr, "Failed to load segment data. (ret
      // %d)\n", ret);
      return ret;
    }
    sent_data += buffer_size;
    blisp_easy_report_progress(progress_callback, sent_data, segment_size);
  }
  return 0;
}

int32_t blisp_easy_load_ram_image(
    struct blisp_device* device,
    struct blisp_easy_transport* image_transport,
    blisp_easy_progress_callback progress_callback) {
  int32_t ret;

  struct bfl_boot_header image_boot_header;
  // TODO: Error handling
  blisp_easy_transport_read(image_transport, &image_boot_header, 176);

  ret = blisp_device_load_boot_header(device, (uint8_t*)&image_boot_header);
  if (ret != BLISP_OK) {
    // TODO: Error printing: fprintf(stderr, "Failed to load boot header.\n");
    return ret;
  }

  {
    for (uint8_t seg_index = 0;
         seg_index < image_boot_header.segment_info.segment_cnt; seg_index++) {
      struct blisp_segment_header segment_header = {0};
      blisp_easy_transport_read(image_transport, &segment_header,
                                16);  // TODO: Error handling

      ret = blisp_device_load_segment_header(device, &segment_header);
      if (ret != 0) {
        // TODO: Error printing: fprintf(stderr, "Failed to load segment
        // header.");
        return ret;
      }
      // TODO: Info printing: printf("Flashing %d. segment\n", seg_index + 1);

      ret = blisp_easy_load_segment_data(device, segment_header.length,
                                         image_transport, progress_callback);
      if (ret != 0) {
        return ret;
      }
    }
  }

  ret = blisp_device_check_image(device);
  if (ret != BLISP_OK) {
    // TODO: Error printing: fprintf(stderr, "Failed to check image.\n");
    return BLISP_EASY_ERR_CHECK_IMAGE_FAILED;
  }

  return BLISP_OK;
}

int32_t blisp_easy_load_ram_app(struct blisp_device* device,
                                struct blisp_easy_transport* app_transport,
                                blisp_easy_progress_callback progress_callback)
{
  int32_t ret;
  // TODO: Rework
  // region boot header fill
  struct bfl_boot_header boot_header;
  memcpy(boot_header.magiccode, "BFNP", 4);
  memcpy(boot_header.flashCfg.magiccode, "FCFG", 4);
  boot_header.revison = 0x01;
  boot_header.flashCfg.cfg.ioMode = 0x04;
  boot_header.flashCfg.cfg.cReadSupport = 0x01;
  boot_header.flashCfg.cfg.clkDelay = 0x01;
  boot_header.flashCfg.cfg.clkInvert = 0x01;
  boot_header.flashCfg.cfg.resetEnCmd = 0x66;
  boot_header.flashCfg.cfg.resetCmd = 0x99;
  boot_header.flashCfg.cfg.resetCreadCmd = 0xFF;
  boot_header.flashCfg.cfg.resetCreadCmdSize = 0x03;
  boot_header.flashCfg.cfg.jedecIdCmd = 0x9F;
  boot_header.flashCfg.cfg.jedecIdCmdDmyClk = 0x00;
  boot_header.flashCfg.cfg.qpiJedecIdCmd = 0x9F;
  boot_header.flashCfg.cfg.qpiJedecIdCmdDmyClk = 0x00;
  boot_header.flashCfg.cfg.sectorSize = 0x04;
  boot_header.flashCfg.cfg.mid = 0xEF;
  boot_header.flashCfg.cfg.pageSize = 0x100;
  boot_header.flashCfg.cfg.chipEraseCmd = 0xC7;
  boot_header.flashCfg.cfg.sectorEraseCmd = 0x20;
  boot_header.flashCfg.cfg.blk32EraseCmd = 0x52;
  boot_header.flashCfg.cfg.blk64EraseCmd = 0xD8;
  boot_header.flashCfg.cfg.writeEnableCmd = 0x06;
  boot_header.flashCfg.cfg.pageProgramCmd = 0x02;
  boot_header.flashCfg.cfg.qpageProgramCmd = 0x32;
  boot_header.flashCfg.cfg.qppAddrMode = 0x00;
  boot_header.flashCfg.cfg.fastReadCmd = 0x0B;
  boot_header.flashCfg.cfg.frDmyClk = 0x01;
  boot_header.flashCfg.cfg.qpiFastReadCmd = 0x0B;
  boot_header.flashCfg.cfg.qpiFrDmyClk = 0x01;
  boot_header.flashCfg.cfg.fastReadDoCmd = 0x3B;
  boot_header.flashCfg.cfg.frDoDmyClk = 0x01;
  boot_header.flashCfg.cfg.fastReadDioCmd = 0xBB;
  boot_header.flashCfg.cfg.frDioDmyClk = 0x00;
  boot_header.flashCfg.cfg.fastReadQoCmd = 0x6B;
  boot_header.flashCfg.cfg.frQoDmyClk = 0x01;
  boot_header.flashCfg.cfg.fastReadQioCmd = 0xEB;
  boot_header.flashCfg.cfg.frQioDmyClk = 0x02;
  boot_header.flashCfg.cfg.qpiFastReadQioCmd = 0xEB;
  boot_header.flashCfg.cfg.qpiFrQioDmyClk = 0x02;
  boot_header.flashCfg.cfg.qpiPageProgramCmd = 0x02;
  boot_header.flashCfg.cfg.writeVregEnableCmd = 0x50;
  boot_header.flashCfg.cfg.wrEnableIndex = 0x00;
  boot_header.flashCfg.cfg.qeIndex = 0x01;
  boot_header.flashCfg.cfg.busyIndex = 0x00;
  boot_header.flashCfg.cfg.wrEnableBit = 0x01;
  boot_header.flashCfg.cfg.qeBit = 0x01;
  boot_header.flashCfg.cfg.busyBit = 0x00;
  boot_header.flashCfg.cfg.wrEnableWriteRegLen = 0x02;
  boot_header.flashCfg.cfg.wrEnableReadRegLen = 0x01;
  boot_header.flashCfg.cfg.qeWriteRegLen = 0x01;
  boot_header.flashCfg.cfg.qeReadRegLen = 0x01;
  boot_header.flashCfg.cfg.releasePowerDown = 0xAB;
  boot_header.flashCfg.cfg.busyReadRegLen = 0x01;
  boot_header.flashCfg.cfg.readRegCmd[0] = 0x05;
  boot_header.flashCfg.cfg.readRegCmd[1] = 0x35;
  boot_header.flashCfg.cfg.readRegCmd[2] = 0x00;
  boot_header.flashCfg.cfg.readRegCmd[3] = 0x00;
  boot_header.flashCfg.cfg.writeRegCmd[0] = 0x01;
  boot_header.flashCfg.cfg.writeRegCmd[1] = 0x31;
  boot_header.flashCfg.cfg.writeRegCmd[2] = 0x00;
  boot_header.flashCfg.cfg.writeRegCmd[3] = 0x00;
  boot_header.flashCfg.cfg.enterQpi = 0x38;
  boot_header.flashCfg.cfg.exitQpi = 0xFF;
  boot_header.flashCfg.cfg.cReadMode = 0x20;
  boot_header.flashCfg.cfg.cRExit = 0xFF;
  boot_header.flashCfg.cfg.burstWrapCmd = 0x77;
  boot_header.flashCfg.cfg.burstWrapCmdDmyClk = 0x03;
  boot_header.flashCfg.cfg.burstWrapDataMode = 0x02;
  boot_header.flashCfg.cfg.burstWrapData = 0x40;
  boot_header.flashCfg.cfg.deBurstWrapCmd = 0x77;
  boot_header.flashCfg.cfg.deBurstWrapCmdDmyClk = 0x03;
  boot_header.flashCfg.cfg.deBurstWrapDataMode = 0x02;
  boot_header.flashCfg.cfg.deBurstWrapData = 0xF0;
  boot_header.flashCfg.cfg.timeEsector = 0x12C;
  boot_header.flashCfg.cfg.timeE32k = 0x4B0;
  boot_header.flashCfg.cfg.timeE64k = 0x4B0;
  boot_header.flashCfg.cfg.timePagePgm = 0x05;
  boot_header.flashCfg.cfg.timeCe = 0xD40;
  boot_header.flashCfg.cfg.pdDelay = 0x03;
  boot_header.flashCfg.cfg.qeData = 0x00;
  boot_header.flashCfg.crc32 = 0xC4BDD748;
  boot_header.clkCfg.cfg.xtal_type = 0x04;
  boot_header.clkCfg.cfg.pll_clk = 0x04;
  boot_header.clkCfg.cfg.hclk_div = 0x00;
  boot_header.clkCfg.cfg.bclk_div = 0x01;
  boot_header.clkCfg.cfg.flash_clk_type = 0x02;
  boot_header.clkCfg.cfg.flash_clk_div = 0x00;
  boot_header.clkCfg.crc32 = 0x824E14BB;
  boot_header.bootcfg.bval.sign = 0x00;
  boot_header.bootcfg.bval.encrypt_type = 0x00;
  boot_header.bootcfg.bval.key_sel = 0x00;
  boot_header.bootcfg.bval.rsvd6_7 = 0x00;
  boot_header.bootcfg.bval.no_segment = 0x01;
  boot_header.bootcfg.bval.cache_enable = 0x01;
  boot_header.bootcfg.bval.notload_in_bootrom = 0x00;
  boot_header.bootcfg.bval.aes_region_lock = 0x00;
  boot_header.bootcfg.bval.cache_way_disable = 0x00;
  boot_header.bootcfg.bval.crc_ignore = 0x01;
  boot_header.bootcfg.bval.hash_ignore = 0x01;
  boot_header.bootcfg.bval.halt_ap = 0x00;
  boot_header.bootcfg.bval.rsvd19_31 = 0x00;
  boot_header.segment_info.segment_cnt = 0x01;
  boot_header.bootentry = 0x00;
  boot_header.flashoffset = device->chip->tcm_address;
  boot_header.hash[0x00] = 0xEF;
  boot_header.hash[0x01] = 0xBE;
  boot_header.hash[0x02] = 0xAD;
  boot_header.hash[0x03] = 0xDE;
  boot_header.hash[0x04] = 0x00;
  boot_header.hash[0x05] = 0x00;
  boot_header.hash[0x06] = 0x00;
  boot_header.hash[0x07] = 0x00;
  boot_header.hash[0x08] = 0x00;
  boot_header.hash[0x09] = 0x00;
  boot_header.hash[0x0a] = 0x00;
  boot_header.hash[0x0b] = 0x00;
  boot_header.hash[0x0c] = 0x00;
  boot_header.hash[0x0d] = 0x00;
  boot_header.hash[0x0e] = 0x00;
  boot_header.hash[0x0f] = 0x00;
  boot_header.hash[0x10] = 0x00;
  boot_header.hash[0x11] = 0x00;
  boot_header.hash[0x12] = 0x00;
  boot_header.hash[0x13] = 0x00;
  boot_header.hash[0x14] = 0x00;
  boot_header.hash[0x15] = 0x00;
  boot_header.hash[0x16] = 0x00;
  boot_header.hash[0x17] = 0x00;
  boot_header.hash[0x18] = 0x00;
  boot_header.hash[0x19] = 0x00;
  boot_header.hash[0x1a] = 0x00;
  boot_header.hash[0x1b] = 0x00;
  boot_header.hash[0x1c] = 0x00;
  boot_header.hash[0x1d] = 0x00;
  boot_header.hash[0x1e] = 0x00;
  boot_header.hash[0x1f] = 0x00;
  boot_header.rsv1 = 0x00;
  boot_header.rsv2 = 0x00;
  boot_header.crc32 = 0xDEADBEEF;
  // endregion


  ret = blisp_device_load_boot_header(device, (uint8_t*)&boot_header);
  if (ret != BLISP_OK) {
    blisp_dlog("Failed to load boot header, ret: %d.", ret);
    return ret;
  }

  struct blisp_segment_header segment_header = {
      .dest_addr = device->chip->tcm_address,
      .length = blisp_easy_transport_size(app_transport),
      .reserved = 0,
      .crc32 = 0
  };
  segment_header.crc32 = crc32_calculate(&segment_header, 3 * sizeof(uint32_t)); // TODO: Make function

  ret = blisp_device_load_segment_header(device, &segment_header);
  if (ret != 0) {
    blisp_dlog("Failed to load segment header, ret: %d.", ret);
    return ret;
  }

  ret = blisp_easy_load_segment_data(device, blisp_easy_transport_size(app_transport),
                                     app_transport, progress_callback);
  if (ret != 0) {
    // TODO: Error printing
    return ret;
  }


  return BLISP_OK;
}

int32_t blisp_easy_flash_write(struct blisp_device* device,
                               struct blisp_easy_transport* data_transport,
                               uint32_t flash_location,
                               uint32_t data_size,
                               blisp_easy_progress_callback progress_callback) {
  int32_t ret;
#if defined (__APPLE__) || defined (__FreeBSD__)
  const uint16_t buffer_max_size = 372 * 1;
#else
  const uint16_t buffer_max_size = 2052;
#endif

  uint32_t sent_data = 0;
  uint32_t buffer_size = 0;
#ifdef _WIN32
  uint8_t buffer[2052];
#else
  uint8_t buffer[buffer_max_size];
#endif
  blisp_easy_report_progress(progress_callback, 0, data_size);

  while (sent_data < data_size) {
    buffer_size = data_size - sent_data;
    if (buffer_size > buffer_max_size) {
      buffer_size = buffer_max_size;
    }
    blisp_easy_transport_read(data_transport, buffer,
                              buffer_size);  // TODO: Error Handling
    ret = blisp_device_flash_write(device, flash_location + sent_data, buffer,
                                   buffer_size);
    if (ret < BLISP_OK) {
      // TODO: Error logigng:  fprintf(stderr, "Failed to write firmware! (ret:
      // %d)\n", ret);
      return ret;
    }
    sent_data += buffer_size;
    blisp_easy_report_progress(progress_callback, sent_data, data_size);
  }
  return BLISP_OK;
}