#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <stdlib.h>
#include <time.h>


#include "serial_proto.h"
#include "timer_proto.h"
#include "callback_proto.h"
#include "debug_proto.h"
#include "simplerexx.h"

char lastError[128];

char serialName[100];
int unitNumber;
int globalAborted = 0;

extern char portNameToLoad[];
char arexxPortName[41];

extern AREXXCONTEXT OutgoingRexx;

extern struct CallBack initCallBack;

struct CallBack globalCallBack;

#define serialRecBufLen 1024

extern struct SerialData *sld;
extern struct timerequest* tr;
extern struct timerequest* tr2;

char* serialError(int e) {
  switch (e) {
  case 1 : return "Device busy";
  case 2 : return "Baud mismatch";
  case 4 : return "Couldn't allocate buffer";
  case 5 : return "Invalid parameter";
  case 6 : return "Line error";
  case 9 : return "Parity error";
  case 11 : return "Timer error";
  case 12 : return "Buffer overflow";
  case 13 : return "No DSR";
  case 15 : return "Break detected";
  default: return "Unknown";
  }
}

enum ReturnStatus waitForCharactersOrTimeout(char* buf, int max, int* actual, int time, struct CallBack* cb);

enum ReturnStatus waitForCharacters(char* buf, int max, int* actual, struct CallBack* cb);

enum ReturnStatus timerCallBack(void) {
  return TimedOut;
}

/* PRIVATE */
/* Internal buffer for our readChar() function to speed up access */
__far static char internalBuffer[serialRecBufLen];
static int readPosition = 0;
static int readCount = 0;

void flushBuffers() {
  ClearSer(sld); /* serial buffer flush */
  readPosition = readCount = 0; /* internal buffer flush */
}

extern int faxHardwareHandshaking;

void setReceiveSerialParms(void) {
  if (!faxHardwareHandshaking) {
    ChangeData(sld, 19200, 8, 1, (ULONG) SERF_SHARED | SERF_XDISABLED, serialRecBufLen);
  }
}

void setSendSerialParms(void) {
  if (!faxHardwareHandshaking) {
    setSerialParms(19200);
  }
}

enum ReturnStatus setSerialParms(int speed) {
  char temp[32];
  if (faxHardwareHandshaking) {
    if (ChangeData(sld,speed,8,1, (ULONG)SERF_SHARED | SERF_XDISABLED | SERF_7WIRE, serialRecBufLen)) {
      /* 7wire handshaking during playback */
      debugString("Couldn't change serial parameters", "setSerialParms");
      mainError(lastError);
      return Error;
    }
    sprintf(temp, "Serial: %d, 7 wire", speed);
  } else {
    if (ChangeData(sld,speed,8,1, (ULONG)SERF_SHARED, serialRecBufLen)) {
      /* xon/xoff */
      debugString("Couldn't change serial parameters", "setSerialParms");
      return Error;
    }
    sprintf(temp, "Serial: %d, xon/xoff", speed);
  }
  mainError(temp);
  return Normal;
}

int writeToSerial(char* s, int len) {
  WriteSer(sld, s, len);
  return len;
}

/* Promises to tell us if there's something to process */
int hasChar(void) {
  if (readPosition < readCount) return 1;
  if (SerBuffer(sld)) return 1;
  return 0;
}

/* WARNING:  this is only safe if you did a readChar() AND you got a character !!! */
void unReadChar(void) {
  readPosition--;
}

/* timeout is in tenths of a second, unlike in voice.c! */
enum ReturnStatus readChar(char* c, int timeout, struct CallBack* cb) {
  /* This is a buffered 1 character read function to make access to the
     serial port slightly more efficient */
  enum ReturnStatus status;

  if (readPosition < readCount) {
    /* There's still something to read from our internal buffer */
    *c = internalBuffer[readPosition];
    readPosition++;
    return Normal;
  }

  if (timeout < 1) {
    return TimedOut;
  }

  readPosition = 0;
  status = waitForCharactersOrTimeout(internalBuffer, sizeof(internalBuffer), &readCount,
				 timeout, cb);
  if (status != Normal) return status;

  *c = internalBuffer[readPosition];
  readPosition++;

  return Normal;
}

/* this builds on a simpler waitForCharacters function using callbacks */
/* time is in tenths of a second! */
enum ReturnStatus waitForCharactersOrTimeout(char* buf, int max, int* actual, int time, struct CallBack* cb) {
  enum ReturnStatus status;
  ULONG timerSignal = TimerSignal(tr2);

  addCallBack(cb, timerSignal, timerCallBack); /* register the timersignal as something
						  that can "break" the function we're
						  about to call */
  
  /* start our timer */
  StartTimer(tr2, time / 10, (time % 10) * 100000);

  status = waitForCharacters(buf, max, actual, cb);

  /* clean up our timer */
  EndTimer(tr2);

  deleteCallBack(cb); /* kill off the callback we added */

  return status;
}

enum ReturnStatus waitForCharacters(char* buf, int max, int* actual, struct CallBack* cb) {
  ULONG mask, signals;
  int numCharacters;
  int error;

  enum ReturnStatus status = Normal;

  ULONG serialSignal = SerialSignal(sld);
  ULONG externalSignals = callBackSignals(cb);

