/*
 * 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 "CoreLib.h"
#include "CoreSys.h"
#include "Schedule.h"


//
// Function prototypes for executing C++ global constructors
//
extern void (*__init_array_start)();
extern void (*__init_array_end)();


//
// Declare the eCxx user initialization and task functions
//
extern void eCxx_user_init();
extern void eCxx_user_task();


//
// External variables from "SysAppEntry.cpp"
//
extern rst_info       resetInfo;
extern eCxx::uint32_t cyclesAtYieldStart;


//
// User task initialization point of ESP8266/NodeMCU application
//
static void userTask_impl()
{
    // Initialize only at the first run
    static bool initDone = false;

    if(!initDone) {
        // Update the CPU frequency
        #if defined(F_CPU)
            #if F_CPU == 160000000UL
                eCxx::NodeMCU::updateCPUFrequency(SYS_CPU_160MHZ);
            #else
                eCxx::NodeMCU::updateCPUFrequency(SYS_CPU_80MHZ);
            #endif
        #else
            if(system_get_cpu_freq() == SYS_CPU_160MHZ) CPU2X |=  1UL;
            else                                        CPU2X &= ~1UL;
        #endif
        // Execute eCxx_user_init()
        eCxx_user_init();
        // Set flag
        initDone = true;
    }

    // Execute eCxx_user_task()
    eCxx_user_task();

    // Ensure scheduled functions are executed
    eCxx::NodeMCU::runScheduledFunctions();
    eCxx::NodeMCU::runScheduledRecurrentFunctions();

    // Ensure the user code are resumed at the next opportunity
    esp_schedule();
}


static void userTask(ETSEvent*)
{
    cyclesAtYieldStart = eCxx::NodeMCU::getCycleCountIRQ();

    resetHeap();

    contRun(g_ContData, &userTask_impl);

    setDRAMHeap();

    if( contCheck(g_ContData) ) panic();
}


static void initDoneCB()
{
    // Turn on output to UART
    system_set_os_print(1);
    gdb_init();

    // Abort on exception
    std::set_terminate(abort);

    // Execute global C++ constructors
    decltype(__init_array_end)* p = &__init_array_end;
    while(p != &__init_array_start) (*--p)();

    // Ensure the user code are resumed at the next opportunity
    esp_schedule();

    // Set heap
    setDRAMHeap();
}


extern "C" void user_init()
{
    // Event queue used by the main task
    static ETSEvent userTaskQueue[eCxx::NodeMCU::UserQueueSize];

    // Configure GPIO16 as input as early as possible
    GPF16  = GP16FFS( GPFFS_GPIO(16) );
    GPC16  =  0;
    GP16E &= ~1;

    // Copy the reset information
    memcpy( &resetInfo, system_get_rst_info(), sizeof(resetInfo) );

    // Restore the pin to Tx after power on and external reset
    if(resetInfo.reason >= REASON_EXCEPTION_RST && resetInfo.reason <= REASON_SOFT_RESTART) {
        eCxx::NodeMCU::pinMode(1, FUNCTION_0);
    }

    // Modify UART0 clock divider
    uart_div_modify(HW_UART_0, UART_CLK_FREQ / 115200);

    // Initialize user application
    eCxx::NodeMCU::initSystemAndTimer();
    eCxx::NodeMCU::initFlashQuirks();

    contInit(g_ContData);

#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP)
    install_non32xfer_exception_handler();
#endif

#if defined(MMU_IRAM_HEAP)
    umm_init_iram();
#endif

    ets_task(userTask, eCxx::NodeMCU::UserTaskPriority, userTaskQueue, eCxx::NodeMCU::UserQueueSize);
    system_init_done_cb(&initDoneCB);
}

