/**
***  IPDial     Script program for initializing a SLIP connection
***  Copyright  (C)   1994    Jochen Wiedmann
***
***  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.
***
***  This program is distributed in the hope that it will be useful,
***  but WITHOUT ANY WARRANTY; without even the implied warranty of
***  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
***  GNU General Public License for more details.
***
***  You should have received a copy of the GNU General Public License
***  along with this program; if not, write to the Free Software
***  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***
***
***
***  This file implements the communication with the serial.device.
***
***
***  Computer: Amiga 1200                       Compiler: Dice 3.01
***
***  Author:    Jochen Wiedmann
***             Am Eisteich 9
***             72555 Metzingen
***             Germany
***
***             Phone: (+0049) 7123 / 14881
***             Internet: jochen.wiedmann@zdv.uni-tuebingen.de
**/





/**
***  Include files
**/
#ifndef IPDIAL_H
#include "IPDial.h"
#endif
#include <ctype.h>
#include <devices/serial.h>
#include <devices/timer.h>

#ifndef MIN
#define MIN(a,b) (((a)>(b))?(b):(a))
#endif





/**
***  Local variables.
**/
STATIC APTR MySerReq            = NULL;
STATIC APTR MyTimeReq           = NULL;
STATIC UBYTE SerBuffer[4096];




/**
***  Cleanup function.
**/
VOID SerialCleanup(VOID)

{ DeviceIODelete(MySerReq);
  DeviceIODelete(MyTimeReq);
}





/**
***  Table of error messages created by the serial.device.
**/
STATIC VOID SerialShowError(ULONG Error)

{ STRPTR ptr;

  switch(Error)
  { case 1:
      ptr = (STRPTR) "Serial device busy";
      break;
    case 2:
      ptr = (STRPTR) "Baud rate not supported";
      break;
    case 4:
      ptr = (STRPTR) "Memory error";
      break;
    case 5:
      ptr = (STRPTR) "Invalid parameters";
      break;
    case 6:
      ptr = (STRPTR) "Line error";
      break;
    case 9:
      ptr = (STRPTR) "Parity error";
      break;
    case 11:
      ptr = (STRPTR) "Timer error";
      break;
    case 12:
      ptr = (STRPTR) "Buffer overflow";
      break;
    case 13:
      ptr = (STRPTR) "No DSR";
      break;
    case 15:
      ptr = (STRPTR) "Break detected";
      break;
    default:
      ptr = (STRPTR) "Unknown error";
      break;
  }

  fprintf(stderr, "Serial device: %s.\n", ptr);
}





/**
***  This function opens the serial device. It can be closed
***  using DeviceIODelete().
**/
STATIC ULONG SerialDecodeProtocol(struct IOExtSer *SerReq, STRPTR Protocol)

{ if (stricmp((char *) Protocol, "XONXOFF") == 0)
  { SerReq->io_SerFlags &= ~(SERF_XDISABLED|SERF_7WIRE);
  }
  else if (stricmp((char *) Protocol, "NONE") == 0)
  { SerReq->io_SerFlags |= SERF_XDISABLED;
    SerReq->io_SerFlags &= ~SERF_7WIRE;
  }
  else if (stricmp((char *) Protocol, "RTSCTS") == 0  ||
	   stricmp((char *) Protocol, "7WIRE") == 0)
  { SerReq->io_SerFlags |= (SERF_XDISABLED|SERF_7WIRE);
  }
  else
  { return(FALSE);
  }
  return(TRUE);
}





ULONG SerialOpen(STRPTR DeviceName, STRPTR Protocol, ULONG Unit)

{ ULONG error;
  struct IOExtSer *RealSerReq;

  if (!(MySerReq = DeviceIOCreate(sizeof(struct IOExtSer))))
  { fprintf(stderr, "Failed to open %s: Memory error.\n",
	    DeviceName);
    exit(10);
  }
  RealSerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  RealSerReq->io_SerFlags = 0;
  if (Protocol)
  { if (!SerialDecodeProtocol(RealSerReq, Protocol))
    { return(FALSE);
    }
  }
  if ((error = DeviceIOOpen(DeviceName, Unit, MySerReq, 0)))
  { SerialShowError(error);
    exit(10);
  }
  if(Protocol)
  { SerialSetProtocol(Protocol);
  }

  if (!(MyTimeReq = DeviceIOCreate(sizeof(struct timerequest))))
  { fprintf(stderr, "Failed to open timer.device: Memory error.\n");
    exit(10);
  }
  if ((error = DeviceIOOpen((STRPTR) "timer.device", UNIT_VBLANK,
				     MyTimeReq, 0)))
  { fprintf(stderr, "Failed to open timer.device: Error %ld\n", error);
    exit(10);
  }
  return(TRUE);
}





