/**
 ** termserv -- A terminal server application for Arduino Due.
 ** Copyright (C) 2022-2023
 **
 **   Cornelius Keck (dfwrpi@keck.us)
 **  
 ** This file is part of the Arduino Due Terminal Server.
 ** The Terminal server   is free software: you can redistribute it and/or modify
 ** it under  the  terms of  the GNU  General  Public  License  as  published  by
 ** the  Free Software Foundation, either  version  3  of  the  License,  or  any
 ** later version.
 ** 
 ** The  soft_uart 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  General
 ** Public License for more details.
 ** 
 ** You should have received a copy  (COPYING file) of  the  GNU  General  Public
 ** License along with the soft_uart library.
 ** If not, see: <http://www.gnu.org/licenses/>.
 **/

/*
 * File: termserv.ino
 * Description:  This  is  an  implementation  of  a  terminal server app for the
 * Arduino  Due's  Atmel ATSAM3X8E micro-controller, as well as for  the  Arduino
 * Mega 2560.
 * 
 * Date: Dec 1st, 2022
 * Author: Cornelius Keck <dfwrpi@keck.us>
 */


#include "Arduino.h"

#ifdef ARDUINO_ARCH_SAM

#include "soft_uart.h"

#ifndef RX_BUF_LENGTH
#define RX_BUF_LENGTH (1024)
#endif

#ifndef TX_BUF_LENGTH
#define TX_BUF_LENGTH (1024)
#endif

#ifndef SOFT_UART_BPS
#define SOFT_UART_BPS (9600)
#endif

serial_tc0_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);
serial_tc1_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);
serial_tc2_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);
serial_tc3_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);
serial_tc4_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);
serial_tc5_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);

#ifdef PatchSerial

serial_tc6_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);
serial_tc7_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);
serial_tc8_declaration(RX_BUF_LENGTH,TX_BUF_LENGTH);

#endif


#endif

int blink = 0;

#ifndef ARDUINO_ARCH_SAM
typedef int time_t;
#endif

time_t lastTime = 0;
int sentSerialHello = 0;
int sentUsbHello = 0;

char sbuf[1024];

/*
 * non-zero if we have a command prefix, but
 * not the command just yet.
 */

int waitForSerialCmd, waitForUsbCmd;

/*
 * which port are the programming and native
 * ports talking to.
 */

HardwareSerial *serialOtherSide, *usbOtherSide;

/*
 * not exactly elegant, but comes in handy when
 * distributing console output to USB clients.
 */

HardwareSerial *cPort[16];

