/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <strings.h>
#include <termios.h>
#include <unistd.h>

#include "Device.h"
#include "Io.h"
#include "SerCfg.h"

namespace Blinker {

/**
 * @brief constructor
 * @param[in] path path to device
 */
Device::Device(const std::string &path)
{
  // open device
  m_fd = open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
}

/// destructor
Device::~Device()
{
  // exit if not initialized
  if (m_fd == -1)
    return;

  // close device
  close(m_fd);
}

/**
 * @brief set serial port configuration
 * @param[in] serCfg serial port configuration
 * @return if configuration succeeded
 *
 * This function should only be used if device is a serial port.
 */
bool Device::setSerCfg(const SerCfg& serCfg)
{
  struct termios tio;

  // exit if not initialized
  if (m_fd == -1)
    return false;

  // set up serial port configuration structure
  bzero(&tio, sizeof(tio));
  tio.c_cflag = CLOCAL | HUPCL | CREAD;
  tio.c_iflag = IGNBRK | IGNPAR;
  tio.c_oflag = 0;
  tio.c_lflag = 0;
  tio.c_cc[VTIME] = 1; // 0.1 sec timeout
  tio.c_cc[VMIN] = 0;  // return on single char read
  switch (serCfg.m_baud) {
    case     300: tio.c_cflag |=     B300; break;
    case     600: tio.c_cflag |=     B600; break;
    case    1200: tio.c_cflag |=    B1200; break;
    case    2400: tio.c_cflag |=    B2400; break;
    case    4800: tio.c_cflag |=    B4800; break;
    case    9600: tio.c_cflag |=    B9600; break;
    case   19200: tio.c_cflag |=   B19200; break;
    case   38400: tio.c_cflag |=   B38400; break;
#ifdef B57600
    case   57600: tio.c_cflag |=   B57600; break;
#endif
#ifdef B115200
    case  115200: tio.c_cflag |=  B115200; break;
#endif
#ifdef B230400
    case  230400: tio.c_cflag |=  B230400; break;
#endif
#ifdef B460800
    case  460800: tio.c_cflag |=  B460800; break;
#endif
#ifdef B500000
    case  500000: tio.c_cflag |=  B500000; break;
#endif
#ifdef B576000
    case  576000: tio.c_cflag |=  B576000; break;
#endif
#ifdef B921600
    case  921600: tio.c_cflag |=  B921600; break;
#endif
#ifdef B1000000
    case 1000000: tio.c_cflag |= B1000000; break;
#endif
#ifdef B1152000
    case 1152000: tio.c_cflag |= B1152000; break;
#endif
#ifdef B1500000
    case 1500000: tio.c_cflag |= B1500000; break;
#endif
#ifdef B2000000
    case 2000000: tio.c_cflag |= B2000000; break;
#endif
#ifdef B2500000
    case 2500000: tio.c_cflag |= B2500000; break;
#endif
#ifdef B3000000
    case 3000000: tio.c_cflag |= B3000000; break;
#endif
#ifdef B3500000
    case 3500000: tio.c_cflag |= B3500000; break;
#endif
#ifdef B4000000
    case 4000000: tio.c_cflag |= B4000000; break;
#endif
    default: return false; // invalid setting
  }
  switch (serCfg.m_data) {
    case 5: tio.c_cflag |= CS5; break;
    case 6: tio.c_cflag |= CS6; break;
    case 7: tio.c_cflag |= CS7; break;
    case 8: tio.c_cflag |= CS8; break;
    default: return false; // invalid setting
  }
  switch (serCfg.m_parity) {
    case SerCfg::ParityNone: break;
    case SerCfg::ParityEven: tio.c_cflag |= PARENB; break;
    case SerCfg::ParityOdd: tio.c_cflag |= PARENB | PARODD; break;
    default: return false; // invalid setting
  }
  switch (serCfg.m_stop) {
    case 1: break;
    case 2: tio.c_cflag |= CSTOPB; break;
    default: return false; // invalid setting
  }

  // configure serial port
  return tcsetattr(m_fd, TCSANOW, &tio) != -1;
}

/**
 * @brief write data to device
 * @param[in] data data to write
 * @param[out] len number of byte written to device
 * @return if some (or zero) data could be written,
 *         i.e. if the device is still operational
 */
bool Device::write(const std::string &data, std::string::size_type &len)
{
  // exit if not initialized
  if (m_fd == -1)
    return false;

  // write data
  ssize_t res = ::write(m_fd, data.c_str(), data.size());
  // write failed
  if (res < 0) {
    // real error
    if (errno != EAGAIN && errno != EWOULDBLOCK) {
      len = 0;
      return false; // report error
    }
    // device busy (e.g. serial device buffer full)
    else {
      len = 0;
      return true; // do not report error, device is still working
    }
  }
  // write succeeded
  else {
    len = res;
    return true; // success
  }
}

} // namespace Blinker