/**
***  This function sends a string to the serial device.
**/
VOID SerialSend(STRPTR str, ULONG len)

{ struct IOExtSer *req;

#ifdef DEBUG
  printf("SerialSend: %s\n", str);
#endif
  req = (struct IOExtSer *) DeviceIOReq(MySerReq);
  req->IOSer.io_Length = strlen((char*) str);
  req->IOSer.io_Data = str;
  DeviceIODo(MySerReq, CMD_WRITE);
}





/**
***  This function is used to wait for a certain string.
***
***  The function will return if either a timeout occurs
***  (never happens, if timeout == -1) or if one of the
***  strings in the args array is read from the serial.device.
***
***  Result is the number of the string or -1.
**/
STATIC APTR waitBuffer = NULL;
LONG SerialWait(STRPTR *args, LONG timeout)

{ ULONG sigs;
  ULONG rsigs;
  BYTE error;
  struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);

  /**
  ***  Be sure, that the buffer is valid.
  **/
  if (!waitBuffer  &&  !(waitBuffer = BufferCreate()))
  { perror("malloc");
    exit(10);
  }

  /**
  ***  Clear the buffer.
  **/
  BufferClear(waitBuffer);

  sigs = SIGBREAKF_CTRL_C | DeviceIOSignal(MySerReq);

  if (timeout != -1)
  { struct timerequest *tr = (struct timerequest *) DeviceIOReq(MyTimeReq);

    tr->tr_time.tv_secs = timeout;
    tr->tr_time.tv_micro = 0;
    DeviceIOSend(MyTimeReq, TR_ADDREQUEST);
    sigs |= DeviceIOSignal(MyTimeReq);
  }

  for(;;)
  { LONG result;

    /**
    ***  First ask, if any data is present on the serial line.
    ***  May be, the string we are waiting for is already present?
    **/
    do
    { DeviceIODo(MySerReq, SDCMD_QUERY);
      if ((result = SerReq->IOSer.io_Actual))
      { SerReq->IOSer.io_Data = SerBuffer;
	SerReq->IOSer.io_Length = MIN(sizeof(SerBuffer)-1, SerReq->IOSer.io_Actual);
	if ((error = DeviceIODo(MySerReq, CMD_READ)))
	{ SerialShowError(error);
	  exit(10);
	}
	if (EchoMode)
	{ int i;

	  for (i = 0;  i < SerReq->IOSer.io_Actual;  i++)
	  { putchar(SerBuffer[i]);
	  }
	  fflush(stdout);
	}
	BufferExtend(waitBuffer, SerBuffer, SerReq->IOSer.io_Actual);
      }
    }
    while (result);


    if ((result = BufferCheck(waitBuffer, args)) >= 0)
    { DeviceIOAbort(MyTimeReq);
      return(result);
    }

    /**
    ***  It isn't, send a request for one byte.
    **/
    SerReq->IOSer.io_Length = 1;
    SerReq->IOSer.io_Data = SerBuffer;
    DeviceIOSend(MySerReq, CMD_READ);

    rsigs = Wait(sigs);

    if (rsigs & SIGBREAKF_CTRL_C)
    { printf("Ctrl-C\n");
      exit(10);
    }
    else if (rsigs & DeviceIOSignal(MySerReq))
    { BYTE error;

      /**
      ***  Data received, add it to the buffer and check for more.
      **/
      if ((error = DeviceIOWait(MySerReq)))
      { SerialShowError(error);
	exit(10);
      }
      if (EchoMode)
      { putchar(*SerBuffer);
	fflush(stdout);
      }
      BufferExtend(waitBuffer, SerBuffer, 1);
    }
    else
    { /**
      ***  Timeout
      **/
      DeviceIOWait(MyTimeReq);
      DeviceIOAbort(MySerReq);
      return(-1);
    }
  }
}




/**
***  Return buffer read by the last "Wait" command.
**/
STRPTR SerialWaitBuffer(VOID)

{ if (!(waitBuffer))
  { return((STRPTR) "");
  }
  else
  { return(BufferBuffer(waitBuffer));
  }
}






/**
***  This function implements a terminal mode. It gives the users input
***  to the modem and likewise the modems output to the user, as long as
***  the user doesn't enter Ctrl-\.
**/
VOID SerialTerminal(VOID)

