/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * Most of the code in this file is copied (with a few modifications) from:
 *     Esp.cpp      - esp8266-specific APIs
 *     Esp-frag.cpp - esp8266-specific APIs
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2015 Ivan Grokhotkov. All rights reserved.
 *
 * This file is part of the esp8266 core for Arduino environment.
 *
 * 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 <umm_malloc.h>
#include <user_interface.h>

#include <eCxx/eCxx_Internal.h>
#include <eCxx/Platform/Common/HWInfo.h>

#include "CoreLib.h"

#include <ESP8266WiFi.h>


//
// External variable from "SysAppEntry.cpp"
//
extern rst_info resetInfo;


namespace eCxx {


//
// Helper functions
//
static void getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag)
{
    umm_info(nullptr, false);

    const uint32_t free_size = umm_free_heap_size_core( umm_get_current_heap() ) ;

    if(hfree) *hfree = free_size;
    if(hmax ) *hmax  = static_cast<uint16_t>( umm_max_block_size_core( umm_get_current_heap() ) );

    if(hfrag) {
        if(free_size) *hfrag = umm_fragmentation_metric_core( umm_get_current_heap() );
        else          *hfrag = 0;
    }
}


//
// A simple hardware information class
//
PGMCharPointer HWInfo::eCxxVersion() noexcept
{
    static const char PROGMEM eCxxVersionString[] = ECXX_LIBRARY_VERSION_STRING;

    return eCxx::PGM_PTR(eCxxVersionString);
}


PGMCharPointer HWInfo::compilerVersion() noexcept
{
    static const char PROGMEM compilerVersionString[] = ECXX_COMPILER_VERSION_STRING;

    return eCxx::PGM_PTR(compilerVersionString);
}


PGMCharPointer HWInfo::deviceName() noexcept
{
    static const char PROGMEM deviceName[] = "esp8266-lx106";

    return eCxx::PGM_PTR(deviceName);
}


uint32_t HWInfo::chipSignature() noexcept
{
    // ESP8266 does not have a user-readable chip signature, therefore, a dummy one is generated here
    //
    // Character      E  S  P  8  2  6  6
    // ASCII         69 83 80 56 50 54 54
    // ASCII-base    65 65 65 48 48 48 48
    // Substracted    4 18 15  8  2  6  6
    // Hexadecimal    4 12  F  8  2  6  6
    //
    // Combined      412F8266
    // Reversed      66822F41

    return 0x66822F41UL;
}


void HWInfo::chipId(uint8_t (&id)[10]) noexcept
{
    const uint32_t chipId = system_get_chip_id();

    id[0] = 0;
    id[1] = 0;
    id[2] = 0;
    id[3] = 0;
    id[4] = 0;
    id[5] = 0;
    id[6] = ( chipId >> 24 ) & 0xFF;
    id[7] = ( chipId >> 16 ) & 0xFF;
    id[8] = ( chipId >>  8 ) & 0xFF;
    id[9] = ( chipId       ) & 0xFF;
}


uint32_t HWInfo::cpuFrequency() noexcept
{ return Application::getCPUFrequency(); }


uint16_t HWInfo::cpuFrequency_kHz() noexcept
{
    const uint32_t cpuFreq = Application::getCPUFrequency();
    if(cpuFreq > 65535000UL) return 0;

    return static_cast<uint16_t>(cpuFreq / 1000UL);
}


uint8_t HWInfo::cpuFrequency_MHz() noexcept
{
    const uint32_t cpuFreq = Application::getCPUFrequency();
    if(cpuFreq > 255000000UL) return 0;

    return static_cast<uint8_t>(cpuFreq / 1000000UL);
}


uint32_t HWInfo::totalRAMSize() noexcept
{ return RAM_SIZE; }


uint32_t HWInfo::totalFlashSize() noexcept
{ return NodeMCU::getFlashChipSize(); }


uint32_t HWInfo::totalEEPROMSize() noexcept
{ return sizeof(Application::_emulatedEEPROMBuffer); }


uint32_t HWInfo::rawNVStorageSize() noexcept
{ return FS_PHYS_SIZE; }


bool HWInfo::eepromIsEmulated() noexcept
{ return true; }


uint16_t HWInfo::iramSize() noexcept
{ return MMU_IRAM_SIZE; }


