/***************************************************************************
 *   copyright           : (C) 2002 by Hendrik Sattler                     *
 *   mail                : post@hendrik-sattler.de                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "ttyaccess.h"
#include "tty_serial.h"
#include "scmxx.h"
#include "helper.h"
#include "atcommand.h"
#include "config.h"
#include "gtincl.h"
#include "w32compat.h"

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

#if defined(OS2_API)
#define INCL_OS2
#define INCL_DOSDEVICES
#define INCL_DOSDEVIOCTL
#include <os2.h>
#include <termios.h>
#undef  B9600
#define B9600 9600
#undef  B19200
#define B19200 19200
#undef  B38400
#define B38400 38400
#undef  B57600
#define B57600 57600
#undef  B115200
#define B115200 115200
typedef int speed_t;
typedef HFILE tty_handle_t;

#elif defined(WINDOWS_API)
#include <windows.h>
#define B9600   CBR_9600
#define B19200  CBR_19200
#define B38400  CBR_38400
#define B57600  CBR_57600
#define B115200 CBR_115200
typedef DWORD speed_t;
typedef HANDLE tty_handle_t;

#else
#include <termios.h>
typedef int tty_handle_t;
#endif

#include "ttyaccess.h"

static tty_handle_t tty_portfd;

static speed_t tty_serial_speed(char *newttyspeed){
  switch (atoi(newttyspeed)) {
  case 9600: return B9600;
  case 19200: return B19200;
  case 38400: return B38400;
#ifdef B57600
  case 57600: return B57600;
#endif
#ifdef B115200
  case 115200: return B115200;
#endif
  default:
    print_verbose(0,"%s","No valid baudrate defined. Using compiled in value...\n");
    return tty_serial_speed(TTYSPEED);
  }
}

#if defined(OS2_API)
static void tty_serial_makeraw() {
  LINECONTROL lc;
  int rc;

  lc.bDataBits = 8;
  lc.bParity = 0;
  lc.bStopBits = 0;
  lc.fTransBreak = 0;
  rc = DosDevIOCtl(tty_portfd, IOCTL_ASYNC, ASYNC_SETLINECTRL,
		   &lc, sizeof(LINECONTROL), &size, NULL, 0, NULL);
  if (rc) {
    fprintf(stderr,"%s: rc=%d\n",_("Error in setting port attributes"),rc);
    exit(EXIT_FAILURE);
  }

}
static void tty_serial_speed_set (char* baud) {
  speed_t bps = tty_speed(args->baud);
  int rc = DosDevIOCtl(tty_portfd, IOCTL_ASYNC, ASYNC_SETBAUDRATE,
		       &bps, sizeof(ULONG), &size, NULL, 0, NULL);
  if (rc) {
    fprintf(stderr,"%s: rc=%d\n",_("Error in setting transmission speed"),rc);
    exit(EXIT_FAILURE);
  }
}
static void tty_serial_timeout_set (DCBINFO* devinfo,uint8_t dsec) {
  int timeout = 65535;
  
  if (dsec != 0) timeout = dsec*10;
  devinfo.fbTimeout = 0x02;
  devinfo.usReadTimeout = timeout;
  devinfo.usWriteTimeout = timeout;  
}
void tty_serial_flush () {
  int rc = DosDevIOCtl(tty_portfd, IOCTL_GENERAL, DEV_FLUSHINPUT,
		       NULL, 0, NULL, NULL, 0, NULL);  
  if (!rc) rc = DosDevIOCtl(tty_portfd, IOCTL_GENERAL, DEV_FLUSHOUTPUT,
			    NULL, 0, NULL, NULL, 0, NULL);
  if (rc) {
    fprintf(stderr,"%s: rc=%d\n",_("Error in flushing buffers"),rc);
    exit(EXIT_FAILURE);
  }
}

#elif defined(WINDOWS_API)
static void tty_serial_makeraw(DCB* dcb) {
  dcb->StopBits = ONESTOPBIT;
  dcb->Parity = NOPARITY;
  dcb->ByteSize = 8;
  dcb->fNull = FALSE;
}
static void tty_serial_speed_set (DCB* dcb, char* baud) {
  dcb->BaudRate = tty_serial_speed(baud);
}
static void tty_serial_timeout_set (uint8_t dsec) {
  COMMTIMEOUTS timeouts;

  if (GetCommTimeouts(tty_portfd,&timeouts)) {
    if (dsec == 0) {
      /* MFC doc tells nothing about ever-blocking read
       * so I hope that this is correct 
       */
      timeouts.ReadIntervalTimeout = 0;
      timeouts.ReadTotalTimeoutMultiplier = 0;
      timeouts.ReadTotalTimeoutConstant = 0;
    } else {
      timeouts.ReadIntervalTimeout = dsec*10;
    }
    SetCommTimeouts(tty_portfd,&timeouts);
  }
}
void tty_serial_flush () {
  if (!PurgeComm(tty_portfd, PURGE_RXCLEAR|PURGE_TXCLEAR)) {
    print_verbose(0,"%s: %s %d\n",_("Error in flushing buffers"),
		  _("system error code"),GetLastError());
    exit(EXIT_FAILURE);
  }
}