  mask = externalSignals | serialSignal;


  RecvSer(sld, buf, 1); /* start a serial request for 1 character */

  while (1) {
    signals = Wait(mask); /* start waiting */

    if (signals & serialSignal) {
      /* the wait was broken because of some serial data in the serial port */
      error = WaitSer(sld); /* performs a getmessage and stuff to end an io operation neatly */
      if (error) {
	sprintf(lastError, "EFax: serial port: %s", serialError(error));
	mainError(lastError);
	buf[0] = 'Q';
	*actual = 1;
	return Normal;
      }

      numCharacters = SerBuffer(sld);

#ifdef DELAYWANTED
      if (!(numCharacters > (serialRecBufLen / 2))) {
	Delay(5); /* give the modem some time to send stuff to the serial
		     port */

	/* now we want to get as many characters as possible without overflowing
	   the buffer */
	numCharacters = SerBuffer(sld);
      }
#endif

      if (numCharacters > max - 1) numCharacters = max - 1;
      
      if (numCharacters > 0) {
	long actualNumCharacters = 0;
	error = ReadSer(sld, &buf[1], numCharacters, &actualNumCharacters);
	if (error) {
	  sprintf(lastError, "EFax: Serial Port: %s req=%d,actual=%d,rem=%d", serialError(error), numCharacters, actualNumCharacters, SerBuffer(sld));
	  mainError(lastError);
	}
	if (actualNumCharacters > numCharacters) {
	  mainError("actualNumCharacters > numCharacters!  Bug!");
	} else {
	  numCharacters = actualNumCharacters;
	}
      }
      *actual = numCharacters + 1;
      return Normal;
    } else {
      /* it wasn't the serial port that sent us a signal */
      status = processCallBack(cb, signals);
      /* if one of the callback things says we should get out, let's get out! */
      if (status != Normal) {
	AbortSer(sld);
	WaitWaitSer(sld);
	*actual = 0;
	return status;
      }
    }
  }

  strcpy(lastError, "FATAL ERROR");
  return Error;
}

enum ReturnStatus waitForTimeout(int seconds, int microseconds, struct CallBack* cb) {
  enum ReturnStatus status = Normal;
  ULONG timerSignal = TimerSignal(tr2);
  ULONG signals;

  addCallBack(cb, timerSignal, timerCallBack); /* register the timersignal as something
						  that can "break" the function we're
						  about to call */
  
  /* start our timer */
  StartTimer(tr2, seconds, microseconds);

  while (status == Normal) {
    signals = Wait(callBackSignals(cb));
    status = processCallBack(cb, signals);
  }

  /* clean up our timer */
  EndTimer(tr2);

  deleteCallBack(cb); /* kill off the callback we added */

  return status;
}

void releaseResources(void) {
  if (sld) {
    CloseSerial(sld);
  }
  if (tr) DeleteTimer(tr);
  if (tr2) DeleteTimer(tr2);
  FreeARexx(OutgoingRexx);

  deleteCallBack(&globalCallBack);
}

int processAbortArexxMessage(void) {
  int retVal = 0;
  /* arexx stuff */
  char* argument;

  char command[100];

  char* error = 0;
  char* result = 0;
  long errorLevel = 0;
  
  struct RexxMsg* rmsg;

  while (rmsg = GetARexxMsg(OutgoingRexx)) {
    if (rmsg == REXX_RETURN_ERROR) {
      continue;
    }

    error = 0;
    result = 0;
    errorLevel = 0;
    
    /* parse the message we were passed */
    argument = stptok(ARG0(rmsg), command, 100, " ,");
    if (*argument) argument++;
    strupr(command);

    if (stricmp("ABORT", command) == 0) {
      retVal = 1;
    } else {
      errorLevel = 20;
      error = "EFax is working";
    }
    
    if (error) {
      SetARexxLastError(OutgoingRexx,rmsg,error);
    }
    ReplyARexxMsg(OutgoingRexx,rmsg,result,errorLevel);
  }

  return retVal;
}

enum ReturnStatus signalDetectedCallBack(void) {
  int aborted = processAbortArexxMessage();

  if (aborted) {
    globalAborted = 1;
    return TimedOut;
  } else {
    return Normal;
  }
}

void allocateResources(void) {
  tr = PrepareTimer();
  if (!tr) {
    mainError("Cannot open timer");
    exit(5);
  }
  tr2 = PrepareTimer();
  if (!tr2) {
    mainError("Cannot open timer");
    exit(5);
  }

  sld = OpenSerial(serialName, unitNumber, SERF_SHARED);
  if (!sld) {
    mainError("Serial port could not be opened!");
    exit(5);
  }

  /* set initial serial parameters */
  if (setSerialParms(19200) != Normal) {
    mainError("Couldn't set required serial parameters");
    exit(5);
  }

  sprintf(arexxPortName, "%sF", portNameToLoad);

  OutgoingRexx = InitARexxSingleApp(arexxPortName, "avm");
  if (!OutgoingRexx) {
    mainError("Couldn't open outgoing rexx port");
    exit(5);
  }

  globalCallBack = initCallBack;
  addCallBack(&globalCallBack, ARexxSignal(OutgoingRexx), signalDetectedCallBack);
}