void
setup()
{
    int i;
    // put your setup code here, to run once:
    Serial.begin(115200);
    Serial1.begin(115200);
    Serial2.begin(115200);
    Serial3.begin(115200);
    pinMode(LED_BUILTIN, OUTPUT);
    lastTime = millis()/1000;
    waitForSerialCmd = waitForUsbCmd = sentSerialHello = sentUsbHello= 0;
    serialOtherSide = &Serial1;
    
#ifdef ARDUINO_ARCH_SAM
    SerialUSB.begin(115200);
    usbOtherSide = &Serial2;
    /* usbOtherSide = &serial_tc0; */
    /* serialOtherSide = &serial_tc0; */
    
    serial_tc0.begin(22,23,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
    serial_tc1.begin(24,25,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
    serial_tc2.begin(26,27,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
    serial_tc3.begin(28,29,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
    serial_tc4.begin(30,31,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
    serial_tc5.begin(32,33,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
#ifdef PatchSerial
    
    serial_tc6.begin(34,35,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
    serial_tc7.begin(36,37,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
    serial_tc8.begin(38,39,SOFT_UART_BPS,
                     soft_uart::data_bit_codes::EIGHT_BITS,
                     soft_uart::parity_codes::NO_PARITY,
                     soft_uart::stop_bit_codes::ONE_STOP_BIT);
    
#endif
    
#endif
    
    for (i=0;i<16;i++)
        cPort[i] = 0;
    
    cPort[0] = &Serial1;
    cPort[1] = &Serial2;
    cPort[2] = &Serial3;
    
#ifdef ARDUINO_ARCH_SAM
    
    cPort[3] = &serial_tc0;
    cPort[4] = &serial_tc1;
    cPort[5] = &serial_tc2;
    cPort[6] = &serial_tc3;
    cPort[7] = &serial_tc4;
    cPort[8] = &serial_tc5;

#ifdef PatchSerial
    
    cPort[9] = &serial_tc6;
    cPort[10]= &serial_tc7;
    cPort[11]= &serial_tc8;

#endif
    
#endif
}

void
help(void)
{
    Serial.println("\r\nHelp:");
    Serial.println("\tCommand excape is 0x00, i.e. ctrl-@ == ctrl-shift-2\r");
    
#ifdef ARDUINO_ARCH_SAM
    
#ifdef PatchSerial
    
    Serial.println("\tctrl-@ 1 through 9, a, b or c: switch to that serial port (a: 10, b: 11, c: 12)\r");

#else
    
    Serial.println("\tctrl-@ 1 through 9: switch to that serial port");

#endif
    
#else
    
    Serial.println("\tctrl-@ 1 through 3: switch to that serial port\r");
    
#endif
    
    Serial.println("\tctrl-@ ?: this.\r\n");
}

void
execCmd(char cmd, int isUsb, HardwareSerial **otherSide)
{
#ifdef ARDUINO_ARCH_SAM
    int maxCmd = '9';
#else
    int maxCmd = '3';
#endif
    
    int portIndex = cmd - '1' + 1;
    
    if (cmd >= 'A' && cmd <= 'Z')
        cmd = cmd - 'A' + 'a';
    
#ifdef ARDUINO_ARCH_SAM
    if ((cmd >= '1' && cmd <= maxCmd)
        || (cmd >= 'a' && cmd <= 'c'))
#else
    if (cmd >= '1' && cmd <= maxCmd)
#endif
    {
        if (cmd < 'a')
        {
            *otherSide = cPort[cmd - '1'];
            portIndex = cmd - '1' + 1;
        }
        
#ifdef PatchSerial
        
        else
        {
            *otherSide = cPort[cmd - 'a' + 10];
            portIndex = cmd - 'a' + 10;
        }
#endif
        
        
#ifdef ARDUINO_ARCH_SAM
        if (isUsb)
        {
            SerialUSB.print("\r\n...looking at Serial");
            SerialUSB.println(portIndex);
            SerialUSB.print("\r");
            return;
        }
#endif
        Serial.print("\r\n...looking at Serial");
        Serial.println(portIndex);
        Serial.print("\r");
    }
    else 
        help();
}


int
findAndExecCmd(char *buf, int nc, int isUsb, int *waitForCmd, HardwareSerial **otherSide)
{
    int i;
    
    if (nc > 0)
    {
        if (waitForCmd)
        {
            
            /*
             *during the previous run, did we end up with a command
             * escape, but no command? If so, then the first char is
             * the command. Act on it, remove from input stream.
             */
            if (*waitForCmd)
            {
                execCmd(buf[0],isUsb,otherSide);
                nc--;
                memcpy(&buf[0],&buf[1],nc);
                *waitForCmd = 0;
            }
            
            /* look for ^@ == 0x00 */
            for (i=0;i<nc;i++)
            {
                if (!buf[i])
                {
                    /*
                     * we have a command char. if there are no more
                     * characters waiting, set a flag for next time
                     * round. Otherwise get that command, act on it,
                     * remove it from input buffer.
                     */
                    if (i==nc-1)
                    {
                        /*
                         * we have command escape, but not the command
                         * itself. Set flag, adjust character count,
                         * done.
                         */
                        *waitForCmd = 1;
                        nc--;
                        continue;
                    }
                    
                    /*
                     * we have more bytes to look at. Grab command,
                     * act on it, remove command escape and command
                     * from input, then look for more cmds.
                     */
                    execCmd(buf[i+1],isUsb,otherSide);
                    if (nc-i-2 > 0)
                        memcpy(&buf[i],&buf[i+2],nc-i-2);
                    nc -= 2;
                    i--;
                    
                }
            }
        }
    }
    return nc;
}

void
loop()
{
    // put your main code here, to run repeatedly:
    int nc;
    time_t theTime;
    
#ifdef ARDUINO_ARCH_SAM
    int maxPort = 9;
#else
    int maxPort = 3;
#endif
    /* anything on Serial? Send it to associated console */
    
    
    if (nc = Serial.available())
    {
        if (nc > sizeof(sbuf))
            nc = sizeof(sbuf);
        nc = Serial.readBytes(&sbuf[0],nc);
        if (nc > 0)
        {
            nc = findAndExecCmd(&sbuf[0], nc, 0, &waitForSerialCmd, &serialOtherSide);
            if (nc > 0)
                nc = serialOtherSide->write(&sbuf[0],nc);
        }
    }
    
#ifdef ARDUINO_ARCH_SAM
    
    /* anything on SerialUSB? Send it to associated console */
    
    if (nc = SerialUSB.available())
    {
        if (nc > sizeof(sbuf))
            nc = sizeof(sbuf);
        nc = SerialUSB.readBytes(&sbuf[0],nc);
        if (nc > 0)
        {
            nc = findAndExecCmd(&sbuf[0], nc, 1, &waitForSerialCmd, &usbOtherSide);
            if (nc > 0)
                nc = usbOtherSide->write(&sbuf[0],nc);
        }
    }
    
#endif
    
    for (int i=0;i<maxPort;i++)
    {
        if (cPort[i])
        {
            if ((nc = cPort[i]->available()) > 0)
            {
                if (nc > sizeof(sbuf))
                    nc = sizeof(sbuf);
                nc = cPort[i]->readBytes(&sbuf[0],nc);
                
                if (cPort[i] == serialOtherSide)
                    Serial.write(&sbuf[0],nc);
                
#ifdef ARDUINO_ARCH_SAM
                
                if (cPort[i] == usbOtherSide)
                    SerialUSB.write(&sbuf[0],nc);
#endif
                
            }
        }
    }
    
    if (millis()/1000 != lastTime)
    {
        digitalWrite(LED_BUILTIN, blink ? HIGH : LOW);
        blink = ! blink;
        lastTime = millis()/1000;
    }
}