#else
#include "config.h"
#ifdef NO_CFMAKERAW
static void tty_serial_makeraw(struct termios* termios_p) {
  termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
  termios_p->c_oflag &= ~OPOST;
  termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
  termios_p->c_cflag &= ~(CSIZE|PARENB);
  termios_p->c_cflag |= CS8;
}
#else
#define tty_serial_makeraw(t) (void)cfmakeraw(t)
#endif /*NO_CFMAKERAW */
static void tty_serial_speed_set (struct termios* termios_p, char* baud) {
  cfsetispeed(termios_p, tty_serial_speed(baud)); //input at baudrate
  cfsetospeed(termios_p, tty_serial_speed(baud)); //ouput at baudrate
}
static void tty_serial_timeout_set (struct termios* termios_p, uint8_t dsec) {
  if (dsec == 0) {
    termios_p->c_cc[VMIN]=1; //wait for at least one character (never times out)
  } else {
    termios_p->c_cc[VMIN]=0; //return after timeout, even with nothing
  }
  termios_p->c_cc[VTIME]=dsec; //try reading for this amount of time (in deciseconds)
}
void tty_serial_flush () {
  int rc = tcflush(tty_portfd, TCIOFLUSH);
  if (rc == -1) {
    fprintf(stderr,"%s: %s\n",_("Error in flushing buffers"),strerror(errno));    
    exit(EXIT_FAILURE);
  }
}
#endif

static struct tty_access funcs;
struct tty_access* tty_serial_open (struct port_args_t* args) {
#if defined(OS2_API)
  long action;
  unsigned long size;
  int rc;
  DCBINFO devinfo;
  
  //opening the device
  rc = DosOpen(args->device, &tty_portfd, &action, 0, FILE_NORMAL, FILE_OPEN,
	             OPEN_ACCESS_READWRITE|OPEN_SHARE_DENYNONE, (PEAOP2)NULL);
  if (rc) {
    print_verbose(0,": rc=%d\n",rc);
    return NULL;
  }

  tty_setspeed(args->baud);
  tty_makeraw();

  rc = DosDevIOCtl(tty_portfd, /* file decsriptor */
		               IOCTL_ASYNC, /*asyncronous change */
		               ASYNC_GETDCBINFO, /* get device control block info */
		               NULL, /*  */
		               0, /* length of the previous parameter */
		               NULL, /* max length of data ret */
		               &devinfo, /* data to be recieved */
		               sizeof(DCBINFO), /* length of data */
		               &size); /* length of data returned */
  
  devinfo.fbFlowReplace = 0; // diable rts/cts control
  devinfo.fbCtlHndShake = 1; // Truning on DTR.
  tty_settimeout(&devinfo,args->timeout);

  rc = DosDevIOCtl(tty_portfd, IOCTL_ASYNC, ASYNC_SETDCBINFO,
		               &devinfo, /* parameters to set  */
		               sizeof(DCBINFO),&size, NULL, 0, NULL); 


#elif defined(WINDOWS_API)
  DCB dcb;
  
