/*!
  \file
  \brief シリアル通信 (Windows 実装)

  Serial Communication Interface 制御


  \author Satofumi KAMIMURA

  $Id: serial_ctrl_win.c 1349 2009-09-30 07:00:56Z satofumi $
*/

#include "serial_ctrl.h"
#include "serial_errno.h"
#include <stdio.h>

//#include "ticks.h"


enum {
  False = 0,
  True,
};


static void setTimeout(serial_t *serial, int timeout)
{
  COMMTIMEOUTS timeouts;
  GetCommTimeouts(serial->hCom_, &timeouts);

  timeouts.ReadIntervalTimeout = (timeout == 0) ? MAXDWORD : 0;
  timeouts.ReadTotalTimeoutConstant = timeout;
  timeouts.ReadTotalTimeoutMultiplier = 0;

  SetCommTimeouts(serial->hCom_, &timeouts);
}


/* 接続 */
int serial_connect(serial_t *serial, const char *device, long baudrate)
{
  // COM10 以降への対応用
  enum { NameLength = 11 };
  char adjusted_device[NameLength];

  /* COM ポートを開く */
  _snprintf(adjusted_device, NameLength, "\\\\.\\%s", device);
  serial->hCom_ = CreateFileA(adjusted_device, GENERIC_READ | GENERIC_WRITE, 0,
                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

  if (serial->hCom_ == INVALID_HANDLE_VALUE) {
    printf("open failed: %s\n", device);
    return -1;
  }

  /* 通信サイズの更新 */
  SetupComm(serial->hCom_, 4096 * 8, 4096);

  /* ボーレートの変更 */
  serial_setBaudrate(serial, baudrate);

  /* シリアル制御構造体の初期化 */
  serial->has_last_ch_ = False;

  /* タイムアウトの設定 */
  serial->current_timeout_ = 0;
  setTimeout(serial, serial->current_timeout_);

  return 0;
}


/* 切断 */
void serial_disconnect(serial_t *serial)
{
  if (serial->hCom_ != INVALID_HANDLE_VALUE) {
    CloseHandle(serial->hCom_);
    serial->hCom_ = INVALID_HANDLE_VALUE;
  }
}


int serial_isConnected(const serial_t *serial)
{
  return (serial->hCom_ == INVALID_HANDLE_VALUE) ? 0 : 1;
}


/* ボーレートの変更 */
int serial_setBaudrate(serial_t *serial, long baudrate)
{
  long baudrate_value;
  DCB dcb;

  switch (baudrate) {

  case 4800:
    baudrate_value = CBR_4800;
    break;

  case 9600:
    baudrate_value = CBR_9600;
    break;

  case 19200:
    baudrate_value = CBR_19200;
    break;

  case 38400:
    baudrate_value = CBR_38400;
    break;

  case 57600:
    baudrate_value = CBR_57600;
    break;

  case 115200:
    baudrate_value = CBR_115200;
    break;

  default:
    baudrate_value = baudrate;
  }

  GetCommState(serial->hCom_, &dcb);
  dcb.BaudRate = baudrate_value;
  dcb.ByteSize = 8;
  dcb.Parity = NOPARITY;
  dcb.fParity = FALSE;
  dcb.StopBits = ONESTOPBIT;
  SetCommState(serial->hCom_, &dcb);

  return 0;
}


/* 送信 */
int serial_send(serial_t *serial, const char *data, int data_size)
{
  DWORD n;

  if (data_size < 0) {
    return 0;
  }

  if (! serial_isConnected(serial)) {
    return SerialConnectionFail;
  }

  WriteFile(serial->hCom_, data, (DWORD)data_size, &n, NULL);
  return n;
}


/* 受信 */
int serial_recv(serial_t *serial, char* data, int data_size_max, int timeout)
{
  DWORD n;
  int filled = 0;
  //int first_ticks;

  if (data_size_max <= 0) {
    return 0;
  }

  /* 書き戻した１文字があれば、書き出す */
  if (serial->has_last_ch_) {
    data[0] = serial->last_ch_;
    serial->has_last_ch_ = False;
    ++filled;
  }
  if (data_size_max - filled <= 0) {
    return filled;
  }

  if (timeout != serial->current_timeout_) {
    setTimeout(serial, timeout);
    serial->current_timeout_ = timeout;
  }

  //first_ticks = ticks();
  ReadFile(serial->hCom_, &data[filled],
           (DWORD)data_size_max - filled, &n, NULL);
  //printf("<%d>, ", ticks() - first_ticks);

  return filled + n;
}


/* １文字書き戻す */
void serial_ungetc(serial_t *serial, char ch)
{
  serial->has_last_ch_ = True;
  serial->last_ch_ = ch;
}


void serial_clear(serial_t* serial)
{
  PurgeComm(serial->hCom_,
            PURGE_RXABORT | PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR);

  serial->has_last_ch_ = False;
}
