#include "windows.h"
#include <stdlib.h>
#include <stdio.h>
#include "idl_export.h"

#define IDL_WARN(a) IDL_Message(IDL_M_GENERIC, IDL_MSG_INFO, a)
#define IDL_WARN_N(a) IDL_Message(IDL_M_GENERIC, GetLastError(), IDL_MSG_INFO, a)
#define COMM_MAX(a,b) ((a>b)?(a):(b))
#define COMM_MIN(a,b) ((a>b)?(b):(a))
#define IDL_ERROR(x) IDL_Message(IDL_M_GENERIC, IDL_MSG_LONGJMP, x)
#define IDL_ERROR_N(x) IDL_MessageErrno(IDL_M_GENERIC, GetLastError(), IDL_MSG_LONGJMP, x)

#define DEBUG
#define NH 100
static HANDLE handles[NH]={0};  /* keep handles in case IDL looses track */

static IDL_VPTR IDL_comm_open(int argc, IDL_VPTR *argv, char *argk)
{
  HANDLE hfile;
  IDL_VPTR retval;
  COMMTIMEOUTS ct;
  static char s[180], parity;
  int len,i;
  
  typedef struct {
    IDL_KW_RESULT_FIRST_FIELD;
    IDL_LONG baud;
    IDL_LONG buffersize;
    IDL_LONG data;
    IDL_LONG mode;
    IDL_STRING parity;
    IDL_LONG stop;
  } KW_RESULT;

  static IDL_KW_PAR kw_pars[] = {
    { "BAUD", IDL_TYP_LONG, 1, IDL_KW_ZERO, 0, IDL_KW_OFFSETOF(baud) },
    { "BUFFERSIZE", IDL_TYP_LONG, 1, IDL_KW_ZERO, 0, IDL_KW_OFFSETOF(buffersize) },
    { "DATA", IDL_TYP_LONG, 1, IDL_KW_ZERO, 0, IDL_KW_OFFSETOF(data) },
    { "MODE", IDL_TYP_LONG, 1, 0, 0, IDL_KW_OFFSETOF(mode) },
    { "PARITY", IDL_TYP_STRING, 1, IDL_KW_ZERO, 0, IDL_KW_OFFSETOF(parity) },
    { "STOP", IDL_TYP_LONG, 1, 0, 0, IDL_KW_OFFSETOF(stop) },
    { NULL } };

  KW_RESULT kw;
  kw.stop=-1;
  kw.mode=1;
  IDL_KWProcessByOffset(argc, argv, argk, kw_pars, (IDL_VPTR *) 0, 1, &kw);
  
  IDL_ENSURE_STRING(argv[0]);

  retval=IDL_Gettmp();
  retval->type=IDL_TYP_LONG;
  retval->value.l=0;
  
  i=0;
  if (kw.mode & 1) i=GENERIC_READ;
  if (kw.mode & 2) i=i | GENERIC_WRITE;

  hfile=CreateFile(IDL_STRING_STR(&argv[0]->value.str),i,0,NULL,OPEN_EXISTING,0,NULL);

  if (hfile == INVALID_HANDLE_VALUE) IDL_ERROR_N("Failed to open port");
  retval->value.l=(int) hfile;
  for (i=0;i<100 && handles[i];i++);
  if (i==100) { IDL_WARN("Maximum number of handles in use"); i=0; }
  handles[i]=hfile;
  
  if (kw.buffersize > 0) 
    if (!SetupComm(hfile,kw.buffersize,kw.buffersize)) 
      IDL_WARN_N("Failed to set specified buffersize");
  
  if (kw.parity.slen) parity=kw.parity.s[0]; else parity=0;
  if (parity == 32) parity=0;
  
  len=0;
  s[0]=0;
  if (kw.baud) { sprintf(s,"BAUD=%d ",kw.baud); len=strlen(s); }
  if (kw.data) { sprintf(s+len,"DATA=%d ",kw.data); len=strlen(s); }
  if (parity) { sprintf(s+len,"PARITY=%c ",parity); len=strlen(s); }
  if (kw.stop != -1) { sprintf(s+len,"STOP=%d ",kw.stop); len=strlen(s); }
#ifdef DEBUG
  IDL_WARN(s);  
#endif

  if (len) {
    DCB dcb;
    if (GetCommState(hfile,&dcb) == 0) IDL_WARN_N("Failed to get the current DCB structure");
    else if (BuildCommDCB(s,&dcb) == 0) IDL_WARN_N("Failed to build DCB stucture");
    else if (SetCommState(hfile,&dcb) == 0) IDL_WARN_N("Failed to set the new DCB structure");
  }
  
  
  ct.ReadIntervalTimeout=MAXDWORD;
  ct.ReadTotalTimeoutConstant=0;
  ct.ReadTotalTimeoutMultiplier=0; 
  ct.WriteTotalTimeoutConstant=0;
  ct.WriteTotalTimeoutMultiplier=0;
  if (SetCommTimeouts(hfile,&ct) == 0) IDL_WARN_N("Failed to set timeouts");
  
  IDL_KW_FREE;
  return(retval);
}