uint16_t HWInfo::icacheSize() noexcept
{ return MMU_ICACHE_SIZE; }


uint32_t HWInfo::freeContStack() noexcept
{ return contGetFreeStack(g_ContData); }


uint32_t HWInfo::freeHeapSize() noexcept
{
    uint32_t ret;
    getHeapStats(&ret, nullptr, nullptr);

    return ret;

    //return system_get_free_heap_size();
}


uint16_t HWInfo::maxHeapFreeBlockSize() noexcept
{
    uint16_t ret;
    getHeapStats(nullptr, &ret, nullptr);

    return ret;

    //return umm_max_block_size();
}


uint8_t HWInfo::percentHeapFragmentation() noexcept
{
    uint8_t ret;
    getHeapStats(nullptr, nullptr, &ret);

    return ret;

    //return static_cast<uint8_t>( umm_fragmentation_metric() );
}


uint32_t HWInfo::userProgramSize() noexcept
{ return NodeMCU::getFlashUserProgramSize(); }


uint32_t HWInfo::freeUserProgramSpace() noexcept
{ return NodeMCU::getFlashUserProgramFreeSpace(); }


uint32_t HWInfo::flashChipId() noexcept
{ return NodeMCU::getFlashChipId(); }


uint32_t HWInfo::flashChipSpeed() noexcept
{ return NodeMCU::getFlashChipSpeed(); }


uint8_t HWInfo::flashChipSpeed_MHz() noexcept
{
    const uint32_t fcSpeed = flashChipSpeed();
    if(fcSpeed > 255000000UL) return 0;

    return static_cast<uint8_t>(fcSpeed / 1000000UL);
}


PGMCharPointer HWInfo::flashChipMode() noexcept
{
    static const char PROGMEM fcmQIO [] = "Quad I/O";
    static const char PROGMEM fcmQOUT[] = "Quad Output";
    static const char PROGMEM fcmDIO [] = "Dual I/O";
    static const char PROGMEM fcmDOUT[] = "Dual Output";

    switch( NodeMCU::getFlashChipMode() ) {
        case FlashChipMode::FCM_QIO  : return eCxx::PGM_PTR(fcmQIO                    ); break;
        case FlashChipMode::FCM_QOUT : return eCxx::PGM_PTR(fcmQOUT                   ); break;
        case FlashChipMode::FCM_DIO  : return eCxx::PGM_PTR(fcmDIO                    ); break;
        case FlashChipMode::FCM_DOUT : return eCxx::PGM_PTR(fcmDOUT                   ); break;
        default                      : return eCxx::PGM_PTR(Application::PGMStrUnknown); break;
    }
}


