/**************************************************************************/
/*!
    @file     DidacticNet.cpp
    @author   anian buehler @ letsgoING.org
*/
/**************************************************************************/

#include "Arduino.h"
#include "DidacticNet.h"

//**************************************************************************
// ROOT
//**************************************************************************
DidacticPSNet::DidacticPSNet() {}

DidacticPSNet::~DidacticPSNet() {}

void DidacticPSNet::begin(Stream &_port)
{
  setStream(_port);
}

void DidacticPSNet::begin(Stream &_port, PSNET_CALLBACK_SIGNATURE)
{
  setStream(_port);
  setCallback(callback);
}

DidacticPSNet &DidacticPSNet::setCallback(PSNET_CALLBACK_SIGNATURE)
{
  this->callback = callback;
  return *this;
}

void DidacticPSNet::setStream(Stream &stream)
{
  _port = &stream;
}

bool DidacticPSNet::handleNetwork()
{
  if (checkData())
  {
    if (recieveData())
    {
      // Serial.print("Message filter: ");Serial.println(_readBufferMessage[1]);
      // Serial.print("Check Message filter: ");Serial.println(getMessageFilter(_readBufferMessage[1]));
      if (getMessageFilter(_readBufferMessage[1]))
      {
        handleData();
      }
    }
    _waitingTimeCSMA = millis() + random(CSMA_MIN_DELAY_MS, CSMA_MAX_DELAY_MS);
  }
  if (_dataToSend && _waitingTimeSend <= millis() && _waitingTimeCSMA <= millis())
  {
    // send data to network
    unsigned long delayStartTime = micros();
    while (micros() < delayStartTime + CSMA_CHECK_DELAY_US)
      ;
    if (!checkData())
    {
      if (!sendData())
      {
        return false;
      }
      else
      {
        _dataToSend = false;
        //_waitingTime = millis()+ random(CSMA_MID_DELAY_MS, CSMA_MAX_DELAY_MS);
        _waitingTimeSend = millis() + _intervalTime; // random(CSMA_MID_DELAY_MS, CSMA_MAX_DELAY_MS);
      }
    }
  }
  //}
  return true;
}

bool DidacticPSNet::isDataToSend()
{
  return _dataToSend;
}

bool DidacticPSNet::sendData()
{
  int counter = 0;
  bool messageSent = false;
  while (!messageSent)
  {
    if (counter >= MAX_LEN_TOPICS + MAX_LEN_PAYLOAD + LEN_OVERHEAD - 1)
    {
      _sendBufferMessage[counter] = MSG_DELIMITER; // cut message and stop sending
      messageSent = true;
    }
    else if (_sendBufferMessage[counter] == MSG_DELIMITER)
    {
      messageSent = true;
    }

    _port->write(_sendBufferMessage[counter]);
    counter++;
  }
  return true;
}

int DidacticPSNet::extractData(int startCounter, int maxLength, char *buffer, char limiter)
{
  int counter = startCounter;
  while (_readBufferMessage[counter] != limiter)
  {
    buffer[counter - startCounter] = _readBufferMessage[counter];
    counter++;
    if ((counter - startCounter) > maxLength)
    {
      counter--;
      break; // if > maxLenght -> leave while and return
    }
  }
  buffer[counter - startCounter] = '\0';
  return counter - startCounter; // length
}

int DidacticPSNet::checkData()
{
  return (int)_port->available();
}

bool DidacticPSNet::recieveData()
{
  static int msgCounter = 0;
  static int topicCounter = 0;
  static int payloadCounter = 0;

  while (checkData())
  {
    char localBuffer = _port->read();
    if (localBuffer == MSG_PRELIMITER)
    {
      msgCounter = 0;
      topicCounter = 0;
      payloadCounter = 0;
      _readBufferMessage[msgCounter] = localBuffer;
    }
    else if (localBuffer == MSG_DELIMITER && _readBufferMessage[0] == MSG_PRELIMITER)
    {
      msgCounter++;
      _readBufferMessage[msgCounter] = localBuffer;
      _readBufferMessage[msgCounter + 1] = '0';
      msgCounter = 0;
      return true;
    }
    else if (localBuffer == MSG_SEPARATOR && _readBufferMessage[0] == MSG_PRELIMITER)
    {
      topicCounter = msgCounter - 2;
      msgCounter++;
      _readBufferMessage[msgCounter] = localBuffer;
    }
    else if (_readBufferMessage[0] == MSG_PRELIMITER && localBuffer != MSG_DELIMITER)
    {
      if (msgCounter > LEN_OVERHEAD + MAX_LEN_TOPICS + MAX_LEN_PAYLOAD)
      {
        msgCounter == 0;
        _readBufferMessage[0] = '\0';
      }
      else
      {
        msgCounter++;
        _readBufferMessage[msgCounter] = localBuffer;
      }
    }
  }
  return false;
}

