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


#ifndef INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_APPLICATION_CPP
#error "this file cannot be compiled directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_APPLICATION_CPP


namespace eCxx {


//
// A standard AVR application
//
bool     Application::_pwmEnabled;

#ifndef __AVR_ARCH_X__
uint8_t  Application::_adcReferenceVoltage;
#endif

uint16_t Application::_microsMultFactor;


void Application::halt() noexcept
{
    // Uninitialize UART
    uart().flush();
    uart().end();

    // Disable interrupt
    cli();

    // Ensure the activity indicator LED is configured as output and turn it off
    setPinMode(ActivityIndicatorLED_Pin, PinMode::Output);
    setActivityIndicatorLED(false);

    // Loop forever here
    for(;;) {
        // Blink the activity indicator LED to indicate that the system is halted;
        // here, delayMS() can be used as it does not use interrupt
        setActivityIndicatorLED(true ); delayMS(250);
        setActivityIndicatorLED(false); delayMS(250);
        setActivityIndicatorLED(true ); delayMS(250);
        setActivityIndicatorLED(false); delayMS(250);
                                        delayMS(500);
    }
}


void Application::softReset() noexcept
{
    // Uninitialize UART
    uart().flush();
    uart().end();

    // Disable interrupt
    cli();

#ifndef __AVR_ARCH_X__

    // Configure the watchdog timer to the smallest possible timeout
    wdt_enable(WDTO_15MS);

    // Loop forever until watchdog restart
    for(;;);

#else

    // Perform software reset
    _PROTECTED_WRITE(RSTCTRL.SWRR, RSTCTRL_SWRE_bm);

    // Loop forever until restart
    for(;;);

#endif
}


void Application::setActivityIndicatorLED(bool on)
{
    // The activity indicator (built-in) LED is active high
    // (it seems that all AVR-based Arduino boards have active high built-in LEDs)
    digitalWrite(ActivityIndicatorLED_Pin, on);
}


//
// A standard AVR application - non-volatile storage
//
#ifndef __AVR_ARCH_X__
static constexpr uint16_t HardwareEEPROMSize = E2END + 1;
#else
static constexpr uint16_t HardwareEEPROMSize = EEPROM_SIZE;
#endif


bool Application::nvStorageFormat()
{ return true; }


bool Application::nvStorageWrite(const void* src, uint16_t size, uint16_t offset)
{
    // Check the size and offset
    if( !size ) return true;
    if( size >= HardwareEEPROMSize || (offset + size) > HardwareEEPROMSize ) return false;

    // Check the EEPROM address
    if(offset + size >= E2END) return false;

    // Write the data
    while( !eeprom_is_ready() ) Application::yield();
    eeprom_update_block(src, reinterpret_cast<void*>(offset + 1), size);

    // The operation above should always be successful
    return true;
}


bool Application::nvStorageRead(void* dst, uint16_t size, uint16_t offset)
{
    // Check the size and offset
    if( !size ) return true;
    if( size >= HardwareEEPROMSize || (offset + size) > HardwareEEPROMSize ) return false;

    // Check the EEPROM address
    if(offset + size >= E2END) return false;

    // Read the data
    while( !eeprom_is_ready() ) Application::yield();
    eeprom_read_block(dst, reinterpret_cast<void*>(offset + 1), size);

    // The operation above should always be successful
    return true;
}


//
// A standard AVR application - event processing
//
uint32_t Application::processEvent(bool blinkActivityIndicatorLED_)
{
    // Yield
    Application::yield();

    // Call user callback(s) with interrupt disabled
    callScheduledAtomicUserCallback();

    // Blink the activity indicator LED to indicate activity and return the current millis()
    if(blinkActivityIndicatorLED_) {
        return blinkActivityIndicatorLEDAndGetMillis();
    }
    // Return the current millis() only
    else {
        return Application::millis();
    }
}


} // namespace eCxx


//
// A standard AVR application - entry point
//
extern void eCxx_user_init();
extern void eCxx_user_task();

int main() __ECXX_ATTR_WEAK;
int main()
{
    // Execute user initialization
    eCxx_user_init();

    // Execute user task
    for(;;) eCxx_user_task();

    return 0;
}