static IDL_VPTR IDL_comm_close(int argc, IDL_VPTR *argv, char *argk)
{
  IDL_VPTR retval;
  int i;
  HANDLE hfile;

  typedef struct {
    IDL_KW_RESULT_FIRST_FIELD;
    IDL_LONG all;
  } KW_RESULT;

  static IDL_KW_PAR kw_pars[] = {
    { "ALL", IDL_TYP_LONG, 1, IDL_KW_ZERO, 0, IDL_KW_OFFSETOF(all) },
    { NULL } };

  KW_RESULT kw;
  IDL_KWProcessByOffset(argc, argv, argk, kw_pars, NULL, 1, &kw);
  
  retval=IDL_Gettmp();
  retval->type=IDL_TYP_LONG;
  if (argc == 1) {
    hfile=(HANDLE) argv[0]->value.l;
    retval->value.l = (CloseHandle(hfile) != 0);
    for (i=0;i<NH;i++) if (handles[i] == hfile) handles[i]=0;
  }
  if (kw.all) {
    retval->value.l=0;
    for (i=0;i<NH;i++) {
      retval->value.l += (CloseHandle(handles[i]) != 0);
      handles[i]=0;
    }
  }
  IDL_KW_FREE;
  return(retval);
}

static IDL_VPTR IDL_comm_read(int argc, IDL_VPTR *argv, char *argk)
{
  IDL_VPTR retval, buffer;
  char *bp,*bp1;
  unsigned long n,*tc,i,junk;
  COMMPROP cp;
  HANDLE hfile;
#ifdef DEBUG
  char s[180];
#endif

  typedef struct {
    IDL_KW_RESULT_FIRST_FIELD;
    IDL_VPTR buffer;
    IDL_LONG nmax;
    IDL_VPTR nout;
    IDL_LONG offset;
    IDL_VPTR transfer_count;
  } KW_RESULT;

  static IDL_KW_PAR kw_pars[] = {
    { "BUFFER", 0, 1, IDL_KW_ZERO | IDL_KW_OUT | IDL_KW_ARRAY, 0, IDL_KW_OFFSETOF(buffer) },
    { "NMAX", IDL_TYP_LONG, 1, IDL_KW_ZERO, 0, IDL_KW_OFFSETOF(nmax) },
    { "NOUT", 0, 1, IDL_KW_ZERO | IDL_KW_OUT, 0, IDL_KW_OFFSETOF(nout) },
    { "OFFSET", IDL_TYP_LONG, 1, IDL_KW_ZERO, 0, IDL_KW_OFFSETOF(offset) },
    { "TRANSFER_COUNT", 0, 1, IDL_KW_ZERO | IDL_KW_OUT, 0, IDL_KW_OFFSETOF(transfer_count) },
    { NULL } };

  KW_RESULT kw;
  IDL_KWProcessByOffset(argc, argv, argk, kw_pars, NULL, 1, &kw);

  if (kw.transfer_count) {
    IDL_ENSURE_SCALAR(kw.transfer_count); 
    IDL_ENSURE_SIMPLE(kw.transfer_count);
    IDL_EXCLUDE_STRING(kw.transfer_count);
    kw.transfer_count->type=IDL_TYP_ULONG;
    kw.transfer_count->value.l = 0;
    tc=&kw.transfer_count->value.ul;
  } else tc=&junk; /* Make sure *tc is valid */

  if (kw.nout) {
    IDL_ENSURE_SCALAR(kw.nout); 
    IDL_ENSURE_SIMPLE(kw.nout);
    IDL_EXCLUDE_STRING(kw.nout);
  }

  hfile=(HANDLE) argv[0]->value.l;
  
  retval=IDL_Gettmp();
  retval->type=IDL_TYP_LONG;
  retval->value.l = -1;
  
  /* Find max bytes to read in each case */
  if (GetCommProperties(hfile,&cp) == 0) { 
    if (kw.nmax>0) n=kw.nmax; 
    else if (kw.buffer) n=kw.buffer->value.arr->arr_len-kw.offset*kw.buffer->value.arr->elt_len;
    else n=4096;
  } else n=cp.dwCurrentRxQueue;
  if (kw.buffer) {
    n=COMM_MIN(n,kw.buffer->value.arr->arr_len-kw.offset*kw.buffer->value.arr->elt_len);
    if (kw.nmax>0 && kw.nmax<n) n=kw.nmax;
    bp1=kw.buffer->value.arr->data+kw.offset*kw.buffer->value.arr->elt_len;
  } else {
    if (kw.nmax>0 && n>kw.nmax) n=kw.nmax;
    if (n>0) bp1=(char *) malloc(n);
  }

  if (kw.nout) {
    kw.nout->type=IDL_TYP_LONG;
    kw.nout->value.l=n;
  }

  *tc=0;
  if (n>0 && ReadFile(hfile,bp1,n,tc,NULL)) {
    if (kw.buffer) retval->value.l = *tc;
    else if (*tc!=0) {
      bp = IDL_MakeTempVector(IDL_TYP_BYTE, *tc, IDL_ARR_INI_NOP, &buffer);
      IDL_Deltmp(retval);
      retval=buffer;
      for (i=0;i<*tc;i++) bp[i]=bp1[i];
    }
  }

  if (!kw.buffer && n>0) free(bp1);
  IDL_KW_FREE;
  return(retval);
}