void DidacticPSNet::setInterval(long intervalTime)
{
  _intervalTime = intervalTime;
}

//**************************************************************************
// CLIENT
//**************************************************************************
DidacticPSNetClient::~DidacticPSNetClient() {}

// NEW easy interface for non advanced users
// ##########################################
void DidacticPSNetClient::begin(Stream &_port) // TODO: check if CLientMode is necessary or an unset callback ist not a problem
{
  DidacticPSNet::begin(_port);
  _clientMode = CLIENT_MODE_BASIC;
}

void DidacticPSNetClient::begin(Stream &_port, PSNET_CALLBACK_SIGNATURE)
{
  DidacticPSNet::begin(_port, callback);
}

bool DidacticPSNetClient::available()
{
  return _newMessageAvailable;
}

int DidacticPSNetClient::readLatestTopicNr()
{
  return _newMessageTopicNr;
}

void DidacticPSNetClient::readTopic(int topicNr, char *topic)
{
  strcpy(topic, _topic[topicNr]);
}

void DidacticPSNetClient::readTopic(char *topic)
{
  readTopic(_newMessageTopicNr, topic);
}

void DidacticPSNetClient::readPayload(int topicNr, char *payload)
{
  strcpy(payload, _payload[topicNr]);

  if (topicNr == _newMessageTopicNr)
  {
    _newMessageAvailable = false;
  }
}

void DidacticPSNetClient::readPayload(char *payload)
{
  readPayload(_newMessageTopicNr, payload);
}

void DidacticPSNetClient::readPayload(char *topic, char *payload)
{
  int topicNr = getTopicNr(topic);
  if (topicNr == _newMessageTopicNr)
  {
    _newMessageAvailable = false;
  }
  readPayload(topicNr, payload);
}

bool DidacticPSNetClient::readBooleanPayload()
{
  _newMessageAvailable = false;
  return _payload[_newMessageTopicNr][0] == '1';
}

int DidacticPSNetClient::readIntegerPayload()
{
  _newMessageAvailable = false;
  return atoi(_payload[_newMessageTopicNr]);
}
// ##########################################

DidacticPSNetClient::DidacticPSNetClient()
{
  _intervalTime = INTERVAL_CLIENT;
}

int DidacticPSNetClient::publish(char *topic, char *payload)
{
  return publish(topic, strlen(topic), payload, strlen(payload));
}

int DidacticPSNetClient::publish(char *topic, int topicLength, char *payload, int payloadLength)
{
  int error = DN_PUBLISH_SUCCESSULL;

  _sendBufferMessage[0] = MSG_PRELIMITER;
  _sendBufferMessage[1] = MSG_PUBLISH;
  _sendBufferMessage[2 + topicLength] = MSG_SEPARATOR;
  _sendBufferMessage[2 + topicLength + 1 + payloadLength] = MSG_DELIMITER;
  _sendBufferMessage[2 + topicLength + 1 + payloadLength + 1] = '\0';

  // TODO: check
  if (topicLength > MAX_LEN_TOPICS)
  {
    topicLength = MAX_LEN_TOPICS;
    error += DN_ERROR_TOPIC_LEN;
  }
  if (payloadLength > MAX_LEN_PAYLOAD)
  {
    payloadLength = MAX_LEN_PAYLOAD;
    error += DN_ERROR_PAYLOAD_LEN;
  }

  for (int i = 0; i < topicLength; i++)
  {
    _sendBufferMessage[2 + i] = topic[i];
  }
  for (int i = 0; i < payloadLength; i++)
  {
    _sendBufferMessage[2 + topicLength + 1 + i] = payload[i];
  }

  _dataToSend = true;
  return error;
}

