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


#include <eCxx/eCxx_WebServerStreamSlotFS.h>

#include "NodeMCU_Common.h"


//
// Instantiate a slot-based file system
//
static eCxx::SlotFS slotFS;

static const char* fileName = "WebServerFile.txt";


//
// The web server
//
static eCxx::WebServer webServer(80);


//
// The init function
//
void eCxx_user_init()
{
    // Initialize application
    initBasicNodeMCUApp();

    // Enable the 74LVC541 buffers on the eCxx NodeMCU development board because
    // the SPI pins are buffered
    App::setPinMode  ( eCxx::NodeMCU::D0, eCxx::Application::PinMode::Output ); // GPIO16
    App::digitalWrite( eCxx::NodeMCU::D0, false                              ); // The buffers are active low

    // Initialize UART
    auto& u = App::uart();
    u.begin(115200);
    u.write('\n');

    //
    // ===== Initialize the file that will be served via the web server =====
    //

    // Initialize the slot-based file system
    u.writeString( PGM_STR("Initializing SPI, memory card, and SlotFS ... ") );
    if( slotFS.begin() != eCxx::RawMemoryCard::RMCResultCode::NoError ) {
        u.writeStringln( PGM_STR("ERROR slotFS.begin()") );
        App::halt();
    }
    u.writeStringln("OK \n");
    App::yield();

    // Try to mount and format as needed
    u.writeString( PGM_STR("Mounting memory card ... ") );
    if( !slotFS.mount() ) {
        // Write message
        u.writeStringln( PGM_STR("ERROR slotFS.mount()") );
        // Format
        u.writeString( PGM_STR("Formatting memory card ... [") );
        const bool formatResult = slotFS.format(
            PGM_STR("=== Formatted by eCxx WebServerSlotFS Test Application ==="),
            [&u](eCxx::uint16_t p) {
                     if(p == 0x0000) u.write('0');
                else if(p == 0xFFFF) u.write('#');
                else                 u.write('-');
            }
        );
        u.write(']');
        u.write(' ');
        if(!formatResult) {
            u.writeStringln( PGM_STR("ERROR slotFS.format()") );
            App::halt();
        }
        // Try mount again
        u.writeString( PGM_STR("Mounting memory card ... ") );
        if( !slotFS.mount() ) {
            u.writeStringln( PGM_STR("ERROR slotFS.mount()") );
            App::halt();
        }
    }
    u.writeStringln("OK \n");
    App::yield();

    // Create the file if it does not exist yet
    const eCxx::uint16_t slotNumber = slotFS.findFirstSlotNumberByUserID(fileName);

    if(slotNumber == eCxx::SlotFS::InvalidSlotNumber) {
        // Write message
        u.writeString( PGM_STR("Creating the file that will be served via the web server ... ") );
        App::yield();
        // Create and open the new file for writing
        eCxx::SlotFS::SlotHandle handle = slotFS.slotWriteNew(fileName, "");
        if(!handle) {
            u.writeStringln( PGM_STR("ERROR slotFS.slotWriteNew()") );
            App::halt();
        }
        App::yield();
        // Write the content
        const uint8_t content[] = "Line 1\r\nLine 2\r\nLine 3\r\n";
        if( !slotFS.slotWritePut( handle, content, sizeof(content) - 1 ) ) { // Do not write the null terminator
            u.writeStringln( PGM_STR("ERROR slotFS.slotWritePut()") );
            App::halt();
        }
        App::yield();
        // Close the file
        if( !slotFS.slotClose(handle) ) {
            u.writeStringln( PGM_STR("ERROR slotFS.slotClose()") );
            App::halt();
        }
        // Done
        u.writeStringln("OK \n");
        App::yield();
    }

    //
    // ===== Intialize the web server =====
    //
    webServer.onNotFound( [&u]() {
        // Send response
        webServer.send(404, "text/plain", "Not found");
        // Write to UART
        u.writeStringln( PGM_STR("\nwebServer.onNotFound()") );
        u.writeStringln( webServer.uri()                     );
    } );

    webServer.on( "/", [&u]() {
        // Write to UART
        u.writeString( PGM_STR("\nServing file ... ") );
        // Serve the file
              eCxx::uint32_t fileSize = 0;
        const eCxx::uint32_t sentSize = eCxx::streamFileToWebServerFromSlotFS(
                                            webServer,
                                            slotFS,
                                            fileName,
                                            "text/plain",
                                            eCxx::HTTPRequestMethod::Get,
                                            &fileSize
                                        );
        // Check for error condition
        if( sentSize == eCxx::NumericLimits<uint32_t>::max() ) {
            // Send error response
            webServer.send(500, "text/plain", "Internal Server Error");
            // Write to UART
            u.writeStringln( PGM_STR("ERROR streamFileToWebServerFromSlotFS() - serve file error ") );
        }
        // Check for error condition
        else if(!sentSize) {
            // Write to UART
            u.writeStringln( PGM_STR("ERROR streamFileToWebServerFromSlotFS() - zero length") );
            // It should not happen, so halt the application
            App::halt();
        }
        // Check for error condition
        else if(sentSize < fileSize) {
            // Write to UART
            u.writeStringln( PGM_STR("ERROR streamFileToWebServerFromSlotFS() - truncated length") );
            // It should not happen, so halt the application
            App::halt();
        }
        // Done
        else {
            u.write('[');
            u.writeDecString(sentSize);
            u.writeStringln( PGM_STR(" bytes] OK") );
        }
    } );

    webServer.begin();
}


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

    // Handle the web server client
    webServer.handleClient();
}
