#include "SerialCommunicationHelper.h"

#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>

#include <cstring>

#include "fsfw/returnvalues/returnvalue.h"
#include "fsfw_hal/linux/serial/helper.h"

SerialCommunicationHelper::SerialCommunicationHelper(SerialConfig cfg) : cfg(cfg) {}

ReturnValue_t SerialCommunicationHelper::initialize() {
  fd = configureUartPort();
  if (fd < 0) {
    return returnvalue::FAILED;
  }
  return returnvalue::OK;
}

int SerialCommunicationHelper::rawFd() const { return fd; }

ReturnValue_t SerialCommunicationHelper::send(const uint8_t* data, size_t dataLen) {
  if (write(fd, data, dataLen) != static_cast<int>(dataLen)) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
    sif::error << "UartComIF::sendMessage: Failed to send data with error code " << errno
               << ": Error description: " << strerror(errno) << std::endl;
#endif
    return returnvalue::FAILED;
  }
  return returnvalue::OK;
}

int SerialCommunicationHelper::configureUartPort() {
  struct termios options = {};

  int flags = O_RDWR;
  if (cfg.getUartMode() == UartModes::CANONICAL) {
    // In non-canonical mode, don't specify O_NONBLOCK because these properties will be
    // controlled by the VTIME and VMIN parameters and O_NONBLOCK would override this
    flags |= O_NONBLOCK;
  }
  int fd = open(cfg.getDeviceFile().c_str(), flags);

  if (fd < 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
    sif::warning << "UartComIF::configureUartPort: Failed to open uart "
                 << cfg.getDeviceFile().c_str()

                 << "with error code " << errno << strerror(errno) << std::endl;
#endif
    return fd;
  }

  /* Read in existing settings */
  if (tcgetattr(fd, &options) != 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
    sif::warning << "UartComIF::configureUartPort: Error " << errno
                 << "from tcgetattr: " << strerror(errno) << std::endl;
#endif
    return fd;
  }

  serial::setParity(options, cfg.getParity());
  serial::setStopbits(options, cfg.getStopBits());
  serial::setBitsPerWord(options, cfg.getBitsPerWord());
  setFixedOptions(&options);
  serial::setMode(options, cfg.getUartMode());
  tcflush(fd, TCIFLUSH);

  /* Sets uart to non-blocking mode. Read returns immediately when there are no data available */
  options.c_cc[VTIME] = 0;
  options.c_cc[VMIN] = 0;

  serial::setBaudrate(options, cfg.getBaudrate());

  /* Save option settings */
  if (tcsetattr(fd, TCSANOW, &options) != 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
    sif::warning << "UartComIF::configureUartPort: Failed to set options with error " << errno
                 << ": " << strerror(errno);
#endif
    return fd;
  }
  return fd;
}

void SerialCommunicationHelper::setFixedOptions(struct termios* options) {
  /* Disable RTS/CTS hardware flow control */
  options->c_cflag &= ~CRTSCTS;
  /* Turn on READ & ignore ctrl lines (CLOCAL = 1) */
  options->c_cflag |= CREAD | CLOCAL;
  /* Disable echo */
  options->c_lflag &= ~ECHO;
  /* Disable erasure */
  options->c_lflag &= ~ECHOE;
  /* Disable new-line echo */
  options->c_lflag &= ~ECHONL;
  /* Disable interpretation of INTR, QUIT and SUSP */
  options->c_lflag &= ~ISIG;
  /* Turn off s/w flow ctrl */
  options->c_iflag &= ~(IXON | IXOFF | IXANY);
  /* Disable any special handling of received bytes */
  options->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
  /* Prevent special interpretation of output bytes (e.g. newline chars) */
  options->c_oflag &= ~OPOST;
  /* Prevent conversion of newline to carriage return/line feed */
  options->c_oflag &= ~ONLCR;
}

ReturnValue_t SerialCommunicationHelper::flushUartRxBuffer() {
  serial::flushRxBuf(fd);
  return returnvalue::OK;
}

ReturnValue_t SerialCommunicationHelper::flushUartTxBuffer() {
  serial::flushTxBuf(fd);
  return returnvalue::OK;
}

ReturnValue_t SerialCommunicationHelper::flushUartTxAndRxBuf() {
  serial::flushTxRxBuf(fd);
  return returnvalue::OK;
}