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

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with some modifications) from:
 *     core_esp8266_main.cpp - platform initialization and context switching
 *                             emulation
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2014 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 <user_interface.h>
#include <umm_malloc/umm_malloc.h>

#include "CoreSys.h"
#include "Schedule.h"


//
// Function prototypes with "C" linkage
//
extern "C" bool ets_post_rom(eCxx::uint8_t prio, ETSSignal sig, ETSParam par);
extern "C" void call_user_start();


//
// Exported variables
//
rst_info       resetInfo;          // Reset information
eCxx::uint32_t cyclesAtYieldStart; // Used to implement optimistic_yield()


// Save and restore the PS state across the ets_post_rom() call because the ROM code
// does not implement this correctly
extern "C" bool ICACHE_RAM_ATTR ets_post(eCxx::uint8_t prio, ETSSignal sig, ETSParam par)
{
    eCxx::uint32_t saved;

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

    const bool res = ets_post_rom(prio, sig, par);

    xt_wsr_ps(saved);

    return res;
}


//
// Replace ets_intr_lock() and ets_intr_unlock() with nestable versions
//
static constexpr eCxx::uint8_t MaxETSIntrLockNest = 7;

static eCxx::uint16_t etsIntrLockStack[MaxETSIntrLockNest];
static eCxx::uint8_t  etsIntrLockStackPtr = 0;


extern "C" void ICACHE_RAM_ATTR ets_intr_lock()
{
    if(etsIntrLockStackPtr < MaxETSIntrLockNest)
        etsIntrLockStack[etsIntrLockStackPtr++] = xt_rsil(3);
    else
        xt_rsil(3);
}


extern "C" void ICACHE_RAM_ATTR ets_intr_unlock()
{
    if(etsIntrLockStackPtr > 0)
        xt_wsr_ps(etsIntrLockStack[--etsIntrLockStackPtr]);
    else
        xt_rsil(0);
}


//
// yield() and optimistic_yield()
//
static inline void esp_yield_within_cont() __ECXX_ATTR_ALWAYS_INLINE;
static inline void esp_yield_within_cont()
{
    contYield(g_ContData);

    cyclesAtYieldStart = eCxx::NodeMCU::getCycleCountIRQ();

    eCxx::NodeMCU::runScheduledRecurrentFunctions();
}


extern "C" bool can_yield()
{ return contCanYield(g_ContData); }


extern "C" void ICACHE_RAM_ATTR esp_schedule()
{ ets_post(eCxx::NodeMCU::UserTaskPriority, 0, 0); }


extern "C" void esp_yield()
{ if( can_yield() ) esp_yield_within_cont(); }


extern "C" void yield()
{
    if( can_yield() ) {
        esp_schedule();
        esp_yield_within_cont();
    }
    else {
        panic();
    }
}


extern "C" void optimistic_yield(eCxx::uint32_t intervalUS)
{
    const eCxx::uint32_t intervalCycles = intervalUS * eCxx::NodeMCU::clockCyclesPerMicrosecond();

    if( ( eCxx::NodeMCU::getCycleCountIRQ() - cyclesAtYieldStart ) > intervalCycles && can_yield() ) yield();
}


extern "C" void restart()
{
    system_restart();
    esp_yield();
}


//
// Primary entry point of ESP8266/NodeMCU application
//
extern "C" void app_entry()
{
    umm_init();

    // Allocate continuation context on this SYS stack and save a pointer to it
    ContData __ECXX_ATTR_ALIGNED_16 s_cont;
    g_ContData = &s_cont;

    // Call the entry point of the SDK code
    call_user_start();
}
