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


#include <eCxx/eCxx_Server.h>


//
// Check the board
//
#ifndef ECXX_ESP01
#warning "this firmware is meant for ESP-01"
#endif


//
// For convenience
//
using App  = eCxx::Application;
auto& uart = App::uart();


//
// Pin number
//
static constexpr eCxx::uint8_t Reset = eCxx::NodeMCU::UART1_Tx; // GPIO2


//
// Servers
//
static eCxx::TCPServer tcpConsole(  23);
static eCxx::TCPServer tcpPUpload(2323);

static eCxx::WebServer webBConfig(  80);


//
// Baudrates
//
static uint32_t baudPUpload = 119200;
static uint32_t baudConsole = 119200;


//
// The init function
//
void eCxx_user_init()
{
    // ##### Initialize application #####
    const App::Config config(
        PGM_STR("ESP-01 AVR Uploader"),
        PGM_STR("esp-01-avr-uploader"),
        11,
        eCxx::makeArray<eCxx::uint8_t, 0x1A, 0xFE, 0x34, 0xC2, 0x32, 0xAA>(),
        PGM_STR("ota$*"  ),
        PGM_STR("esp01au")
    );

    App::initialize(config);

    // ##### Intialize TCP servers #####
    tcpConsole.begin();
    tcpPUpload.begin();

    // ##### Initialize baudrate-configuration web server #####
    webBConfig.onNotFound( []() {
        // Send response
        webBConfig.send(404, "text/plain", "Not found");
    } );

    // Set program-uploading or console baudrate, for example:
    //     http://esp01au.local/upload/baud?rate=57600
    //     http://esp01au.local/console/baud?rate=9600
    webBConfig.on( UriBraces("/{}/baud"), []() {
        // Check the path
        const bool sbrPUpload = (webBConfig.pathArgValue(0) == "upload" );
        const bool sbrConsole = (webBConfig.pathArgValue(0) == "console");
        // Send response
        if(!sbrPUpload && !sbrConsole) {
            webBConfig.send(404, "text/plain", "Not found");
            return;
        }
        webBConfig.send(200, "text/plain", "OK");
        // Process the parameters
        for(eCxx::uint8_t i = 0; i < webBConfig.argCount(); i++) {
            // Ignore unsupported argument(s)
            if(webBConfig.argName(i) != "rate") continue;
            // Read and check the new baudrate
            uint32_t newBaudrate = atoi( webBConfig.argValue(i).c_str() );
            if(!newBaudrate || newBaudrate > 115200) newBaudrate = 115200;
            // Set the new baudrate
            if(sbrPUpload) baudPUpload = newBaudrate;
            if(sbrConsole) baudConsole = newBaudrate;
            break;
        }
    } );

    webBConfig.begin();

    // Configure the reset pin to be used to reset the attached AVR MCU
    App::setPinMode  (Reset, eCxx::Application::PinMode::Output     );
    App::digitalWrite(Reset, true                                   );
    App::setPinMode  (Reset, eCxx::Application::PinMode::InputPullUp);
}


//
// The task function
//
void eCxx_user_task()
{
    // ##### Process event #####
    eCxx::Application::processEvent();

    // ##### Handle the web server client #####
    webBConfig.handleClient();

    // ##### Handle the TCP clients #####
    eCxx::TCPClient&& pUpload   = tcpPUpload.handleClient();
    eCxx::TCPClient&& console   = tcpConsole.handleClient();

    eCxx::TCPClient*  tcpClient = nullptr;

    // Check for TCP program-uploading client
    if( pUpload.connected() ) {
        // Save a pointer to the active TCP client
        tcpClient = &pUpload;
        // If the TCP console client is also connected, disconnect it
        if( console.connected() ) console.end();
        // (Re-)configure the UART as needed
        uart.begin(baudPUpload);
    }
    // Check for TCP console client
    else if( console.connected() ) {
        // Save a pointer to the active TCP client
        tcpClient = &console;
        // (Re-)configure the UART as needed
        uart.begin(baudConsole);
        // Ensure that the connection will be kept alive a long as possible
        tcpClient->setKeepAlive();
    }

    // Exit here if there is no client connected
    if(!tcpClient) return;

    // Yield
    App::yield();

    // Reset the attached AVR MCU
    App::setPinMode  (Reset, eCxx::Application::PinMode::Output     );
    App::digitalWrite(Reset, true                                   );
    App::digitalWrite(Reset, false                                  );
    App::delayMS     (25                                            );
    App::digitalWrite(Reset, true                                   );
    App::setPinMode  (Reset, eCxx::Application::PinMode::InputPullUp);

    // Perform WiFi-serial pass-through
    // (this loop will block other non-essential network handling, including OTA programming of this ESP-01!)
    while( tcpClient->connected() ) {
        // Read from the TCP client and write to UART
        if( tcpClient->canRead() ) {
            eCxx::uint8_t c;
            tcpClient->read(c);
            uart.write(c);
        }
        // Read from UART and write to the TCP client
        if( uart.canRead() ) {
            eCxx::uint8_t c;
            uart.read(c);
            tcpClient->write(c);
        }
        // Yield
        App::yield();
    };

    // Disconnect the TCP client
    tcpClient->end();
}
