/* MIT License - Copyright (c) 2019-2021 Francis Van Roie
   For full license information read the LICENSE file in the project folder */

#include "hasp_hal.h"
#include "hasp_conf.h"

#if defined(ESP8266)
#include <Esp.h>
#include <ESP8266WiFi.h>
#endif

#if defined(ESP32)
#include <Esp.h>
#include <WiFi.h>
#include "esp_system.h"
#endif

#if defined(ARDUINO_ARCH_ESP32)
#include <rom/rtc.h> // needed to get the ResetInfo

// Compatibility function for ESP8266 getRestInfo
// String esp32ResetReason(uint8_t cpuid)
// {
//     if(cpuid > 1) {
//         return F("Invalid CPU id");
//     }
//     RESET_REASON reason = rtc_get_reset_reason(cpuid);

//     String resetReason((char*)0);
//     resetReason.reserve(128);

//     resetReason += F("CPU");
//     resetReason += cpuid;
//     resetReason += F(": ");

//     switch(reason) {
//         case 1:
//             resetReason += F("POWERON");
//             break; /**<1, Vbat power on reset*/
//         case 3:
//             resetReason += F("SW");
//             break; /**<3, Software reset digital core*/
//         case 4:
//             resetReason += F("OWDT");
//             break; /**<4, Legacy watch dog reset digital core*/
//         case 5:
//             resetReason += F("DEEPSLEEP");
//             break; /**<5, Deep Sleep reset digital core*/
//         case 6:
//             resetReason += F("SDIO");
//             break; /**<6, Reset by SLC module, reset digital core*/
//         case 7:
//             resetReason += F("TG0WDT_SYS");
//             break; /**<7, Timer Group0 Watch dog reset digital core*/
//         case 8:
//             resetReason += F("TG1WDT_SYS");
//             break; /**<8, Timer Group1 Watch dog reset digital core*/
//         case 9:
//             resetReason += F("RTCWDT_SYS");
//             break; /**<9, RTC Watch dog Reset digital core*/
//         case 10:
//             resetReason += F("INTRUSION");
//             break; /**<10, Instrusion tested to reset CPU*/
//         case 11:
//             resetReason += F("TGWDT_CPU");
//             break; /**<11, Time Group reset CPU*/
//         case 12:
//             resetReason += F("SW_CPU");
//             break; /**<12, Software reset CPU*/
//         case 13:
//             resetReason += F("RTCWDT_CPU");
//             break; /**<13, RTC Watch dog Reset CPU*/
//         case 14:
//             resetReason += F("EXT_CPU");
//             break; /**<14, for APP CPU, reseted by PRO CPU*/
//         case 15:
//             resetReason += F("RTCWDT_BROWN_OUT");
//             break; /**<15, Reset when the vdd voltage is not stable*/
//         case 16:
//             resetReason += F("RTCWDT_RTC");
//             break; /**<16, RTC Watch dog reset digital core and rtc module*/
//         default:
//             resetReason += F("NO_MEAN");
//             return resetReason;
//     }
//     resetReason += F("_RESET");
//     return resetReason;
// }
#endif

// void halRestartMcu(void)
// {
// #if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)
//     ESP.restart();
// #else
//     NVIC_SystemReset();
// #endif
//     for(;;) {
//     } // halt
// }

// String halGetResetInfo()
// {
// #if defined(ARDUINO_ARCH_ESP32)
//     String resetReason((char*)0);
//     resetReason.reserve(128);

//     resetReason += String(esp32ResetReason(0));
//     resetReason += F(" / ");
//     resetReason += String(esp32ResetReason(1));
//     return resetReason;
// #elif defined(ARDUINO_ARCH_ESP8266)
//     return ESP.getResetInfo();
// #else
//     return "";
// #endif
// }

// String halGetCoreVersion()
// {
// #if defined(ARDUINO_ARCH_ESP32)
//     return String(ESP.getSdkVersion());
// #elif defined(ARDUINO_ARCH_ESP8266)
//     return String(ESP.getCoreVersion());
// #else
//     return String(STM32_CORE_VERSION_MAJOR) + "." + STM32_CORE_VERSION_MINOR + "." + STM32_CORE_VERSION_PATCH;
// #endif
// }

