/*
 * 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 a few modifications) from:
 *     core_esp8266_wiring.cpp - wiring API implementation for esp8266
 * ----------------------------------------------------------------------------------
 * 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"


namespace eCxx {

namespace NodeMCU {


//
// updateCPUFrequency()
//
extern void updateCPUFrequency(uint8_t freq)
{
    if(freq == 160) {
        ets_update_cpu_frequency(160);
        CPU2X |= 1UL;
    }
    else {
        ets_update_cpu_frequency(80);
        CPU2X &= ~1UL;
    }
}


//
// delay() and delayMicroseconds()
//
extern void delay(unsigned long ms)
{
    static os_timer_t delayTimer;

    if(ms) {
        os_timer_setfn(
            &delayTimer,
            +[](void*) { esp_schedule(); },
            0
        );
        os_timer_arm(&delayTimer, ms, false);
    }
    else {
        esp_schedule();
    }

    esp_yield();

    if(ms) os_timer_disarm(&delayTimer);
}


extern void ICACHE_RAM_ATTR delayMicroseconds(uint32_t us)
{ os_delay_us(us); }


//
// millis(), micros(), and micros64()
//
static uint32_t microsAtLastOverflowTick = 0;
static uint32_t microsOverflowCount      = 0;


extern unsigned long ICACHE_RAM_ATTR millis()
{
    // See the original code (core_esp8266_wiring.cpp) for an explanation about how this function works

    constexpr uint64_t MAGIC_1E3_LO = 0x4BC6A7F0ULL;
    constexpr uint64_t MAGIC_1E3_HI = 0x00418937ULL;

    union {

        uint64_t q;
        uint32_t a[2];

    } acc = { 0 };

    const uint32_t m = system_get_time();
    const uint32_t c = microsOverflowCount + ( ( m < microsAtLastOverflowTick ) ? 1 : 0 );

    acc.q  = ( ( m * MAGIC_1E3_LO ) >> 32 );
    acc.q +=   ( m * MAGIC_1E3_HI );
    acc.q +=   ( c * MAGIC_1E3_LO );

    return acc.a[1] + ( c * MAGIC_1E3_HI );
}


extern unsigned long ICACHE_RAM_ATTR micros()
{ return system_get_time(); }


extern uint64_t ICACHE_RAM_ATTR micros64()
{
    const uint32_t low32  = system_get_time();
    const uint32_t high32 = microsOverflowCount + ( ( low32 < microsAtLastOverflowTick ) ? 1 : 0 );

    return ( static_cast<uint64_t>(high32) << 32 ) | low32;
}


//
// initSystemAndTimer()
//
extern void initSystemAndTimer()
{
    // Turn off printing and disable UART interrupts
    system_set_os_print(0);
    U0IE = 0;
    U1IE = 0;

    // Reset pins
    for(uint8_t i = 0; i <= 16; ++i) {
        if( !userGPIOPin<false>(i) ) continue;
        NodeMCU::pinMode(i, INPUT);
    }

    // Initialize Timer1 and the timing system
    static os_timer_t microsOverflowTimer;

    NodeMCU::timer1InitISR();

    os_timer_setfn(
        &microsOverflowTimer,
        +[](void*) {
            const uint32_t m = system_get_time();
            if(m < microsAtLastOverflowTick) ++microsOverflowCount;
            microsAtLastOverflowTick = m;
        },
        0
    );

    os_timer_arm(&microsOverflowTimer, 60000, true);
}


} // namespace NodeMCU

} // namespace eCxx


//
// Function aliases with "C" linkage for compatibility with the Arduino library part
//
extern "C" {

    // make mangle CODE='namespace eCxx { namespace NodeMCU { void delay(unsigned long ms) {} } }'
    extern void delay(unsigned long ms) __ECXX_ATTR_ALIAS("_ZN4eCxx7NodeMCU5delayEm");

    // make mangle CODE='namespace eCxx { namespace NodeMCU { unsigned long millis() {} } }'
    extern unsigned long millis() __ECXX_ATTR_ALIAS("_ZN4eCxx7NodeMCU6millisEv");

    // make mangle CODE='namespace eCxx { namespace NodeMCU { unsigned long micros() {} } }'
    extern unsigned long micros() __ECXX_ATTR_ALIAS("_ZN4eCxx7NodeMCU6microsEv");

    // make mangle CODE='namespace eCxx { namespace NodeMCU { unsigned long long micros64() {} } }'
    extern eCxx::uint64_t micros64() __ECXX_ATTR_ALIAS("_ZN4eCxx7NodeMCU8micros64Ev");

} // extern "C"