{ struct DosPacket *dp;
  struct MsgPort *port = NULL;
  struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  APTR SerSendReq = NULL;
  struct IOExtSer *serSendReq;
  char buffer[128];
  char sendBuffer[256]; /* Twice the size of buffer */
  ULONG status = 10;
  BPTR cis = Input();
  ULONG sigs;


  if (!cis)
  { fprintf(stderr, "Can't use nil: as input device.\n");
    exit(10);
  }

  if ((dp = AllocDosObject(DOS_STDPKT, NULL)))
  { if ((port = CreateMsgPort()))
    { if ((SerSendReq = DeviceIOCreate(sizeof(struct IOExtSer))))
      { serSendReq = (struct IOExtSer *) DeviceIOReq(SerSendReq);
	serSendReq->IOSer.io_Device = SerReq->IOSer.io_Device;
	serSendReq->IOSer.io_Unit = SerReq->IOSer.io_Unit;
	status = 0;
      }
    }
  }

  if (status)
  { fprintf(stderr, "Memory error.\n");
    goto ExitSerialTerminal;
  }

  dp->dp_Type = ACTION_READ;
  dp->dp_Arg1 = ((struct FileHandle *) BADDR(cis))->fh_Arg1;
  dp->dp_Arg2 = (ULONG) buffer;
  dp->dp_Arg3 = sizeof(buffer);
  SendPkt(dp, (struct MsgPort *) ((struct FileHandle *) BADDR(cis))->fh_Type, port);

  SerReq->IOSer.io_Length = 1;
  SerReq->IOSer.io_Data = SerBuffer;
  DeviceIOSend(MySerReq, CMD_READ);

  sigs = (1 << port->mp_SigBit) | SIGBREAKF_CTRL_C | DeviceIOSignal(MySerReq);

  for(;;)
  { LONG rsigs;

    rsigs = Wait(sigs);

    if (rsigs & SIGBREAKF_CTRL_C)
    { fprintf(stderr, "Ctrl-C\n");
      status = 5;
      DeviceIOAbort(MySerReq);
      AbortPkt(port, dp);
      WaitPort(port);
      goto ExitSerialTerminal;
    }

    if (rsigs & (1 << port->mp_SigBit))
    { GetMsg(port);
      if (dp->dp_Res1 == -1)
      { fprintf(stderr, "Error %ld while reading input.\n", dp->dp_Res2);
	status = 10;
      }
      if (dp->dp_Res1 == -1  ||  dp->dp_Res1 == 0)
      { DeviceIOAbort(MySerReq);
	goto ExitSerialTerminal;
      }

      { int i, bufLen;  /*  Extend LF to CR/LF and send resulting string.   */

	bufLen = 0;
	for (i = 0;  i < dp->dp_Res1;  i++)
	{ if (buffer[i] == '\n')
	  { sendBuffer[bufLen++] = '\r';
	    sendBuffer[bufLen++] = '\n';
	  }
	  else
	  { sendBuffer[bufLen++] = buffer[i];
	  }
	}

	if (bufLen > 0)
	{ serSendReq->IOSer.io_Length = bufLen;
	  serSendReq->IOSer.io_Data = sendBuffer;
	  DeviceIODo(SerSendReq, CMD_WRITE);
	}
      }

      dp->dp_Type = ACTION_READ;
      dp->dp_Arg1 = ((struct FileHandle *) BADDR(cis))->fh_Arg1;
      dp->dp_Arg2 = (ULONG) buffer;
      dp->dp_Arg3 = sizeof(buffer);
      SendPkt(dp, (struct MsgPort *) ((struct FileHandle *) BADDR(cis))->fh_Type, port);
    }

    if (rsigs & DeviceIOSignal(MySerReq))
    { LONG error;
      LONG result;

      if ((error = DeviceIOWait(MySerReq)))
      { SerialShowError(error);
	AbortPkt(port, dp);
	WaitPort(port);
	goto ExitSerialTerminal;
      }
      putchar(*SerBuffer);

      do
      { DeviceIODo(MySerReq, SDCMD_QUERY);
	if ((result = SerReq->IOSer.io_Actual))
	{ SerReq->IOSer.io_Data = SerBuffer;
	  SerReq->IOSer.io_Length = MIN(sizeof(SerBuffer)-1, SerReq->IOSer.io_Actual);
	  if ((error = DeviceIODo(MySerReq, CMD_READ)))
	  { SerialShowError(error);
	    exit(10);
	  }
	  fwrite(SerBuffer, SerReq->IOSer.io_Actual, 1, stdout);
	  fflush(stdout);
	}
      }
      while (result);

      SerReq->IOSer.io_Length = 1;
      SerReq->IOSer.io_Data = SerBuffer;
      DeviceIOSend(MySerReq, CMD_READ);
    }
  }


ExitSerialTerminal:
  if (dp)
  { FreeDosObject(DOS_STDPKT, dp);
  }
  if (port)
  { DeleteMsgPort(port);
  }
  if (SerSendReq)
  { DeviceIODelete(SerSendReq);
  }
  if (status)
  { exit(status);
  }
}





