#include <algorithm>

#if !defined(_WIN32) && !defined(__OpenBSD__) && !defined(__FreeBSD__)
# include <alloca.h>
#endif

#if defined (__MINGW32__)
# define alloca __builtin_alloca
#endif

#include "serial.h"
#include "common.h"
namespace ydlidar {
namespace core {
using namespace common;
namespace serial {

using std::min;
using std::numeric_limits;
using std::vector;
using std::size_t;
using std::string;

using serial::Serial;
using serial::bytesize_t;
using serial::parity_t;
using serial::stopbits_t;
using serial::flowcontrol_t;

class Serial::ScopedReadLock {
 public:
  explicit ScopedReadLock(Serial::SerialImpl *pimpl) : pimpl_(pimpl) {
    this->pimpl_->readLock();
  }
  ~ScopedReadLock() {
    this->pimpl_->readUnlock();
  }
 private:
  // Disable copy constructors
  ScopedReadLock(const ScopedReadLock &);
  const ScopedReadLock &operator=(ScopedReadLock);

  Serial::SerialImpl *pimpl_;
};

class Serial::ScopedWriteLock {
 public:
  explicit ScopedWriteLock(Serial::SerialImpl *pimpl) : pimpl_(pimpl) {
    this->pimpl_->writeLock();
  }
  ~ScopedWriteLock() {
    this->pimpl_->writeUnlock();
  }
 private:
  // Disable copy constructors
  ScopedWriteLock(const ScopedWriteLock &);
  const ScopedWriteLock &operator=(ScopedWriteLock);
  Serial::SerialImpl *pimpl_;
};

Serial::Serial(const string &port, uint32_t baudrate, serial::Timeout timeout,
               bytesize_t bytesize, parity_t parity, stopbits_t stopbits,
               flowcontrol_t flowcontrol)
  : pimpl_(new SerialImpl(port, baudrate, bytesize, parity,
                          stopbits, flowcontrol)) {
  pimpl_->setTimeout(timeout);
}

Serial::~Serial() {
  delete pimpl_;
}

bool Serial::open() {
  return pimpl_->open();
}

void Serial::closePort() {
  pimpl_->close();
}

bool Serial::isOpen() {
  return pimpl_->isOpen();
}

size_t Serial::available() {
  return pimpl_->available();
}

bool Serial::waitReadable() {
  serial::Timeout timeout(pimpl_->getTimeout());
  return pimpl_->waitReadable(timeout.read_timeout_constant);
}

void Serial::waitByteTimes(size_t count) {
  pimpl_->waitByteTimes(count);
}

Serial::SerialPortError Serial::getSystemError(int systemErrorCode) const {
  return pimpl_->getSystemError(systemErrorCode);
}


int Serial::waitfordata(size_t data_count, uint32_t timeout,
                        size_t *returned_size) {
  return pimpl_->waitfordata(data_count, timeout, returned_size);
}

size_t Serial::writeData(const uint8_t *data, size_t size) {
  return write(data, size);
}

size_t Serial::readData(uint8_t *data, size_t size) {
  return read(data, size);
}

const char *Serial::DescribeError(SerialPortError err) {
  char const *errorString = "Unknown error";

  switch (err) {
    case Serial::NoError:
      errorString = ("No error");
      break;

    case Serial::OpenError:
      errorString = ("Device is already open");
      break;

    case Serial::NotOpenError:
      errorString = ("Device is not open");
      break;

    case Serial::TimeoutError:
      errorString = ("Operation timed out");
      break;

    case Serial::ReadError:
      errorString = ("Error reading from device");
      break;

    case Serial::WriteError:
      errorString = ("Error writing to device");
      break;

    case Serial::ResourceError:
      errorString = ("Device disappeared from the system");
      break;

    default:
      // an empty string will be interpreted as "Unknown error"
      break;
  }

  return errorString;
}


size_t Serial::read_(uint8_t *buffer, size_t size) {
  return this->pimpl_->read(buffer, size);
}

size_t Serial::read(uint8_t *buffer, size_t size) {
  ScopedReadLock lock(this->pimpl_);
  return this->pimpl_->read(buffer, size);
}

size_t Serial::read(std::vector<uint8_t> &buffer, size_t size) {
  ScopedReadLock lock(this->pimpl_);
  uint8_t *buffer_ = static_cast<uint8_t *>(alloca(size * sizeof(uint8_t)));
  size_t bytes_read = this->pimpl_->read(buffer_, size);
  buffer.insert(buffer.end(), buffer_, buffer_ + bytes_read);
  return bytes_read;
}

size_t Serial::read(std::string &buffer, size_t size) {
  ScopedReadLock lock(this->pimpl_);
  uint8_t *buffer_ = static_cast<uint8_t *>(alloca(size * sizeof(uint8_t)));
  size_t bytes_read = this->pimpl_->read(buffer_, size);
  buffer.append(reinterpret_cast<const char *>(buffer_), bytes_read);
  return bytes_read;
}

string Serial::readSize(size_t size) {
  std::string buffer;
  this->read(buffer, size);
  return buffer;
}

size_t Serial::readline(string &buffer, size_t size, string eol) {
  ScopedReadLock lock(this->pimpl_);
  size_t eol_len = eol.length();
  uint8_t *buffer_ = static_cast<uint8_t *>(alloca(size * sizeof(uint8_t)));
  size_t read_so_far = 0;

  while (true) {
    size_t bytes_read = this->read_(buffer_ + read_so_far, 1);
    read_so_far += bytes_read;

    if (bytes_read == 0) {
      break; // Timeout occured on reading 1 byte
    }

    if (string(reinterpret_cast<const char *>(buffer_ + read_so_far - eol_len),
               eol_len) == eol) {
      break; // EOL found
    }

    if (read_so_far == size) {
      break; // Reached the maximum read length
    }
  }

  buffer.append(reinterpret_cast<const char *>(buffer_), read_so_far);
  return read_so_far;
}

string Serial::readline(size_t size, string eol) {
  std::string buffer;
  this->readline(buffer, size, eol);
  return buffer;
}

vector<string> Serial::readlines(size_t size, string eol) {
  ScopedReadLock lock(this->pimpl_);
  std::vector<std::string> lines;
  size_t eol_len = eol.length();
  uint8_t *buffer_ = static_cast<uint8_t *>(alloca(size * sizeof(uint8_t)));
  size_t read_so_far = 0;
  size_t start_of_line = 0;

  while (read_so_far < size) {
    size_t bytes_read = this->read_(buffer_ + read_so_far, 1);
    read_so_far += bytes_read;

    if (bytes_read == 0) {
      if (start_of_line != read_so_far) {
        lines.push_back(string(reinterpret_cast<const char *>(buffer_ + start_of_line),
                               read_so_far - start_of_line));
      }

      break; // Timeout occured on reading 1 byte
    }

    if (string(reinterpret_cast<const char *>
               (buffer_ + read_so_far - eol_len), eol_len) == eol) {
      // EOL found
      lines.push_back(string(reinterpret_cast<const char *>(buffer_ + start_of_line),
                             read_so_far - start_of_line));
      start_of_line = read_so_far;
    }

    if (read_so_far == size) {
      if (start_of_line != read_so_far) {
        lines.push_back(string(reinterpret_cast<const char *>(buffer_ + start_of_line),
                               read_so_far - start_of_line));
      }

      break; // Reached the maximum read length
    }
  }

  return lines;
}

size_t Serial::write(const string &data) {
  ScopedWriteLock lock(this->pimpl_);
  return this->write_(reinterpret_cast<const uint8_t *>(data.c_str()),
                      data.length());
}

size_t Serial::write(const std::vector<uint8_t> &data) {
  ScopedWriteLock lock(this->pimpl_);
  return this->write_(&data[0], data.size());
}

size_t Serial::write(const uint8_t *data, size_t size) {
  ScopedWriteLock lock(this->pimpl_);
  return this->write_(data, size);
}

size_t Serial::write_(const uint8_t *data, size_t length) {
  return pimpl_->write(data, length);
}

void Serial::setPort(const string &port) {
  ScopedReadLock rlock(this->pimpl_);
  ScopedWriteLock wlock(this->pimpl_);
  bool was_open = pimpl_->isOpen();

  if (was_open) {
    closePort();
  }

  pimpl_->setPort(port);

  if (was_open) {
    open();
  }
}

string Serial::getPort() const {
  return pimpl_->getPort();
}

void Serial::setTimeout(serial::Timeout &timeout) {
  pimpl_->setTimeout(timeout);
}

serial::Timeout Serial::getTimeout() const {
  return pimpl_->getTimeout();
}

bool Serial::setBaudrate(uint32_t baudrate) {
  return pimpl_->setBaudrate(baudrate);
}

uint32_t Serial::getBaudrate() const {
  return uint32_t(pimpl_->getBaudrate());
}

bool Serial::setBytesize(bytesize_t bytesize) {
  return pimpl_->setBytesize(bytesize);
}

bytesize_t Serial::getBytesize() const {
  return pimpl_->getBytesize();
}

bool Serial::setParity(parity_t parity) {
  return pimpl_->setParity(parity);
}

parity_t Serial::getParity() const {
  return pimpl_->getParity();
}

bool Serial::setStopbits(stopbits_t stopbits) {
  return pimpl_->setStopbits(stopbits);
}

stopbits_t Serial::getStopbits() const {
  return pimpl_->getStopbits();
}

bool Serial::setFlowcontrol(flowcontrol_t flowcontrol) {
  return pimpl_->setFlowcontrol(flowcontrol);
}

flowcontrol_t Serial::getFlowcontrol() const {
  return pimpl_->getFlowcontrol();
}

void Serial::flush() {
  ScopedReadLock rlock(this->pimpl_);
  ScopedWriteLock wlock(this->pimpl_);
  pimpl_->flush();
}

void Serial::flushInput() {
  ScopedReadLock lock(this->pimpl_);
  pimpl_->flushInput();
}

void Serial::flushOutput() {
  ScopedWriteLock lock(this->pimpl_);
  pimpl_->flushOutput();
}

void Serial::sendBreak(int duration) {
  pimpl_->sendBreak(duration);
}

bool Serial::setBreak(bool level) {
  return pimpl_->setBreak(level);
}

bool Serial::setRTS(bool level) {
  return pimpl_->setRTS(level);
}

bool Serial::setDTR(bool level) {
  return pimpl_->setDTR(level);
}

bool Serial::waitForChange() {
  return pimpl_->waitForChange();
}

bool Serial::getCTS() {
  return pimpl_->getCTS();
}

bool Serial::getDSR() {
  return pimpl_->getDSR();
}

bool Serial::getRI() {
  return pimpl_->getRI();
}

bool Serial::getCD() {
  return pimpl_->getCD();
}

int Serial::getByteTime() {
  return pimpl_->getByteTime();
}
}
}
}
