/*
 Esp.cpp - ESP31B-specific APIs
 Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "Arduino.h"
#include "Esp.h"
#include "esp_sleep.h"
#include "spi_flash_mmap.h"
#include <memory>
#include <soc/soc.h>
#include <esp_partition.h>
extern "C" {
#include "esp_ota_ops.h"
#include "esp_image_format.h"
}
#include <MD5Builder.h>

#include "soc/spi_reg.h"
#include "esp_system.h"
#include "esp_chip_info.h"
#include "esp_mac.h"
#include "esp_flash.h"
#ifdef ESP_IDF_VERSION_MAJOR // IDF 4+
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
#include "esp32/rom/spi_flash.h"
#include "soc/efuse_reg.h"
#define ESP_FLASH_IMAGE_BASE 0x1000     // Flash offset containing flash size and spi mode
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/spi_flash.h"
#include "soc/efuse_reg.h"
#define ESP_FLASH_IMAGE_BASE 0x1000
#elif CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/rom/spi_flash.h"
#include "soc/efuse_reg.h"
#define ESP_FLASH_IMAGE_BASE 0x0000     // Esp32s3 is located at 0x0000
#elif CONFIG_IDF_TARGET_ESP32C2
#include "esp32c2/rom/spi_flash.h"
#define ESP_FLASH_IMAGE_BASE 0x0000     // Esp32c2 is located at 0x0000
#elif CONFIG_IDF_TARGET_ESP32C3
#include "esp32c3/rom/spi_flash.h"
#define ESP_FLASH_IMAGE_BASE 0x0000     // Esp32c3 is located at 0x0000
#elif CONFIG_IDF_TARGET_ESP32C6
#include "esp32c6/rom/spi_flash.h"
#define ESP_FLASH_IMAGE_BASE 0x0000     // Esp32c6 is located at 0x0000
#elif CONFIG_IDF_TARGET_ESP32H2
#include "esp32h2/rom/spi_flash.h"
#define ESP_FLASH_IMAGE_BASE 0x0000     // Esp32h2 is located at 0x0000
#else 
#error Target CONFIG_IDF_TARGET is not supported
#endif
#else // ESP32 Before IDF 4.0
#include "rom/spi_flash.h"
#define ESP_FLASH_IMAGE_BASE 0x1000
#endif

// REG_SPI_BASE is not defined for S3/C3 ??

#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3
  #ifndef REG_SPI_BASE
  #define REG_SPI_BASE(i)     (DR_REG_SPI1_BASE + (((i)>1) ? (((i)* 0x1000) + 0x20000) : (((~(i)) & 1)* 0x1000 )))
  #endif // REG_SPI_BASE
#endif // TARGET

/**
 * User-defined Literals
 *  usage:
 *
 *   uint32_t = test = 10_MHz; // --> 10000000
 */

unsigned long long operator"" _kHz(unsigned long long x)
{
    return x * 1000;
}

unsigned long long operator"" _MHz(unsigned long long x)
{
    return x * 1000 * 1000;
}

unsigned long long operator"" _GHz(unsigned long long x)
{
    return x * 1000 * 1000 * 1000;
}

unsigned long long operator"" _kBit(unsigned long long x)
{
    return x * 1024;
}

unsigned long long operator"" _MBit(unsigned long long x)
{
    return x * 1024 * 1024;
}

unsigned long long operator"" _GBit(unsigned long long x)
{
    return x * 1024 * 1024 * 1024;
}

unsigned long long operator"" _kB(unsigned long long x)
{
    return x * 1024;
}

unsigned long long operator"" _MB(unsigned long long x)
{
    return x * 1024 * 1024;
}

unsigned long long operator"" _GB(unsigned long long x)
{
    return x * 1024 * 1024 * 1024;
}


EspClass ESP;

void EspClass::deepSleep(uint64_t time_us)
{
    esp_deep_sleep(time_us);
}

void EspClass::restart(void)
{
    esp_restart();
}

uint32_t EspClass::getHeapSize(void)
{
    return heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
}

uint32_t EspClass::getFreeHeap(void)
{
    return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
}

uint32_t EspClass::getMinFreeHeap(void)
{
    return heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
}

uint32_t EspClass::getMaxAllocHeap(void)
{
    return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
}

uint32_t EspClass::getPsramSize(void)
{
	if(psramFound()){
	    return heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
	}
	return 0;
}

uint32_t EspClass::getFreePsram(void)
{
	if(psramFound()){
	    return heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
	}
	return 0;
}

uint32_t EspClass::getMinFreePsram(void)
{
	if(psramFound()){
	    return heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
	}
	return 0;
}

uint32_t EspClass::getMaxAllocPsram(void)
{
	if(psramFound()){
	    return heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
	}
	return 0;
}

static uint32_t sketchSize(sketchSize_t response) {
    esp_image_metadata_t data;
    const esp_partition_t *running = esp_ota_get_running_partition();
    if (!running) return 0;
    const esp_partition_pos_t running_pos  = {
        .offset = running->address,
        .size = running->size,
    };
    data.start_addr = running_pos.offset;
    esp_image_verify(ESP_IMAGE_VERIFY, &running_pos, &data);
    if (response) {
        return running_pos.size - data.image_len;
    } else {
        return data.image_len;
    }
}

