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

/*
 * If you are not using a bootloader, please set the correct fuses for this firmware using:
 *
 *     avrdude -c usbasp -B 1 -p atmega32u4 -e -U lfuse:w:0xFF:m -U hfuse:w:0xDF:m -U efuse:w:0xCB:m -U lock:w:0x3F:m
 */

//
// Firmware for the JTAG2UPDI and serial bridge board (JTAG2UPDI_Serial_Bridge_Micro_ProMini):
//
//     # Arduino Micro    : Bootloader : 3rd_party/bootloaders/caterina/Caterina-ATmega32U4-16MHz.hex
//                          Firmware   : this file
//
//     # Arduino Pro-Mini : Bootloader : none
//                          Firmware   : 3rd_party/src/jtag2updi/JTAG2UPDI-ATmega328P-16MHz-TYPE1-LEDB5.hex
//


#include <eCxx/eCxx.h>
#include <eCxx/eCxx_InputDevice.h>


//
// Check the MCU
//
#if ( !defined(__AVR_ATmega32U4__) ) || ( (F_CPU) != 16000000UL )
#warning "this firmware is meant for ATmega32U4 at 16 MHz"
#endif


//
// For convenience
//
using App = eCxx::Application;


//
// Pin numbers
//
static constexpr eCxx::uint8_t I_MODE_SEL   = 23; // Mode selection

static constexpr eCxx::uint8_t O_LED_SERIAL = 21; // Indicator for target-serial    bridge mode
static constexpr eCxx::uint8_t O_LED_UPDI   = 22; // Indicator for JTAG2UPDI-serial bridge mode

static constexpr eCxx::uint8_t O_UPDI_SEC   = 11; // Secondary UPDI I/O - OC0A (unused in this firmware; always low)

static constexpr eCxx::uint8_t O_RST_PROG   = 12; // Reset the JTAG2UPDI programmer
static constexpr eCxx::uint8_t O_RST_TARGET = 17; // Reset the target


//
// Default baudrates
//
static constexpr eCxx::uint32_t DEF_BAUDRATE_TARGET = 57600UL; // Default baudrate for target-serial    bridge mode
static constexpr eCxx::uint32_t DEF_BAUDRATE_UPDI   = 19200UL; // Default baudrate for JTAG2UPDI-serial bridge mode


//
// State variables
//
#ifdef ECXX_AVR_HAVE_HARDWARE_USB

static bool                  resetTarget = false;

static eCxx::uint32_t        curBaudrate = DEF_BAUDRATE_TARGET;
static eCxx::uint32_t        newBaudrate = 0;

static bool                  bridgeMode  = true; // false = target-serial bridge ; true = JTAG2UPDI-serial bridge

static eCxx::Debounce8Switch db8sw;

#endif


//
// Helper functions
//
#ifdef ECXX_AVR_HAVE_HARDWARE_USB


static inline void setBaudrate(uint32_t baudrate)
{
    if(baudrate == curBaudrate) return;

    curBaudrate = baudrate;
    App::uart1()->changeBaudrate(curBaudrate);
}


static inline void setMode_TargetSerialBridge()
{
    // Check and change the mode
    if(!bridgeMode) return;
    bridgeMode = false;

    // Set the indicator LEDs
    App::digitalWrite(O_LED_SERIAL, true );
    App::digitalWrite(O_LED_UPDI,   false);

    // Set to the default baudrate
    setBaudrate(DEF_BAUDRATE_TARGET);

    // Deactivate the JTAG2UPDI board (keep it in reset state)
    App::digitalWrite(O_RST_PROG, false);
}


static inline void setMode_JTAG2UPDIBridge()
{
    // Check and change the mode
    if(bridgeMode) return;
    bridgeMode = true;

    // Set the indicator LEDs
    App::digitalWrite(O_LED_SERIAL, false);
    App::digitalWrite(O_LED_UPDI,   true );

    // Set to the default baudrate
    setBaudrate(DEF_BAUDRATE_UPDI);

    // Activate the JTAG2UPDI board
    App::digitalWrite(O_RST_PROG, true);
}