// String halGetChipModel()
// {
//     String model((char*)0);
//     model.reserve(128);

// #if defined(STM32F4xx)
//     model = F("STM32");
// #endif

// #if defined(STM32F4xx)
//     model = F("STM32F4xx");

// #elif defined(ARDUINO_ARCH_ESP8266)
//     model = F("ESP8266");

// #elif defined(ARDUINO_ARCH_ESP32)
//     esp_chip_info_t chip_info;
//     esp_chip_info(&chip_info);

//     model = chip_info.cores;
//     model += F("core ");
//     switch(chip_info.model) {
//         case CHIP_ESP32:
//             model += F("ESP32");
//             break;
// #ifdef CHIP_ESP32S2
//         case CHIP_ESP32S2:
//             model += F("ESP32-S2");
//             break;
// #endif
//         default:
//             model = F("Unknown ESP32");
//     }
//     model += F(" rev");
//     model += chip_info.revision;

// #else
//     model = F("Unknown");
// #endif

//     return model;
// }

/*******************************/
/* Memory Management Functions */

#if defined(STM32F4xx)
#include <malloc.h> // for mallinfo()
#include <unistd.h> // for sbrk()

int freeHighMemory()
{
    char top;
#ifdef __arm__
    return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
    return &top - __brkval;
#else  // __arm__
    return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
#endif

/*
extern char *fake_heap_end;   // current heap start
extern char *fake_heap_start;   // current heap end

char* getHeapStart() {
   return fake_heap_start;
}

char* getHeapEnd() {
   return (char*)sbrk(0);
}

char* getHeapLimit() {
   return fake_heap_end;
}

int getMemUsed() { // returns the amount of used memory in bytes
   struct mallinfo mi = mallinfo();
   return mi.uordblks;
}

int getMemFree() { // returns the amount of free memory in bytes
   struct mallinfo mi = mallinfo();
   return mi.fordblks + freeHighMemory();
} */

// size_t halGetMaxFreeBlock()
// {
// #if defined(ARDUINO_ARCH_ESP32)
//     return ESP.getMaxAllocHeap();
// #elif defined(ARDUINO_ARCH_ESP8266)
//     return ESP.getMaxFreeBlockSize();
// #else
//     return freeHighMemory();
// #endif
// }

// size_t halGetFreeHeap(void)
// {
// #if defined(ARDUINO_ARCH_ESP32)
//     return ESP.getFreeHeap();
// #elif defined(ARDUINO_ARCH_ESP8266)
//     return ESP.getFreeHeap();
// #else
//     struct mallinfo chuncks = mallinfo();

//     // fordblks
//     //    This is the total size of memory occupied by free (not in use) chunks.

//     return chuncks.fordblks + freeHighMemory();
// #endif
// }

// uint8_t halGetHeapFragmentation()
// {
// #if defined(ARDUINO_ARCH_ESP32)
//     return (int8_t)(100.00f - (float)ESP.getMaxAllocHeap() * 100.00f / (float)ESP.getFreeHeap());
// #elif defined(ARDUINO_ARCH_ESP8266)
//     return ESP.getHeapFragmentation();
// #else
//     return (int8_t)(100.00f - (float)freeHighMemory() * 100.00f / (float)halGetFreeHeap());
// #endif
// }

String halGetMacAddress(int start, const char* seperator)
{
    byte mac[6];

#if defined(STM32F4xx)
    uint8_t* mac_p = nullptr;
#if HASP_USE_ETHERNET > 0
#if USE_BUILTIN_ETHERNET > 0
    mac_p = Ethernet.MACAddress();
    for(int i = 0; i < 6; i++) mac[i] = *(mac_p + i);
#else
    Ethernet.macAddress(mac);
#endif
#endif
#else
    WiFi.macAddress(mac);
#endif

    String cMac((char*)0);
    cMac.reserve(32);

    for(int i = start; i < 6; ++i) {
        if(mac[i] < 0x10) cMac += "0";
        cMac += String(mac[i], HEX);
        if(i < 5) cMac += seperator;
    }
    cMac.toUpperCase();
    return cMac;
}

// uint16_t halGetCpuFreqMHz()
// {
// #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_32)
//     return ESP.getCpuFreqMHz();
// #else
//     return (F_CPU / 1000 / 1000);
// #endif
// }

String halDisplayDriverName()
{
#if defined(ILI9341_DRIVER)
    return F("ILI9341");
#elif defined(ST7735_DRIVER)
    return F("ST7735");
#elif defined(ILI9163_DRIVER)
    return F("ILI9163");
#elif defined(S6D02A1_DRIVER)
    return F("S6D02A1");
#elif defined(ST7796_DRIVER)
    return F("ST7796");
#elif defined(ILI9486_DRIVER)
    return F("ILI9486");
#elif defined(ILI9481_DRIVER)
    return F("ILI9481");
#elif defined(ILI9488_DRIVER)
    return F("ILI9488");
#elif defined(HX8357D_DRIVER)
    return F("HX8357D");
#elif defined(EPD_DRIVER)
    return F("EPD");
#elif defined(ST7789_DRIVER)
    return F("ST7789");
#elif defined(R61581_DRIVER)
    return F("R61581");
#elif defined(ST7789_2_DRIVER)
    return F("ST7789_2");
#elif defined(RM68140_DRIVER)
    return F("RM68140");
#endif
    return F("Unknown");
}

String halGpioName(uint8_t gpio)
{
#if defined(STM32F4xx)
    String ioName;
    uint16_t name = digitalPin[gpio];
    uint8_t num   = name % 16;
    switch(name / 16) {
        case PortName::PortA:
            ioName = F("PA");
            break;
        case PortName::PortB:
            ioName = F("PB");
            break;
#if defined GPIOC_BASE
        case PortName::PortC:
            ioName = F("PC");
            break;
#endif
#if defined GPIOD_BASE
        case PortName::PortD:
            ioName = F("PD");
            break;
#endif
#if defined GPIOE_BASE
        case PortName::PortE:
            ioName = F("PE");
            break;
#endif
#if defined GPIOF_BASE
        case PortName::PortF:
            ioName = F("PF");
            break;
#endif
#if defined GPIOG_BASE
        case PortName::PortG:
            ioName = F("PG");
            break;
#endif
#if defined GPIOH_BASE
        case PortName::PortH:
            ioName = F("PH");
            break;
#endif
#if defined GPIOI_BASE
        case PortName::PortI:
            ioName = F("PI");
            break;
#endif
#if defined GPIOJ_BASE
        case PortName::PortJ:
            ioName = F("PJ");
            break;
#endif
#if defined GPIOK_BASE
        case PortName::PortK:
            ioName = F("PK");
            break;
#endif
#if defined GPIOZ_BASE
        case PortName::PortZ:
            ioName = F("PZ");
            break;
#endif
        default:
            ioName = F("P?");
    }
    ioName += num;
    ioName += F(" (io");
    ioName += gpio;
    ioName += F(")");
    return ioName;
#endif

// For ESP32 pin labels on boards use the GPIO number
#ifdef ARDUINO_ARCH_ESP32
    return /*String(F("gpio")) +*/ String(gpio);
#endif

#ifdef ARDUINO_ARCH_ESP8266
    // For ESP8266 the pin labels are not the same as the GPIO number
    // These are for the NodeMCU pin definitions:
    //        GPIO       Dxx
    switch(gpio) {
        case 16:
            return F("D0");
        case 5:
            return F("D1");
        case 4:
            return F("D2");
        case 0:
            return F("D3");
        case 2:
            return F("D4");
        case 14:
            return F("D5");
        case 12:
            return F("D6");
        case 13:
            return F("D7");
        case 15:
            return F("D8");
        case 3:
            return F("TX");
        case 1:
            return F("RX");
        // case 9:
        //     return F("D11");
        // case 10:
        //     return F("D12");
        default:
            return F("D?"); // Invalid pin
    }
#endif
}