/*
 *  © 2022 Paul M. Antoine
 *  © 2021 Chris Harlow
 *  © 2022 2024 Harald Barth
 *  All rights reserved.
 *  
 *  This file is part of DCC++EX
 *
 *  This 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
 *  (at your option) any later version.
 *
 *  It 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 of the GNU General Public License
 *  along with CommandStation.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "SerialManager.h"
#include "DCCEXParser.h"
#include "StringFormatter.h"
#include "DIAG.h"

#ifdef ARDUINO_ARCH_ESP32
#ifdef SERIAL_BT_COMMANDS
#include <BluetoothSerial.h>
//#include <BleSerial.h>
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error No Bluetooth library available
#endif //ENABLED
BluetoothSerial SerialBT;
//BleSerial SerialBT;
#endif //COMMANDS
#endif //ESP32

static const byte PAYLOAD_FALSE = 0;
static const byte PAYLOAD_NORMAL = 1;
static const byte PAYLOAD_STRING = 2;

SerialManager * SerialManager::first=NULL;

SerialManager::SerialManager(Stream * myserial) {
  serial=myserial;
  next=first;
  first=this;
  bufferLength=0;
  inCommandPayload=PAYLOAD_FALSE; 
} 

void SerialManager::init() {
  USB_SERIAL.begin(115200);
  while (!USB_SERIAL && millis() < 5000); // wait max 5s for Serial to start
  new SerialManager(&USB_SERIAL);
  
#ifdef SERIAL6_COMMANDS
  Serial6.begin(115200);
  new SerialManager(&Serial6);
#endif
#ifdef SERIAL5_COMMANDS
  Serial5.begin(115200);
  new SerialManager(&Serial5);
#endif
#ifdef SERIAL4_COMMANDS
  Serial4.begin(115200);
  new SerialManager(&Serial4);
#endif
#ifdef SERIAL3_COMMANDS
  Serial3.begin(115200);
  new SerialManager(&Serial3);
#endif
#ifdef SERIAL2_COMMANDS
#ifdef ARDUINO_ARCH_ESP32
  Serial2.begin(115200, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
#else  // not ESP32
  Serial2.begin(115200);
#endif // ESP32
  new SerialManager(&Serial2);
#endif
#ifdef SERIAL1_COMMANDS
  Serial1.begin(115200);
  new SerialManager(&Serial1);
#endif
#ifdef SERIAL_BT_COMMANDS
  {
    //SerialBT.setPin("6666"); // choose other pin
    uint64_t chipid = ESP.getEfuseMac();
    char idstr[16] = {0};
    snprintf(idstr, 15, "DCCEX-%08X",
	     __builtin_bswap32((uint32_t)(chipid>>16)));
    SerialBT.begin(idstr);
    new SerialManager(&SerialBT);
    delay(1000);
  }
#endif
#ifdef SABERTOOTH
#ifdef ARDUINO_ARCH_ESP32
  Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
#else
  Serial2.begin(9600);
#endif
#endif
}

void SerialManager::broadcast(char * stringBuffer) {
    for (SerialManager * s=first;s;s=s->next) s->broadcast2(stringBuffer);
}
void SerialManager::broadcast2(char * stringBuffer) {
    serial->print(stringBuffer);
}

void SerialManager::loop() {
    for (SerialManager * s=first;s;s=s->next) s->loop2();
}

void SerialManager::loop2() {
  while (serial->available()) {
    char ch = serial->read();
    if (!inCommandPayload) {
      if (ch == '<') {
        inCommandPayload = PAYLOAD_NORMAL;
        bufferLength = 0;
        buffer[0] = '\0';
      }
    } else { // if (inCommandPayload)
      if (bufferLength <  (COMMAND_BUFFER_SIZE-1))
        buffer[bufferLength++] = ch;
      if (inCommandPayload > PAYLOAD_NORMAL) {
        if (inCommandPayload > 32 + 2) {    // String way too long
          ch = '>';                         // we end this nonsense
          inCommandPayload = PAYLOAD_NORMAL;
          DIAG(F("Parse error: Unbalanced string"));
          // fall through to ending parsing below
        } else if (ch == '"') {               // String end
          inCommandPayload = PAYLOAD_NORMAL;
          continue; // do not fall through
        } else
          inCommandPayload++;
      }
      if (inCommandPayload == PAYLOAD_NORMAL) {
        if (ch == '>') {
          buffer[bufferLength] = '\0';
          DCCEXParser::parse(serial, buffer, NULL); 
          inCommandPayload = PAYLOAD_FALSE;
          break;
        } else if (ch == '"') {
          inCommandPayload = PAYLOAD_STRING;
        }
      }
    }
  }
}