int DidacticPSNetClient::publish(char *topic, int data)
{
  char sendPayload[MAX_LEN_PAYLOAD];
  itoa(data, sendPayload, 10);

  return publish(topic, sendPayload);
}

int DidacticPSNetClient::publish(char *topic, bool data)
{
  char sendPayload[2];
  itoa(data, sendPayload, 10);

  return publish(topic, sendPayload);
}

int DidacticPSNetClient::publishOnChange(char *topic, bool input)
{
  if (!_dataToSend)
  {
    if (eDetector.edgeDetected(input))
    {
      return publish(topic, input);
    }
  }
  return DN_ERROR_NO_ERROR;
}

int DidacticPSNetClient::publishOnChange(char *topic, int input, int threshold)
{
  if (!_dataToSend)
  {
    if (cDetector.valueChanged(input, threshold))
    {
      return publish(topic, input);
    }
  }
  return DN_ERROR_NO_ERROR;
}

int DidacticPSNetClient::subscribe(char *topic)
{
  return subscribe(topic, strlen(topic));
}

int DidacticPSNetClient::subscribe(char *topic, int topicLength)
{
  int error = DN_ERROR_NO_ERROR;

  if (topicLength > MAX_LEN_TOPICS)
  {
    topicLength = MAX_LEN_TOPICS;
    error = DN_ERROR_TOPIC_LEN;
  }

  _sendBufferMessage[0] = MSG_PRELIMITER;
  _sendBufferMessage[1] = MSG_SUBSCRIBE;
  _sendBufferMessage[2 + topicLength] = MSG_DELIMITER;

  int topicNumber = getTopicNr(topic);

  if (topicNumber < 0)
  {
    topicNumber = getFreeTopicNr();
    if (topicNumber < 0)
    {
      topicNumber = 0;
    }
    for (int i = 0; i < topicLength; i++)
    {
      _topic[topicNumber][i] = topic[i];
      _sendBufferMessage[2 + i] = topic[i];
    }
    _topic[topicNumber][topicLength] = '\0';
    _sendBufferMessage[2 + topicLength + 1] = '\0';
    _dataToSend = true;
  }
  else
  {
    for (int i = 0; i < topicLength; i++)
    {
      _sendBufferMessage[2 + i] = topic[i];
    }
    _sendBufferMessage[2 + topicLength + 1] = '\0';
    _dataToSend = true;
  }
  while (_dataToSend)
  {
    handleNetwork();
  }
  return error;
}

bool DidacticPSNetClient::unsubscribe(char *topic)
{
  return unsubscribe(topic, strlen(topic));
}

bool DidacticPSNetClient::unsubscribe(char *topic, int topicLength)
{
  int topicNumber = getTopicNr(topic);
  if (topicNumber >= 0)
  {
    _topic[topicNumber][0] = '\0';
    return true;
  }
  return false;
}

bool DidacticPSNetClient::getMessageFilter(char messageType)
{
  return messageType == MSG_UPDATE;
}

bool DidacticPSNetClient::savePayload(char *buffer, int position)
{
  strcpy(_payload[position], buffer);
  return true;
}

bool DidacticPSNetClient::handleData()
{
  int currentTopicNr = 0;
  int topicLength = 0;
  int payloadLength = 0;
  topicLength = extractData(2, MAX_LEN_TOPICS, _bufferTopic, MSG_SEPARATOR);
  if (topicLength > 0)
  {
    currentTopicNr = getTopicNr(_bufferTopic);
    payloadLength = extractData(topicLength + 3, MAX_LEN_PAYLOAD, _bufferPayload, MSG_DELIMITER);
    if (currentTopicNr >= 0)
    {
      savePayload(_bufferPayload, currentTopicNr);
      _newMessageAvailable = true;         // new
      _newMessageTopicNr = currentTopicNr; // new

      if (_clientMode == CLIENT_MODE_ADVANCED) // new -> client with callback
      {
#ifdef CALLBACK_W_LENGTH
        callback(_bufferTopic, topicLength, _bufferPayload, payloadLength);
#else
        callback(_bufferTopic, _bufferPayload);
#endif
      }
    }
  }
  return true;
}

