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


#ifndef ECXX_PLATFORM_NODEMCU_CORE_SYS_H
#define ECXX_PLATFORM_NODEMCU_CORE_SYS_H


#include <ets_sys.h>
#include <spi_flash.h>
#include <eboot/flash.h>
#include <eboot/spi_vendors.h>

#include <eCxx/eCxx_Internal.h>
#include <eCxx/Platform/Common/Application.h>
#include <eCxx/Platform/NodeMCU/ESP8266Peripheral.h>


//
// Macros
//

//
// Uncomment this to disable Puya flash support
//
// #define ECXX_NODEMCU_DISABLE_PUYA_SUPPORT


// Interrupt disable and enable
// Level 0-15 : interrupts of the given level and above will be enabled
// Level   15 : all interrupts will be disabled
// Level    0 : all interrupts will be enabled
#undef  xt_rsil
#define xt_rsil(LEVEL) ( __extension__ ( {  \
    uint32_t state;                         \
    __asm__ __volatile__ (                  \
        "rsil %0, " __ECXX_STRINGIFY(LEVEL) \
        : "=a"(state)                       \
        :                                   \
        : "memory"                          \
    );                                      \
    state;                                  \
} ) )

#undef  xt_wsr_ps
#define xt_wsr_ps(STATE)   \
    __asm__ __volatile__ ( \
        "wsr %0, ps \n\t"  \
        "isync      \n\t"  \
        :                  \
        : "a"(STATE)       \
        : "memory"         \
    )


// Preload instruction code into the flash cache
#undef  PRECACHE_ATTR
#define PRECACHE_ATTR __attribute__((optimize("no-reorder-blocks"))) __attribute__((noinline))