uint32_t EspClass::getSketchSize () {
    return sketchSize(SKETCH_SIZE_TOTAL);
}

String EspClass::getSketchMD5()
{
    static String result;
    if (result.length()) {
        return result;
    }
    uint32_t lengthLeft = getSketchSize();

    const esp_partition_t *running = esp_ota_get_running_partition();
    if (!running) {
        log_e("Partition could not be found");
        return String();
    }

    const size_t bufSize = SPI_FLASH_SEC_SIZE;
    uint8_t *pb = (uint8_t *)malloc(bufSize);
    if(!pb) {
        log_e("Not enough memory to allocate buffer");
        return String();
    }
    uint32_t offset = 0;

    MD5Builder md5;
    md5.begin();
    while(lengthLeft > 0) {
        size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
        if (!ESP.flashRead(running->address + offset, (uint32_t *)pb, (readBytes + 3) & ~3)) {
            free(pb);
            log_e("Could not read buffer from flash");
            return String();
        }
        md5.add(pb, readBytes);
        lengthLeft -= readBytes;
        offset += readBytes;

        #if CONFIG_FREERTOS_UNICORE
        delay(1);  // Fix solo WDT
        #endif
    }
    free(pb);
    md5.calculate();
    result = md5.toString();
    return result;
}

uint32_t EspClass::getFreeSketchSpace () {
    const esp_partition_t* _partition = esp_ota_get_next_update_partition(NULL);
    if(!_partition){
        return 0;
    }

    return _partition->size;
}

uint16_t EspClass::getChipRevision(void)
{
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    return chip_info.revision;
}

const char * EspClass::getChipModel(void)
{
#if CONFIG_IDF_TARGET_ESP32
    uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_PACKAGE);
    uint32_t pkg_ver = chip_ver & 0x7;
    switch (pkg_ver) {
        case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ6 :
            if (getChipRevision() == 3)
                return "ESP32-D0WDQ6-V3";  
            else
                return "ESP32-D0WDQ6";
        case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ5 :
            if (getChipRevision() == 3)
                return "ESP32-D0WD-V3";  
            else
                return "ESP32-D0WD";
        case EFUSE_RD_CHIP_VER_PKG_ESP32D2WDQ5 :
            return "ESP32-D2WD";
        case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD2 :
            return "ESP32-PICO-D2";
        case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4 :
            return "ESP32-PICO-D4";
        case EFUSE_RD_CHIP_VER_PKG_ESP32PICOV302 :
            return "ESP32-PICO-V3-02";
        case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDR2V3 :
            return "ESP32-D0WDR2-V3";
        default:
            return "Unknown";
    }
#elif CONFIG_IDF_TARGET_ESP32S2
    uint32_t pkg_ver = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_PKG_VERSION);
    switch (pkg_ver) {
    case 0:
      return "ESP32-S2";
    case 1:
      return "ESP32-S2FH16";
    case 2:
      return "ESP32-S2FH32";
    default:
      return "ESP32-S2 (Unknown)";
    }
#else
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    switch(chip_info.model){
        case CHIP_ESP32S3: return "ESP32-S3";
        case CHIP_ESP32C3: return "ESP32-C3";
        case CHIP_ESP32C2: return "ESP32-C2";
        case CHIP_ESP32C6: return "ESP32-C6";
        case CHIP_ESP32H2: return "ESP32-H2";
        default: return "UNKNOWN";
    }
#endif
}

uint8_t EspClass::getChipCores(void)
{
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    return chip_info.cores;
}

const char * EspClass::getSdkVersion(void)
{
    return esp_get_idf_version();
}

const char * EspClass::getCoreVersion(void)
{
    return ESP_ARDUINO_VERSION_STR;
}

uint32_t ESP_getFlashChipId(void)
{
  uint32_t id = g_rom_flashchip.device_id;
  id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00);
  return id;
}

uint32_t EspClass::getFlashChipSize(void)
{
  uint32_t id = (ESP_getFlashChipId() >> 16) & 0xFF;
  return 2 << (id - 1);
}

uint32_t EspClass::getFlashChipSpeed(void)
{
    esp_image_header_t fhdr;
    if(esp_flash_read(esp_flash_default_chip, (void*)&fhdr, ESP_FLASH_IMAGE_BASE, sizeof(esp_image_header_t)) && fhdr.magic != ESP_IMAGE_HEADER_MAGIC) {
        return 0;
    }
    return magicFlashChipSpeed(fhdr.spi_speed);
}

