/*!
  \file
  \brief シリアル通信

  \author Satofumi KAMIMURA

  $Id: SerialDevice.cpp 1454 2009-10-26 03:59:39Z satofumi $
*/

#include "SerialDevice.h"
#include "DetectOS.h"
#include "RingBuffer.h"
#include <string>

using namespace qrk;
using namespace std;


#if defined(WINDOWS_OS)
#include "SerialDevice_win.cpp"   // Windows (win32) 環境
#else
#include "SerialDevice_lin.cpp"   // Linux, Mac 環境 (共通)
#endif


struct SerialDevice::pImpl
{
  string error_message_;
  long baudrate_;
  RawSerialDevice raw_;
  RingBuffer<char> ring_buffer_; //!< 受信バッファ


  pImpl(void) : error_message_("no error"), baudrate_(0)
  {
  }


  void updateRingBuffer(void)
  {
    enum { BufferSize = 2048 };
    char buffer[BufferSize];

    int n = raw_.receive(buffer, BufferSize, 0);
    if (n > 0) {
      ring_buffer_.put(buffer, n);
    }
  }


  int receive(char* data, size_t count, int timeout)
  {
    if (! isConnected()) {
      error_message_ = "no connection.";
      return -1;
    }
    if (count == 0) {
      return 0;
    }

    size_t filled = 0;

    size_t ring_filled = ring_buffer_.size();
    if (ring_filled < count) {
      updateRingBuffer();
    }

    // バッファにデータがある場合、バッファからデータを格納する
    size_t read_size = std::min(count, ring_buffer_.size());
    ring_buffer_.get(data, read_size);
    filled += read_size;

    // バッファが空の場合、残りのデータはシステムから直接読み込む
    read_size = max(0, static_cast<int>(count - filled));
    if (read_size > 0) {
      int n = raw_.receive(&data[filled], static_cast<int>(read_size), timeout);
      if (n < 0) {
	error_message_ = raw_.what();
	return n;
      }
      filled += n;
    }
    return static_cast<int>(filled);
  }


  bool isConnected(void)
  {
    return raw_.isConnected();
  }
};


SerialDevice::SerialDevice(void) : pimpl(new pImpl)
{
}


SerialDevice::~SerialDevice(void)
{
  disconnect();
}


const char* SerialDevice::what(void) const
{
  return pimpl->error_message_.c_str();
}


bool SerialDevice::connect(const char* device, long baudrate)
{
  disconnect();
  clear();
  if (! pimpl->raw_.connect(device, baudrate)) {
    pimpl->error_message_ = pimpl->raw_.what();
    return false;
  } else {
    return true;
  }
}


void SerialDevice::disconnect(void)
{
  return pimpl->raw_.disconnect();
}


bool SerialDevice::setBaudrate(long baudrate)
{
  if (! pimpl->raw_.setBaudrate(baudrate)) {
    pimpl->error_message_ = pimpl->raw_.what();
    pimpl->baudrate_ = 0;
    return false;
  }
  pimpl->baudrate_ = baudrate;
  return true;
}


long SerialDevice::baudrate(void) const
{
  return pimpl->baudrate_;
}


bool SerialDevice::isConnected(void) const
{
  return pimpl->isConnected();
}


int SerialDevice::send(const char* data, size_t count)
{
  if (! isConnected()) {
    pimpl->error_message_ = "no connection.";
    return 0;
  }

  int n = pimpl->raw_.send(data, static_cast<int>(count));
  if (n < 0) {
    pimpl->error_message_ = pimpl->raw_.what();
  }
  return n;
}


int SerialDevice::receive(char* data, size_t count, int timeout)
{
  if (! isConnected()) {
    pimpl->error_message_ = "no connection.";
    return 0;
  }

  return pimpl->receive(data, count, timeout);
}


size_t SerialDevice::size(void) const
{
  pimpl->updateRingBuffer();
  return pimpl->ring_buffer_.size();
}


void SerialDevice::flush(void)
{
  if (! isConnected()) {
    pimpl->error_message_ = "no connection.";
    return;
  }

  return pimpl->raw_.flush();
}


void SerialDevice::clear(void)
{
  pimpl->raw_.flush();
  pimpl->ring_buffer_.clear();
}


void SerialDevice::ungetc(const char ch)
{
  if (! isConnected()) {
    pimpl->error_message_ = "no connection.";
    return;
  }

  pimpl->ring_buffer_.ungetc(ch);
}