  //opening the device
  tty_portfd = CreateFile(args->device,
                          GENERIC_READ | GENERIC_WRITE,
                          0,    // must be opened with exclusive-access
                          NULL, // no security attributes
                          OPEN_EXISTING, // must use OPEN_EXISTING
                          0,    // not overlapped I/O
                          NULL); // hTemplate must be NULL for comm devices
  if (tty_portfd == INVALID_HANDLE_VALUE) {
    print_verbose(0,": %s %d\n",_("system error code"),GetLastError());
    return NULL;
  }
  if (GetCommState(tty_portfd,&dcb) == 0) {
    fprintf(stderr,"%s: %s %d\n",_("Error in getting port attributes"),
            _("system error code"),GetLastError());
    return NULL;
  }
  tty_serial_speed_set(&dcb,args->baud);
  tty_serial_makeraw(&dcb);
  if (SetCommState(tty_portfd,&dcb) == 0) {
    fprintf(stderr,"%s: %s %d\n",_("Error in setting port attributes"),
            _("system error code"),GetLastError());
    return NULL;
  }
  tty_serial_timeout_set(args->timeout);

#else
  int flags = O_RDWR | O_NOCTTY | O_NONBLOCK;
  struct termios newtio;

  /* FHS (http://www.pathname.com/fhs/pub/fhs-2.3.html#VARLOCKLOCKFILES)
   * specifies lock files for devices but the Serial HowTo (13.3)
   * also mentions the problem when it comes to DevFS (Linux).
   * Since this makes it an unreliable test, forget about it.
   */

  //opening the device
  if ((tty_portfd=open(args->device,flags)) == -1) {
    print_error("%s\n",strerror(errno));
    return NULL;
  }
  if (fcntl(tty_portfd,F_SETFL,flags&(~O_NONBLOCK)) == -1) {
    fprintf(stderr,"%s: %s\n",_("Error in setting port attributes"),strerror(errno));
    return NULL;
  }

  if (!args->ignorebits) {
    //getting port parameters
    if (tcgetattr(tty_portfd,&newtio) < 0){
      fprintf(stderr,"%s: %s\n",_("Error in getting port attributes"),strerror(errno));
      return NULL;
    }
  } else {
    memset(&newtio,0,sizeof(newtio));
  }
  tty_serial_speed_set(&newtio, args->baud);
  tty_serial_makeraw(&newtio); //make raw, 8N1
  /* CLOCAL: ignore modem control lines
   * CREAD: enable receiver
   * ~CSTOPB: do not send two stop bits (but one)
   */
  newtio.c_cflag |= (CLOCAL | CREAD);
  newtio.c_cflag &= ~CSTOPB;
  tty_serial_timeout_set(&newtio,args->timeout);
  tty_serial_flush();
  if(tcsetattr(tty_portfd,TCSANOW,&newtio) < 0){ //set now
    fprintf(stderr,"%s: %s\n",_("Error in setting port attributes"),strerror(errno));
    return NULL;
  }
#endif

  funcs.read = tty_serial_read;
  funcs.write = tty_serial_write;
  funcs.flush = tty_serial_flush;
  funcs.close = tty_serial_close;
  return &funcs;
}

void tty_serial_close() {
#if defined(OS2_API)
  DosClose(tty_portfd);
#elif defined(WINDOWS_API)
  CloseHandle(tty_portfd);
#else
  close(tty_portfd);
#endif
}

/* as argument to tty_write() */
static long devicewrite (const char* data, long len) {
#if defined(OS2_API)
  ULONG written;;
  if (DosWrite(tty_port,data,len,&written)) return -1;
  else return (long)written;
#elif defined(WINDOWS_API)
  ULONG written;;
  if (WriteFile(tty_portfd,data,len,&written,NULL) == FALSE) return -1;
  else return (long)written;
#else
  return write(tty_portfd,data,len);
#endif
}

int tty_serial_write (const char* data, size_t count) {
  return tty_write(data,count,devicewrite);
}

/* as argument to tty_read() */
static long deviceread (char* buf, long len) {
#if defined(OS2_API)
  ULONG status;
  if (DosRead(tty_portfd,buf,len,&status)) return -1;
  else return (long)status;
#elif defined(WINDOWS_API)
  DWORD status;
  if (ReadFile(tty_portfd,buf,len,&status,NULL) == FALSE) return -1;
  else return (long)status;
#else
  return read(tty_portfd,buf,len);
#endif
}

char* tty_serial_read (int (*stop_condition)(const char*,const size_t)) {
  return tty_read(stop_condition,deviceread);
}
