#include "arch.h"
#include "flash.h"
#include "log.h"
#include "ota.h"
#ifdef MG_OTA_PUBLIC_KEY
#include "sha256.h"
#include "tls_uecc.h"
#endif

#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM

static char *s_addr;      // Current address to write to
static size_t s_size;     // Firmware size to flash. In-progress indicator
static uint32_t s_crc32;  // Firmware checksum

bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) {
  bool ok = false;
  if (s_size) {
    MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
  } else {
    size_t half = flash->size / 2;
    s_crc32 = 0;
    s_addr = (char *) flash->start + half;
    MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half));
    if (new_firmware_size < half) {
      ok = true;
      s_size = new_firmware_size;
      MG_INFO(("Starting OTA, firmware size %lu", s_size));
    } else {
      MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half - flash->align));
    }
  }
  return ok;
}

bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) {
  bool ok = false;
  if (s_size == 0) {
    MG_ERROR(("OTA is not started, call mg_ota_begin()"));
  } else if (s_addr + MG_ROUND_UP(len, flash->align) >
             (char *) flash->start + flash->size) {
    MG_ERROR(("Flash overflow: attempting to write past the flash boundary"));
  } else {
    size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align);
    if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down);
    if (len_aligned_down < len) {
      size_t left = len - len_aligned_down;
      char tmp[flash->align];
      memset(tmp, 0xff, sizeof(tmp));
      memcpy(tmp, (char *) buf + len_aligned_down, left);
      ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp));
    }
    s_crc32 = mg_crc32(s_crc32, (char *) buf, len);  // Update CRC
    MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok));
    s_addr += len;
  }
  return ok;
}

bool mg_ota_flash_end(struct mg_flash *flash) {
  char *base = (char *) flash->start + flash->size / 2;
  bool ok = false;
  int prev_state = MG_OTA_STATE_GET();
  if (s_size) {
    size_t size = (size_t) (s_addr - base);
    uint32_t crc32 = mg_crc32(0, base, s_size);
    if (size == s_size && crc32 == s_crc32) ok = true;
    MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
              size, ok ? "ok" : "fail"));
#ifdef MG_OTA_PUBLIC_KEY
    if (ok) {
      bool signed_fw = s_size > 68 &&
                       memcmp((uint8_t *) base + s_size - 4, "MGSG", 4) == 0;
      if (signed_fw) {
        static const uint8_t s_pubkey[] = MG_OTA_PUBLIC_KEY;
        uint8_t hash[32];
        size_t fw_size = s_size - 68;  // strip 64-byte sig + 4-byte magic
        mg_sha256(hash, (uint8_t *) base, fw_size);
        ok = mg_uecc_verify(s_pubkey, hash, sizeof(hash),
                            (uint8_t *) base + fw_size,
                            mg_uecc_secp256r1()) == 1;
        MG_INFO(("Signature: %s", ok ? "ok" : "fail"));
      } else {
        ok = false;
        MG_ERROR(("Unsigned firmware rejected"));
      }
    }
#endif
    s_size = 0;
    if (ok) MG_OTA_STATE_SET(MG_OTA_TESTING);
    if (ok) ok = flash->swap_fn();
    if (!ok) MG_OTA_STATE_SET(prev_state); // undo state in case of failure
  }
  MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
  return ok;
}

#endif