static IDL_VPTR IDL_comm_write(int argc, IDL_VPTR *argv, char *argk)
{
  IDL_VPTR retval;
  HANDLE hfile;
  int n,i;
  unsigned long tc;
  char *bp;

  retval=IDL_Gettmp();
  retval->type=IDL_TYP_LONG;
  retval->value.l=0;

  IDL_ENSURE_SCALAR(argv[0]);
  if (argv[0]->type!=IDL_TYP_LONG) IDL_ERROR("First argument must be a long integer");
  IDL_ENSURE_SIMPLE(argv[1]);
  hfile=(HANDLE) argv[0]->value.l;
  if (argv[1]->type == 0) n=0;
  else if (argv[1]->type==IDL_TYP_STRING && (argv[1]->flags & IDL_V_ARR) == 0) {
    n=argv[1]->value.str.slen;
    bp=argv[1]->value.str.s;
    IDL_WARN("Got here");
  }
  else if (argv[1]->flags & IDL_V_ARR == 0) { /* scalar */ 
    switch (argv[1]->type) {
    case IDL_TYP_BYTE     : { n=1; break; }
    case IDL_TYP_UINT     :
    case IDL_TYP_INT      : { n=2; break; }
    case IDL_TYP_ULONG    :
    case IDL_TYP_FLOAT    :
    case IDL_TYP_LONG     : { n=4; break; }
    case IDL_TYP_DOUBLE   :
    case IDL_TYP_ULONG64  :
    case IDL_TYP_LONG64   : { n=8; break; }
    case IDL_TYP_DCOMPLEX : { n=16; break; }
    }
    bp=&(argv[1]->value.c);
  }
  else if (argv[1]->type!=IDL_TYP_STRING) {
    n=argv[1]->value.arr->arr_len;
    bp=argv[1]->value.arr->data;
  }
  else { /* string array */
    for (i=0;i<argv[1]->value.arr->n_elts;i++) {
      n=((IDL_STRING *) argv[1]->value.arr->data)[i].slen;
      bp=((IDL_STRING *) argv[1]->value.arr->data)[i].s;
      if (n>0) {
	if (WriteFile(hfile,bp,n,&tc,NULL)==0) IDL_ERROR_N("Failed to write data");
	retval->value.l += tc;
      }
    }
    n=0;
  }    
  if (n>0) {
    if (WriteFile(hfile,bp,n,&tc,NULL) == 0) IDL_ERROR_N("Failed to write data");
    retval->value.l=tc;
  }
  return (retval);
}

#define ARRLEN(arr) (sizeof(arr)/sizeof(arr[0]))

int IDL_Load(void)
{
  static IDL_SYSFUN_DEF2 function_addr[] = {
    { (IDL_FUN_RET) IDL_comm_open, "COMM_OPEN", 1, 1, IDL_SYSFUN_DEF_F_KEYWORDS, 0},
    { (IDL_FUN_RET) IDL_comm_close, "COMM_CLOSE", 0, 1, IDL_SYSFUN_DEF_F_KEYWORDS, 0},
    { (IDL_FUN_RET) IDL_comm_read, "COMM_READ", 1, 1, IDL_SYSFUN_DEF_F_KEYWORDS, 0},
    { (IDL_FUN_RET) IDL_comm_write, "COMM_WRITE", 2, 2, 0, 0},
  };
  return IDL_SysRtnAdd(function_addr, TRUE, ARRLEN(function_addr));
}