/**
***  This function shows the current serial parameters.
**/
VOID SerialShowParms(VOID)

{ STRPTR Parity, Protocol;
  struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);

  printf("\tBaud = %ld\n", SerReq->io_Baud);
  printf("\tDataBits = %ld\n", (ULONG) SerReq->io_ReadLen);
  printf("\tStopBits = %ld\n", (ULONG) SerReq->io_StopBits);
  printf("\tBufSize = %ld\n", SerReq->io_RBufLen);
  if (SerReq->io_SerFlags & SERF_PARTY_ON)
  { if (SerReq->io_SerFlags & SERF_PARTY_ODD)
    { Parity = (STRPTR) "Odd";
    }
    else
    { Parity = (STRPTR) "Even";
    }
  }
  else
  { Parity = (STRPTR) "None";
  }
  printf("\tParity = %s\n", Parity);
  if (SerReq->io_SerFlags & SERF_7WIRE)
  { if (SerReq->io_SerFlags & SERF_XDISABLED)
    { Protocol = (STRPTR) "RTS/CTS";
    }
    else
    { Protocol = (STRPTR) "RTS/CTS, XON/XOFF";
    }
  }
  else
  { if (SerReq->io_SerFlags & SERF_XDISABLED)
    { Protocol = (STRPTR) "None";
    }
    else
    { Protocol = (STRPTR) "XON/XOFF";
    }
  }
  printf("\tProtocol = %s\n\n", Protocol);
}





/**
***  Some functions for setting serial parameters.
**/
VOID SerialSetBaud(ULONG Baud)

{ struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  BYTE error;

  SerReq->io_Baud = Baud;
  if ((error = DeviceIODo(MySerReq, SDCMD_SETPARAMS)))
  { SerialShowError(error);
    exit(10);
  }
}
VOID SerialSetDataBits(UBYTE DataBits)

{ struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  BYTE error;

  SerReq->io_ReadLen = SerReq->io_WriteLen = DataBits;
  if ((error = DeviceIODo(MySerReq, SDCMD_SETPARAMS)))
  { SerialShowError(error);
    exit(10);
  }
}
VOID SerialSetStopBits(UBYTE StopBits)

{ struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  BYTE error;

  SerReq->io_StopBits = StopBits;
  if ((error = DeviceIODo(MySerReq, SDCMD_SETPARAMS)))
  { SerialShowError(error);
    exit(10);
  }
}
VOID SerialSetBufSize(ULONG BufSize)

{ struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  BYTE error;

  if (BufSize & 0x3f)
  { fprintf(stderr,
	    "Warning: BufSize must be a multiple of 64, rounding up.\n");
    BufSize = (BufSize & 0x3f) + 0x40;
  }
  SerReq->io_RBufLen = BufSize;
  if ((error = DeviceIODo(MySerReq, SDCMD_SETPARAMS)))
  { SerialShowError(error);
    exit(10);
  }
}
ULONG SerialSetParity(STRPTR Parity)

{ struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  BYTE error;

  if (stricmp((char *) Parity, "NONE") == 0)
  { SerReq->io_SerFlags &= ~SERF_PARTY_ON;
  }
  else if (stricmp((char *) Parity, "EVEN") == 0)
  { SerReq->io_SerFlags |= SERF_PARTY_ON;
    SerReq->io_SerFlags &= ~SERF_PARTY_ODD;
  }
  else if (stricmp((char *) Parity, "ODD") == 0)
  { SerReq->io_SerFlags |= (SERF_PARTY_ON|SERF_PARTY_ODD);
  }
  else
  { return(FALSE);
  }

  if ((error = DeviceIODo(MySerReq, SDCMD_SETPARAMS)))
  { SerialShowError(error);
    exit(10);
  }
  return(TRUE);
}
ULONG SerialSetProtocol(STRPTR Protocol)

{ struct IOExtSer *SerReq = (struct IOExtSer *) DeviceIOReq(MySerReq);
  BYTE error;

  if (!(SerialDecodeProtocol(SerReq, Protocol)))
  { return(FALSE);
  }

  if ((error = DeviceIODo(MySerReq, SDCMD_SETPARAMS)))
  { SerialShowError(error);
    exit(10);
  }
  return(TRUE);
}