int DidacticPSNetClient::getTopicNr(char *topic)
{
  for (int i = 0; i < MAX_NR_TOPICS_CLIENT; i++)
  {
    if (strcmp(_topic[i], topic) == 0 || _topic[i][0] == MSG_TOPIC_MULTI)
    {
      return i;
    }
  }
  return -1;
}

int DidacticPSNetClient::getFreeTopicNr()
{
  for (int i = 0; i < MAX_NR_TOPICS_CLIENT; i++)
  {
    if (strcmp(_topic[i], "") == 0)
    {
      return i;
    }
  }
  return -1;
}

int DidacticPSNetClient::getSubscribedTopic(char *topic, int number)
{
  if (number > 0 && number < getMaxNrTopics())
  {
    strcpy(topic, _topic[number]);
    return DN_ERROR_NO_ERROR;
  }
  return DN_ERROR_NO_TOPIC;
}

int DidacticPSNetClient::getMaxNrTopics()
{
  return getFreeTopicNr();
}

//**************************************************************************
// Broker
//**************************************************************************
DidacticPSNetBroker::DidacticPSNetBroker()
{
  _intervalTime = INTERVAL_BROKER;
}

DidacticPSNetBroker::~DidacticPSNetBroker() {}

bool DidacticPSNetBroker::getMessageFilter(char messageType)
{
  return (messageType == MSG_PUBLISH || messageType == MSG_SUBSCRIBE);
}

bool DidacticPSNetBroker::savePayload(char *buffer, int position)
{
  strcpy(_data[position], buffer);
  return true;
}

void DidacticPSNetBroker::writeDataToTopic(int topicNumber, char *usedTopic, char *newData)
{
  if (strcmp(_topic[topicNumber], "") == 0)
  {
    strcpy(_topic[topicNumber], usedTopic);
  }
  strcpy(_data[topicNumber], newData);
}

bool DidacticPSNetBroker::handleData()
{
  int currentTopicNr = 0;
  int topicLength = 0;
  int dataLength = 0;

  printVerbose(_readBufferMessage);

  if (_readBufferMessage[1] == MSG_PUBLISH)
  {
    topicLength = extractData(2, MAX_LEN_TOPICS, _bufferTopic, MSG_SEPARATOR);
    if (topicLength > 0)
    {
      currentTopicNr = getTopicNr(_bufferTopic);
      dataLength = extractData(topicLength + 3, MAX_LEN_PAYLOAD, _bufferPayload, MSG_DELIMITER);
      if (currentTopicNr >= 0)
      {
        writeDataToTopic(currentTopicNr, _bufferTopic, _bufferPayload);
        update(_topic[currentTopicNr], topicLength, _data[currentTopicNr], dataLength);
      }
    }
  }
  else if (_readBufferMessage[1] == MSG_SUBSCRIBE)
  {
    topicLength = extractData(2, MAX_LEN_TOPICS, _bufferTopic, MSG_DELIMITER);
    if (topicLength > 0)
    {
      currentTopicNr = getTopicNr(_bufferTopic);
      if (currentTopicNr >= 0)
      {
        update(_topic[currentTopicNr], strlen(_topic[currentTopicNr]), _data[currentTopicNr], strlen(_data[currentTopicNr]));
      }
    }
  }
  return true;
}

bool DidacticPSNetBroker::update(char *topic, int topicLength, char *data, int dataLength)
{
  _sendBufferMessage[0] = MSG_PRELIMITER;
  _sendBufferMessage[1] = MSG_UPDATE;
  _sendBufferMessage[2 + topicLength] = MSG_SEPARATOR;
  _sendBufferMessage[2 + topicLength + 1 + dataLength] = MSG_DELIMITER;
  _sendBufferMessage[2 + topicLength + 1 + dataLength + 1] = '\0';

  if (topicLength <= MAX_LEN_TOPICS)
  {
    for (int i = 0; i < topicLength; i++)
    {
      _sendBufferMessage[2 + i] = topic[i];
    }
  }
  else
  {
    _dataToSend = false;
    return false;
  }
  if (dataLength <= MAX_LEN_PAYLOAD)
  {
    for (int i = 0; i < dataLength; i++)
    {
      _sendBufferMessage[2 + topicLength + 1 + i] = data[i];
    }
  }
  else
  {
    _dataToSend = false;
    return false;
  }
  _dataToSend = true;
  return true;
}