#undef  PRECACHE_START
#define PRECACHE_START(TAG)                                                                           \
    precache(                                                                                         \
        nullptr,                                                                                      \
        static_cast<uint8_t*>(&&_precache_end_##TAG) - static_cast<uint8_t*>(&&_precache_start_##TAG) \
    );                                                                                                \
    _precache_start_##TAG:

#undef  PRECACHE_END
#define PRECACHE_END(TAG) _precache_end_##TAG:


// Panic function
#undef  panic
#define panic() __panic_func( PGM_STR(__FILE__).p, __LINE__, __func__ )


// Flash HAL
#undef FS_PHYS_ADDR
#undef FS_PHYS_SIZE
#undef FS_PHYS_PAGE
#undef FS_PHYS_BLOCK

#define FS_PHYS_ADDR  ( reinterpret_cast<eCxx::uint32_t>(&_FS_start) - 0x40200000UL                                 )
#define FS_PHYS_SIZE  ( reinterpret_cast<eCxx::uint32_t>(&_FS_end  ) - reinterpret_cast<eCxx::uint32_t>(&_FS_start) )
#define FS_PHYS_PAGE  ( reinterpret_cast<eCxx::uint32_t>(&_FS_page )                                                )
#define FS_PHYS_BLOCK ( reinterpret_cast<eCxx::uint32_t>(&_FS_block)                                                )

#define FLASH_HAL_OK           (0)
#define FLASH_HAL_READ_ERROR  (-1)
#define FLASH_HAL_WRITE_ERROR (-2)
#define FLASH_HAL_ERASE_ERROR (-3)


// UART number
#define HW_UART_0   0
#define HW_UART_1   1
#define HW_UART_NA -1


// UART setting
#define UART_NB_BIT_8      0b00001100
#define UART_PARITY_NONE   0b00000000
#define UART_PARITY_EVEN   0b00000010
#define UART_PARITY_ODD    0b00000011
#define UART_NB_STOP_BIT_1 0b00010000
#define UART_NB_STOP_BIT_2 0b00110000


//
// Data structure for continuations support for Xtensa call0 ABI
//
// This structure is copied (with a slight modifications) from:
//     cont.h - continuations support for Xtensa call0 ABI
//     Copyright (C) 2014 Ivan Grokhotkov. All rights reserved.
//     GNU Lesser General Public License 2.1 or later
//
#define CONT_STACKSIZE 4096

typedef eCxx::Application::VoidFuncPtr ContCB;

struct ContData {
    // Do not change the order or the data type!
    ContCB    pcRet;
    unsigned* spRet;

    ContCB    pcYield;
    unsigned* spYield;

    unsigned* stackEnd;
    unsigned  unused1;
    unsigned  unused2;
    unsigned  stackGuard1;

    unsigned  stack[CONT_STACKSIZE / 4];

    unsigned  stackGuard2;
    unsigned* structStart;
};


//
// Flash chip mode
//
enum FlashChipMode {
     FCM_QIO     = 0x00,
     FCM_QOUT    = 0x01,
     FCM_DIO     = 0x02,
     FCM_DOUT    = 0x03,
     FCM_UNKNOWN = 0xFF
};


//
// Forward declaration of time structures
//
extern "C" {
    struct timeval;
    struct timezone;
}


//
// Function prototypes/variables with "C" linkage
//
extern "C" {

    // "eagle.flash.*.ld"
    extern eCxx::uint32_t _FS_start;
    extern eCxx::uint32_t _FS_end;
    extern eCxx::uint32_t _FS_page;
    extern eCxx::uint32_t _FS_block;

    // "SysCont.S"
    extern void contYield(ContData* cont);
    extern void contRun(ContData* cont, ContCB func);

    // "SysCont.cpp"
    extern ContData* g_ContData __ECXX_ATTR_SEC_NOINIT;

    extern void contInit(ContData*);
    extern void contRepaintStack(ContData *cont);
    extern eCxx::uint32_t contGetFreeStack(ContData* cont);

    extern bool ICACHE_RAM_ATTR contCanYield(ContData* cont);
    extern bool ICACHE_RAM_ATTR contCheck(ContData* cont);

    // "SysHeap.cpp"
    extern void enableVM();
    extern void setExternalHeap();
    extern void setIRAMHeap();
    extern void setDRAMHeap();
    extern void resetHeap();

    // "SysCrashDebug.cpp"
    extern void abort() __ECXX_ATTR_NORETURN;

    extern void __unhandled_exception(const char* str) __ECXX_ATTR_NORETURN;
    extern void __assert_func(const char* file, int line, const char* func, const char* what) __ECXX_ATTR_NORETURN;
    extern void __panic_func(const char* file, int line, const char* func) __ECXX_ATTR_NORETURN;

    extern void gdb_init();
    extern void gdb_do_break();
    extern bool gdb_present();

    extern bool gdbstub_has_putc1_control();
    extern void gdbstub_set_putc1_callback(void (*func)(char));
    extern bool gdbstub_has_uart_isr_control();
    extern void gdbstub_set_uart_isr_callback(void (*func)(void*, eCxx::uint8_t), void* arg);
    extern void gdbstub_write_char(char c);
    extern void gdbstub_write(const char* buf, size_t size);

    // "SysAppEntry.cpp"
    extern bool can_yield();
    extern void ICACHE_RAM_ATTR esp_schedule();
    extern void esp_yield();

    extern void yield();
    extern void optimistic_yield(eCxx::uint32_t intervalUS);

    extern void restart();

    // "SysTime.cpp"
    extern time_t time(time_t* t);
    extern eCxx::int32_t setTimeOfDay(const timeval* tv, const timezone* tz);
    extern eCxx::int32_t clockGetTime(struct timespec *tp);

} // extern "C"


namespace eCxx {

namespace NodeMCU {


//
// User-task related constants
//
static constexpr uint8_t UserQueueSize       = 1; // For now it is only used to call eCxx_user_task()
static constexpr uint8_t UserTaskPriority    = 1;

static constexpr uint8_t SoftTWIQueueSize    = 1; // For now it is only used by SoftTWIDriver class
static constexpr uint8_t SoftTWITaskPriority = 2;


//
// Callback function for settimeofday()
//
typedef eCxx::Application::VoidFuncPtr     SetTimeOfDayCB_Trivial;
typedef eCxx::Application::VoidFuncPtrBool SetTimeOfDayCB_Bool;


//
// Inline helper functions
//
static constexpr uint32_t clockCyclesPerMicrosecond() __ECXX_ATTR_ALWAYS_INLINE;
static constexpr uint32_t clockCyclesPerMicrosecond()
{ return Application::getCPUFrequency() / 1000000UL; }


static constexpr uint32_t clockCyclesToMicroseconds(uint32_t cc) __ECXX_ATTR_ALWAYS_INLINE;
static constexpr uint32_t clockCyclesToMicroseconds(uint32_t cc)
{ return cc / clockCyclesPerMicrosecond(); }


static constexpr uint32_t microsecondsToClockCycles(uint32_t us) __ECXX_ATTR_ALWAYS_INLINE;
static constexpr uint32_t microsecondsToClockCycles(uint32_t us)
{ return us * clockCyclesPerMicrosecond(); }


static inline ICACHE_RAM_ATTR uint32_t getCycleCount() __ECXX_ATTR_ALWAYS_INLINE;
static inline ICACHE_RAM_ATTR uint32_t getCycleCount()
{
    uint32_t cc;

    __asm__ __volatile__ (
        "esync          \n\t"
        "rsr %0, ccount \n\t"
        : "=a"(cc)
    );

    return cc;
}


static inline ICACHE_RAM_ATTR uint32_t getCycleCountIRQ() __ECXX_ATTR_ALWAYS_INLINE;
static inline ICACHE_RAM_ATTR uint32_t getCycleCountIRQ()
{
    uint32_t cc;

    __asm__ __volatile__ (
        "rsr %0, ccount \n\t"
        : "=a"(cc)
    );

    return cc;
}


static inline ICACHE_RAM_ATTR uint32_t getProgramCounter() __ECXX_ATTR_ALWAYS_INLINE;
static inline ICACHE_RAM_ATTR uint32_t getProgramCounter()
{
    uint32_t pc;

    __asm__ __volatile__ (
        "movi %0, . \n\t"
        : "=r"(pc)
    );

    return pc;
}


template <typename ValueT>
static inline bool addressInIRAM(ValueT val)
{
    // https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map
    return reinterpret_cast<uint32_t>(val) < 0x40200000;
}


//
// Function prototypes
//

// "SysPrecache.cpp"
extern void precache(void* f, uint32_t bytes);

// "SysFlashHAL.cpp"
extern FlashChipMode magicFlashChipMode(uint8_t byte);
extern uint32_t magicFlashChipSize(uint8_t byte);
extern uint32_t magicFlashChipSpeed(uint8_t byte);

extern uint32_t getFlashChipId();
extern uint8_t getFlashChipVendorId();
extern uint32_t getFlashChipRealSize();
extern uint32_t getFlashChipSize();
extern uint32_t getFlashChipSpeed();
extern FlashChipMode getFlashChipMode();

extern bool checkFlashConfig(bool needsEquals);
extern uint32_t getFlashUserProgramSize();
extern uint32_t getFlashUserProgramFreeSpace();

extern bool spiFlashEraseSector(uint32_t sector);

extern bool spiFlashRead(uint32_t address, uint32_t* data, size_t size);
extern bool spiFlashRead(uint32_t address, uint8_t* data, size_t size);
extern bool spiFlashWrite(uint32_t address, const uint32_t* data, size_t size);
extern bool spiFlashWrite(uint32_t address, const uint8_t* data, size_t size);

extern int32_t flashHalRead(uint32_t addr, uint32_t size, uint8_t* dst);
extern int32_t flashHalWrite(uint32_t addr, uint32_t size, const uint8_t* src);
extern int32_t flashHalErase(uint32_t addr, uint32_t size);

// "SysFlashQuirks.cpp"
extern void initFlashQuirks();

// "SysTime.cpp"
extern void setTimeOfDayCallback(const SetTimeOfDayCB_Trivial& cb);
extern void setTimeOfDayCallback(const SetTimeOfDayCB_Bool& cb);

extern void configTime(const char* tz, const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
extern void configTime(eCxx::PGMCharPointer tz, const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);

extern void configTime(eCxx::int32_t timezoneSec, eCxx::int32_t daylightOffsetSec, const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);


} // namespace NodeMCU

} // namespace eCxx


//
// Define aliases to std::nothrow_t and std::nothrow
//
namespace eCxx {

    // make asm CODE='#include <new> \n const void* test() { return &std::nothrow; }'
    using nothrow_t = std::nothrow_t;

    static const nothrow_t nothrow __ECXX_ATTR_WEAKREF("_ZSt7nothrow");

} // namespace eCxx


#endif