FlashMode_t EspClass::getFlashChipMode(void)
{
   #if CONFIG_IDF_TARGET_ESP32S2
   uint32_t spi_ctrl = REG_READ(PERIPHS_SPI_FLASH_CTRL);
   #else
   #if CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6
   uint32_t spi_ctrl = REG_READ(DR_REG_SPI0_BASE + 0x8);
   #else
   uint32_t spi_ctrl = REG_READ(SPI_CTRL_REG(0));
   #endif
   #endif
   /* Not all of the following constants are already defined in older versions of spi_reg.h, so do it manually for now*/
   if (spi_ctrl & BIT(24)) { //SPI_FREAD_QIO
       return (FM_QIO);
   } else if (spi_ctrl & BIT(20)) { //SPI_FREAD_QUAD
       return (FM_QOUT);
   } else if (spi_ctrl &  BIT(23)) { //SPI_FREAD_DIO
       return (FM_DIO);
   } else if (spi_ctrl & BIT(14)) { // SPI_FREAD_DUAL
       return (FM_DOUT);
   } else if (spi_ctrl & BIT(13)) { //SPI_FASTRD_MODE
       return (FM_FAST_READ);
   } else {
       return (FM_SLOW_READ);
   }
   return (FM_DOUT);
}

uint32_t EspClass::magicFlashChipSize(uint8_t byte)
{
/*
    FLASH_SIZES = {
        "1MB": 0x00,
        "2MB": 0x10,
        "4MB": 0x20,
        "8MB": 0x30,
        "16MB": 0x40,
        "32MB": 0x50,
        "64MB": 0x60,
        "128MB": 0x70,
    }
*/
    switch(byte & 0x0F) {
      case 0x0: return (1_MB);   // 8 MBit (1MB)
      case 0x1: return (2_MB);   // 16 MBit (2MB)
      case 0x2: return (4_MB);   // 32 MBit (4MB)
      case 0x3: return (8_MB);   // 64 MBit (8MB)
      case 0x4: return (16_MB);  // 128 MBit (16MB)
      case 0x5: return (32_MB);  // 256 MBit (32MB)
      case 0x6: return (64_MB);  // 512 MBit (64MB)
      case 0x7: return (128_MB); // 1 GBit (128MB)
      default: // fail?
        return 0;
    }
}

uint32_t EspClass::magicFlashChipSpeed(uint8_t byte)
{
#if CONFIG_IDF_TARGET_ESP32C2
/*
    FLASH_FREQUENCY = {
        "60m": 0xF,
        "30m": 0x0,
        "20m": 0x1,
        "15m": 0x2,
    }
*/
    switch(byte & 0x0F) {
      case 0xF: return (60_MHz);
      case 0x0: return (30_MHz);
      case 0x1: return (20_MHz);
      case 0x2: return (15_MHz);
      default: // fail?
        return 0;
    }


#elif CONFIG_IDF_TARGET_ESP32C6
/*
   FLASH_FREQUENCY = {
        "80m": 0x0,  # workaround for wrong mspi HS div value in ROM
        "40m": 0x0,
        "20m": 0x2,
    }
*/
    switch(byte & 0x0F) {
      case 0x0: return (80_MHz);
      case 0x2: return (20_MHz);
      default: // fail?
        return 0;
    }

#elif CONFIG_IDF_TARGET_ESP32H2

/*
    FLASH_FREQUENCY = {
        "48m": 0xF,
        "24m": 0x0,
        "16m": 0x1,
        "12m": 0x2,
    }
*/
    switch(byte & 0x0F) {
      case 0xF: return (48_MHz);
      case 0x0: return (24_MHz);
      case 0x1: return (16_MHz);
      case 0x2: return (12_MHz);
      default: // fail?
        return 0;
    }


#else
/*
    FLASH_FREQUENCY = {
        "80m": 0xF,
        "40m": 0x0,
        "26m": 0x1,
        "20m": 0x2,
    }
*/
    switch(byte & 0x0F) {
      case 0xF: return (80_MHz);
      case 0x0: return (40_MHz);
      case 0x1: return (26_MHz);
      case 0x2: return (20_MHz);
      default: // fail?
        return 0;
    }
#endif
}


FlashMode_t EspClass::magicFlashChipMode(uint8_t byte)
{
    FlashMode_t mode = (FlashMode_t) byte;
    if(mode > FM_SLOW_READ) {
        mode = FM_UNKNOWN;
    }
    return mode;
}

bool EspClass::flashEraseSector(uint32_t sector)
{
    return esp_flash_erase_region(esp_flash_default_chip, sector * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE) == ESP_OK;
}

// Warning: These functions do not work with encrypted flash
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size)
{
    return esp_flash_write(esp_flash_default_chip, (const void*) data, offset, size) == ESP_OK;
}

bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size)
{
    return esp_flash_read(esp_flash_default_chip, (void*) data, offset, size) == ESP_OK;
}

bool EspClass::partitionEraseRange(const esp_partition_t *partition, uint32_t offset, size_t size) 
{
    return esp_partition_erase_range(partition, offset, size) == ESP_OK;
}

bool EspClass::partitionWrite(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size) 
{
    return esp_partition_write(partition, offset, data, size) == ESP_OK;
}

bool EspClass::partitionRead(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size) 
{
    return esp_partition_read(partition, offset, data, size) == ESP_OK;
}

uint64_t EspClass::getEfuseMac(void)
{
    uint64_t _chipmacid = 0LL;
    esp_efuse_mac_get_default((uint8_t*) (&_chipmacid));
    return _chipmacid;
}