int DidacticPSNetBroker::getTopicNr(char *topic)
{
  for (int i = 0; i < MAX_NR_TOPICS_BROKER; i++)
  {
    if (strcmp(_topic[i], topic) == 0)
    {
      return i;
    }
  }
  return getFreeTopicNr();
}

int DidacticPSNetBroker::getFreeTopicNr()
{
  for (int i = 0; i < MAX_NR_TOPICS_BROKER; i++)
  {
    if (strcmp(_topic[i], "") == 0)
    {
      return i;
    }
  }
  return -1;
}

void DidacticPSNetBroker::setVerbose(Stream &verbosePort) // TEST: new & untested
{
  _isVerbose = true;
  _verbosePort = &verbosePort;
}

void DidacticPSNetBroker::setNoneVerbose() // TEST: new & untested
{
  _isVerbose = false;
}

bool DidacticPSNetBroker::printVerbose(char *recievedData) // TEST: new & untested
{
  char signBuffer = 0;
  int signCounter = 0;

  if (_isVerbose)
  {
    while (signBuffer != MSG_DELIMITER)
    {
      signBuffer = recievedData[signCounter];
      _verbosePort->write(signBuffer);
      signCounter++;
    }
    _verbosePort->write('\n');
  }
  return _isVerbose;
}

//**************************************************************************
// LITTLE HELPERS FOR CLIENTS ;-)
//*************************************************************************

EdgeDetector::EdgeDetector(){};
EdgeDetector::~EdgeDetector(){};

int EdgeDetector::edgeDetected(bool currentState)
{
  static bool lastState = false;
  int edEdge = 0;

  if (currentState && !lastState)
  {
    edEdge = RISING;
  }
  else if (!currentState && lastState)
  {
    edEdge = FALLING;
  }
  lastState = currentState;
  return edEdge;
}

ChangeDetector::ChangeDetector() {}
ChangeDetector::~ChangeDetector() {}

bool ChangeDetector::valueChanged(int value, int threshold)
{
  static int lastValue = 0;

  if (abs(value - lastValue) > threshold)
  {
    lastValue = value;
    return true;
  }
  return false;
}

UnblockingTimer::UnblockingTimer() {}
UnblockingTimer::~UnblockingTimer() {}

bool UnblockingTimer::timeElapsed(long delayTime)
{
  long currentTime = millis();

  if (lastTime + (delayTime - 1) < currentTime)
  {
    lastTime = currentTime;
    return true;
  }
  return false;
}

SerialReader::SerialReader() {}
SerialReader::~SerialReader() {}

void SerialReader::begin(Stream &rsStream)
{
  _port = &rsStream;
}

int SerialReader::readSerialData(char *rsDataArray, char rsEndSign)
{

  if (_port->available())
  {
    char charBuffer = _port->read();
    rsDataArray[charCounter] = charBuffer;

    if (charBuffer == rsEndSign)
    {
      rsDataArray[charCounter] = '\0';
      int nrOfChars = charCounter;
      charCounter = 0;
      return nrOfChars;
    }
    else
    {
      charCounter++;
    }
  }
  return 0;
}

serialMonitorUI::serialMonitorUI() {}
serialMonitorUI::~serialMonitorUI() {}

char serialMonitorUI::extractCommand(char *userInput)
{
  if ((userInput[0] >= 33 && userInput[0] <= 47) || ((userInput[0] >= 58 && userInput[0] <= 64))) // ! " # $ % & ' ( ) * + , - . / : ; < = > ? @
  {
    char command = userInput[0];
    strcpy(userInput, &userInput[1]);
    return command;
  }
  return DN_ASCII_EOS;
}

bool serialMonitorUI::available()
{
  if (SerialReader::readSerialData(_currentInput, DN_ASCII_CR) > 0)
  {
    _currentCommand = extractCommand(_currentInput);
    return true;
  }

  return false;
}

char serialMonitorUI::readCommand()
{
  return _currentCommand;
}

void serialMonitorUI::readUserInput(char *inputData)
{
  strcpy(inputData, _currentInput);
}