PGMCharPointer HWInfo::flashChipMap() noexcept
{
    static const char PROGMEM fcm_2M                [] = "FLASH_SIZE_2M "                 ": "   "256 kB (Map "  "256 kB + "    "0 kB)";
    static const char PROGMEM fcm_4M_MAP_256_256    [] = "FLASH_SIZE_4M_MAP_256_256 "     ": "   "512 kB (Map "  "256 kB + "  "256 kB)";
    static const char PROGMEM fcm_8M_MAP_512_512    [] = "FLASH_SIZE_8M_MAP_512_512 "     ": "  "1024 kB (Map "  "512 kB + "  "512 kB)";
    static const char PROGMEM fcm_16M_MAP_512_512   [] = "FLASH_SIZE_16M_MAP_512_512 "    ": "  "2048 kB (Map "  "512 kB + "  "512 kB)";
    static const char PROGMEM fcm_16M_MAP_1024_1024 [] = "FLASH_SIZE_16M_MAP_1024_1024 "  ": "  "2048 kB (Map " "1024 kB + " "1024 kB)";
    static const char PROGMEM fcm_32M_MAP_512_512   [] = "FLASH_SIZE_32M_MAP_512_512 "    ": "  "4096 kB (Map "  "512 kB + "  "512 kB)";
    static const char PROGMEM fcm_32M_MAP_1024_1024 [] = "FLASH_SIZE_32M_MAP_1024_1024 "  ": "  "4096 kB (Map " "1024 kB + " "1024 kB)";
    static const char PROGMEM fcm_32M_MAP_2048_2048 [] = "FLASH_SIZE_32M_MAP_2048_2048 "  ": "  "4096 kB (Map " "2048 kB + " "2048 kB)";
    static const char PROGMEM fcm_64M_MAP_1024_1024 [] = "FLASH_SIZE_64M_MAP_1024_1024 "  ": "  "8192 kB (Map " "1024 kB + " "1024 kB)";
    static const char PROGMEM fcm_128M_MAP_1024_1024[] = "FLASH_SIZE_128M_MAP_1024_1024 " ": " "16384 kB (Map " "1024 kB + " "1024 kB)";

    switch( system_get_flash_size_map() ) {
        case FLASH_SIZE_2M                 : return eCxx::PGM_PTR(fcm_2M                    ); break;
        case FLASH_SIZE_4M_MAP_256_256     : return eCxx::PGM_PTR(fcm_4M_MAP_256_256        ); break;
        case FLASH_SIZE_8M_MAP_512_512     : return eCxx::PGM_PTR(fcm_8M_MAP_512_512        ); break;
        case FLASH_SIZE_16M_MAP_512_512    : return eCxx::PGM_PTR(fcm_16M_MAP_512_512       ); break;
        case FLASH_SIZE_16M_MAP_1024_1024  : return eCxx::PGM_PTR(fcm_16M_MAP_1024_1024     ); break;
        case FLASH_SIZE_32M_MAP_512_512    : return eCxx::PGM_PTR(fcm_32M_MAP_512_512       ); break;
        case FLASH_SIZE_32M_MAP_1024_1024  : return eCxx::PGM_PTR(fcm_32M_MAP_1024_1024     ); break;
        case FLASH_SIZE_32M_MAP_2048_2048  : return eCxx::PGM_PTR(fcm_32M_MAP_2048_2048     ); break;
        case FLASH_SIZE_64M_MAP_1024_1024  : return eCxx::PGM_PTR(fcm_64M_MAP_1024_1024     ); break;
        case FLASH_SIZE_128M_MAP_1024_1024 : return eCxx::PGM_PTR(fcm_128M_MAP_1024_1024    ); break;
        default                            : return eCxx::PGM_PTR(Application::PGMStrUnknown); break;
    }
}


uint8_t HWInfo::bootVersion() noexcept
{ return system_get_boot_version(); }


const char* HWInfo::sdkVersion() noexcept
{ return system_get_sdk_version(); }


uint8_t HWInfo::time_tBitSize() noexcept
{ return sizeof(time_t) * 8; }


uint8_t HWInfo::bootMode() noexcept
{ return system_get_boot_mode(); }


PGMCharPointer HWInfo::resetReason() noexcept
{
    static const char PROGMEM rrPowerOn   [] = "Power On";
    static const char PROGMEM rrExternal  [] = "External Reset";
    static const char PROGMEM rrHWWatchdog[] = "Hardware Watchdog";
    static const char PROGMEM rrSWWatchdog[] = "Software Watchdog";
    static const char PROGMEM rrSWRestart [] = "Software Restart";
    static const char PROGMEM rrDSWake    [] = "Deep-Sleep Wake";
    static const char PROGMEM rrException [] = "Exception";

    switch(resetInfo.reason) {
        case REASON_DEFAULT_RST      : return eCxx::PGM_PTR(rrPowerOn                 ); break;
        case REASON_EXT_SYS_RST      : return eCxx::PGM_PTR(rrExternal                ); break;
        case REASON_WDT_RST          : return eCxx::PGM_PTR(rrHWWatchdog              ); break;
        case REASON_SOFT_WDT_RST     : return eCxx::PGM_PTR(rrSWWatchdog              ); break;
        case REASON_SOFT_RESTART     : return eCxx::PGM_PTR(rrSWRestart               ); break;
        case REASON_DEEP_SLEEP_AWAKE : return eCxx::PGM_PTR(rrDSWake                  ); break;
        case REASON_EXCEPTION_RST    : return eCxx::PGM_PTR(rrException               ); break;
        default                      : return eCxx::PGM_PTR(Application::PGMStrUnknown); break;
    }
}


const uint8_t* HWInfo::macAddress() noexcept
{
    static uint8_t _macAddress[6];

    if( Application::inWiFiStationMode() ) return WiFi.macAddress(_macAddress);

    return WiFi.softAPmacAddress(_macAddress);
}


} // namespace eCxx