#endif


//
// The init function
//
void eCxx_user_init()
{
    // Initialize application
    const App::Config config( false, App::Config::WDTO::_250mS );

    App::initialize(config);

#ifdef ECXX_AVR_HAVE_HARDWARE_USB

    // Initialize the input pin
    App::setPinMode(I_MODE_SEL,   App::PinMode::InputPullUp);

    // Initialize the output pins
    App::setPinMode(O_LED_SERIAL, App::PinMode::Output);
    App::setPinMode(O_LED_UPDI,   App::PinMode::Output);
    App::setPinMode(O_UPDI_SEC,   App::PinMode::Output);
    App::setPinMode(O_RST_PROG,   App::PinMode::Output);
    App::setPinMode(O_RST_TARGET, App::PinMode::Output);

    App::digitalWrite(O_LED_SERIAL, false);
    App::digitalWrite(O_LED_UPDI,   false);
    App::digitalWrite(O_UPDI_SEC,   false);
    App::digitalWrite(O_RST_PROG,   true );
    App::digitalWrite(O_RST_TARGET, true );

    // Initialize virtual UART #1 for debugging information
    App::vuart1()->begin(0);

    // Initialize virtual UART #0 for main communication and set the callbacks
    App::vuart0()->begin(0);

    App::vuart0DtrLowCallback( +[]() {
        resetTarget = true;
    } );

    App::vuart0ChangeSettingsCallback( +[]() {
        newBaudrate = App::vuart0GetBaudrate();
    } );

    // Initialize hardware UART #1
    App::uart1()->begin(curBaudrate);

    // Set the initial mode to target-serial bridge
    setMode_TargetSerialBridge();

#endif
}


//
// The task function
//
void eCxx_user_task()
{
    // Process event
    const eCxx::uint32_t curMillis = App::processEvent();

#ifndef ECXX_AVR_HAVE_HARDWARE_USB

    eCxx::ignoreUnused(curMillis);

#else

    // For convenience
    eCxx::uint8_t c;
    auto&         vuart = *App::vuart0();
    auto&         uart1 = *App::uart1 ();

    // Reset the target as needed
    if(resetTarget) {
        // Print debugging information
        App::vuart1()->writeStringln("Reset target");
        // Reset target
        App::digitalWrite(O_RST_TARGET, false);
        App::delayMS     (25                 );
        App::digitalWrite(O_RST_TARGET, true );
        resetTarget = false;
        // Yield
        App::yield();
    }

    // Change the hardware UART baudrate as needed
    if(newBaudrate) {
        // Print debugging information
        App::vuart1()->writeString     ("New baudrate = ");
        App::vuart1()->writeDecStringln(newBaudrate      );
        // Change baudrate
        setBaudrate(newBaudrate);
        newBaudrate = 0;
        // Yield
        App::yield();
    }

    // Perform serial pass-through
    while( vuart.canRead() || uart1.canRead() ) {
        // Read from the virtual UART and write to the hardware UART
        if( vuart.canRead() ) {
            vuart.read(c);
            uart1.write(c);
        }
        // Read from UART and write to the TCP client
        if( uart1.canRead() ) {
            uart1.read(c);
            vuart.write(c);
        }
        // Yield
        App::yield();
    }

    // Process keypress
    static eCxx::uint32_t prvMillis = 0;
    if(curMillis - prvMillis < 10) return;
    else                           prvMillis = curMillis;

    if( App::digitalRead(I_MODE_SEL) ) db8sw.debounce(0b00000000); // The switch is active low
    else                               db8sw.debounce(0b00000001);

    if( db8sw.pressed(0b00000001) ) {
        if(bridgeMode) setMode_TargetSerialBridge();
        else           setMode_JTAG2UPDIBridge   ();
    }

#endif
}
