/*
** NAME: ModemLinkTask.c
** AUTHOR: Mike Veroukis
*/


#include <exec/errors.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/types.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>

#include <proto/dos.h>
#include <proto/exec.h>

#include <strings.h>
#include <stdio.h>
#include <dos.h>

#include "ModemLinkTask.h"
#include "Link.h"
#include "ModemLinkAPI.h"
#include "CRC.h"
#include "DeviceStuff.h"

char ReadPacketNum(struct IOExtSer *SerReadIO)
{
  char PktLong = 0;

  if (SerReadIO) {
    SerReadIO->IOSer.io_Command = CMD_READ;
    SerReadIO->IOSer.io_Length = sizeof(char);
    SerReadIO->IOSer.io_Data = (APTR)&PktLong;

    // To be changed to TimedIO() later on
    DoIO((struct IORequest *) SerReadIO);
  }

  return (PktLong);
}

int ReadPacketWord(struct IOExtSer *SerReadIO)
{
  int PktWord = -1;

  if (SerReadIO) {
    SerReadIO->IOSer.io_Command = CMD_READ;
    SerReadIO->IOSer.io_Length = sizeof(int);
    SerReadIO->IOSer.io_Data = (APTR)&PktWord;

    // To be changed to TimedIO() later on
    DoIO((struct IORequest *) SerReadIO);
  }

  return (PktWord);
}

LONG ReadPacketLong(struct IOExtSer *SerReadIO)
{
  LONG PktLong = -1;

  if (SerReadIO) {
    SerReadIO->IOSer.io_Command = CMD_READ;
    SerReadIO->IOSer.io_Length = sizeof(LONG);
    SerReadIO->IOSer.io_Data = (APTR)&PktLong;

    // To be changed to TimedIO() later on
    DoIO((struct IORequest *) SerReadIO);
  }

  return (PktLong);
}

BYTE *ReadPacketData(struct IOExtSer *SerReadIO, LONG Length)
{
  BYTE *PktData = NULL;

  if (SerReadIO && Length > 0) {
    if (PktData = AllocMem(Length, MEMF_PUBLIC)) {
      SerReadIO->IOSer.io_Command = CMD_READ;
      SerReadIO->IOSer.io_Length = Length;
      SerReadIO->IOSer.io_Data = (APTR)PktData;

      // To be changed to TimedIO() later on
      DoIO((struct IORequest *) SerReadIO);
    }
  }

  return (PktData);
}

int ReadPacket(struct IOExtSer *SerIO, struct LinkPkt **Pkt, UBYTE CurPktNum)
{
  UBYTE PktNum;
  ULONG CRC;
  int err = 0;

  if (*Pkt = ML_AllocPkt()) {
    PktNum = ReadPacketNum(SerIO);

    SerIO->IOSer.io_Command = CMD_READ;
    SerIO->IOSer.io_Length = (ULONG)&((*Pkt)->Data) - (ULONG)&((*Pkt)->Length);
    SerIO->IOSer.io_Data = &((*Pkt)->Length);
    DoIO((struct IORequest *) SerIO);

    (*Pkt)->Data = ReadPacketData(SerIO, (*Pkt)->Length);

    if (PktNum != CurPktNum)
      err = RPERR_PKTNUM;

    CRC = GetCRC((*Pkt)->Data, (*Pkt)->Length);

    if (CRC != (*Pkt)->CRC)
      err = RPERR_PKTCRC;
 
    if (err) {
      ML_FreePkt(*Pkt);
    }
  }
  else
    err = RPERR_NOPKT;

  return (err);
}

void WritePacket(struct IOExtSer *SerIO, struct LinkPkt *Pkt, UBYTE CurPktNum)
{
  UBYTE buf[2];

  if (SerIO && Pkt) {
    buf[0] = SOH;
    buf[1] = CurPktNum;

    Pkt->CRC = GetCRC(Pkt->Data, Pkt->Length);

    SerIO->IOSer.io_Command = CMD_WRITE;
    SerIO->IOSer.io_Length = 2 * sizeof(char);
    SerIO->IOSer.io_Data = (APTR)buf;
    DoIO((struct IORequest *) SerIO);

    SerIO->IOSer.io_Command = CMD_WRITE;
    SerIO->IOSer.io_Length = (ULONG)&(Pkt->Data) - (ULONG)&(Pkt->Length);
    SerIO->IOSer.io_Data = &(Pkt->Length);
    DoIO((struct IORequest *) SerIO);


    SerIO->IOSer.io_Command = CMD_WRITE;
    SerIO->IOSer.io_Length = Pkt->Length;
    SerIO->IOSer.io_Data = (APTR)Pkt->Data;
    DoIO((struct IORequest *) SerIO);
  }
}

void SendCode(struct IOExtSer *SerWriteIO,  BYTE Code, BYTE CurPktNum)
{
  UBYTE buf[2];

  if (SerWriteIO) {
    buf[0] = Code;
    buf[1] = CurPktNum;

    SerWriteIO->IOSer.io_Command = CMD_WRITE;
    SerWriteIO->IOSer.io_Data = (APTR)buf;
    if (CurPktNum < 0)
      SerWriteIO->IOSer.io_Length = 1;
    else
      SerWriteIO->IOSer.io_Length = 2;

    // To be changed to TimedIO() later on
    DoIO((struct IORequest *) SerWriteIO);
  }
}

void ResetTimer(struct timerequest *TimerIO)
{
  DoAbortIO((struct IORequest *) TimerIO);

  TimerIO->tr_node.io_Command = TR_ADDREQUEST;
  TimerIO->tr_time.tv_secs = 3;
  TimerIO->tr_time.tv_micro = 0;
  SendIO((struct IORequest *) TimerIO);
}


void SetTimer(struct timerequest *TimerIO)
{
  TimerIO->tr_node.io_Command = TR_ADDREQUEST;
  TimerIO->tr_time.tv_secs = 3;
  TimerIO->tr_time.tv_micro = 0;
  SendIO((struct IORequest *) TimerIO);
}

void __saveds __asm AckTask(register __d0 ULONG Len, register __a0 char *PortName)
{
  struct IOExtSer *SerIO;          // points to the SerIO req. that's passed to us
  struct IOExtSer *SerWriteIO;     // is used exclusively for writing to serial device
  struct IOExtSer *SerReadIO;      // is used exclusively for reading from serial device
  struct timerequest *TimerIO = NULL; // used for TIMEOUTs when waiting for ACK
  struct MsgPort *SerWriteMP;      // message port for SerWriteIO
  struct MsgPort *SerReadMP;       // message port for SerReadIO
  struct MsgPort *TimerMP = NULL;  // message port for TimerIO
  struct MsgPort *LinkMP;          // we get all our IO reqs through this port
  struct LinkPkt OutPkt;           // contents of packet to write to serial device
  struct LinkPkt *InPkt = NULL;    // points to packet just read from serial device
  struct List InPktList;           // list of packets read, but not yet sent to user
  struct IOExtLink *ReadReq = NULL;   // current user read IO request structure
  struct IOExtLink *WriteReq = NULL;  // current user write IO request structure
  struct IOExtLink *Req;           // next read/write IO request structure

  ULONG SerWriteBit, SerReadBit;   // signal bits for serial read/write
  ULONG TimerBit, PktBit;          // signal bits for timer and new LinkIO packet
  ULONG WaitMask, Sig = 0;         // Mask for Wait()

  int err;

  UBYTE CurInPktNum = 0;
  UBYTE CurOutPktNum = 0;
  UBYTE WaitACK = 0;
  UBYTE WaitPacket = 0;
  UBYTE ReadyToSend = 1;
  UBYTE PktType;


  NewList(&InPktList);

  if (LinkMP = CreateMsgPort()) {
    LinkMP->mp_Node.ln_Name = PortName;
    LinkMP->mp_Node.ln_Pri = 0;
    AddPort(LinkMP);

    WaitPort(LinkMP);
    Req = (struct IOExtLink *)GetMsg(LinkMP);
    SerIO = (struct IOExtSer *)Req->IOLink.io_Data;

    if (Req->IOLink.io_Device) {
      putreg(REG_A6, (long)Req->IOLink.io_Device); 
      geta4();
    }

    ReplyMsg((struct Message *)Req);

    if (OpenTimerDevice(&TimerMP, &TimerIO)) {

      Req = NULL;

      if (CloneIO((struct IORequest *)SerIO, &SerReadMP, (struct IORequest **)&SerReadIO)) {
        if (CloneIO((struct IORequest *)SerIO, &SerWriteMP, (struct IORequest **)&SerWriteIO)) {

          SerWriteBit = 1 << SerWriteMP->mp_SigBit;
          SerReadBit = 1 << SerReadMP->mp_SigBit;
          TimerBit = 1 << TimerMP->mp_SigBit;
          PktBit = 1 << LinkMP->mp_SigBit;

          WaitMask = SerWriteBit | SerReadBit | TimerBit | PktBit | SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E;

          SerReadIO->IOSer.io_Command = CMD_READ;
          SerReadIO->IOSer.io_Length = ID_LENGTH;
          SerReadIO->IOSer.io_Data = (APTR)&PktType;
          SendIO((struct IORequest *) SerReadIO);

          while (!(Sig & SIGBREAKF_CTRL_C)) {
            Sig = Wait(WaitMask);

            if (WaitACK && CheckIO((struct IORequest *) TimerIO)) {
              WaitIO((struct IORequest *) TimerIO);
              WritePacket(SerWriteIO, &OutPkt, CurOutPktNum);
              WaitACK = 1;
              ResetTimer(TimerIO);
            }

            if (CheckIO((struct IORequest *) SerReadIO)) {
              WaitIO((struct IORequest *) SerReadIO);
              // remember, PktType is set by io_Data above
              switch (PktType) {
                case SOH:
                  err = ReadPacket(SerReadIO, &InPkt, CurInPktNum);
                  if (!err) {
                    AddTail(&InPktList, (struct Node *)InPkt);
                    SendCode(SerWriteIO, ACK, CurInPktNum);
                    CurInPktNum ^= 1;
                  }
                  else if (err == RPERR_PKTNUM) 
                    SendCode(SerWriteIO, ACK, CurInPktNum ^ 1);
                  else
                    SendCode(SerWriteIO, GACK, -1);

                  if (WaitACK)
                    ResetTimer(TimerIO);

                  break;
                case ACK:
                  if (CurOutPktNum == ReadPacketNum(SerReadIO)) {
                    DoAbortIO((struct IORequest *) TimerIO);
                    CurOutPktNum ^= 1;
                    ReadyToSend = 1;
                    ML_ReplyMsg(WriteReq);
                    WriteReq = NULL;
                    WaitACK = 0;
                  }
                  break;
                case GACK:
                  WritePacket(SerWriteIO, &OutPkt, CurOutPktNum);
                  WaitACK = 1;
                  ResetTimer(TimerIO);
                  break;
                case ENQ:
                  SendCode(SerWriteIO, ACK, -1);
                  break;
                case CAN:
                  Signal(FindTask(NULL), SIGBREAKF_CTRL_C);
                  break;
              }
              SerReadIO->IOSer.io_Command = CMD_READ;
              SerReadIO->IOSer.io_Length = ID_LENGTH;
              SerReadIO->IOSer.io_Data = (APTR)&PktType;
              SendIO((struct IORequest *) SerReadIO);
            }

            // Check for aborted requests...
            if (Req && Req->IOLink.io_Error == IOERR_ABORTED) {
              ML_ReplyMsg(Req);
              Req = NULL;
            }

            if (ReadReq && ReadReq->IOLink.io_Error == IOERR_ABORTED) {
              ML_ReplyMsg(ReadReq);
              ReadReq = NULL;
              WaitPacket = 0;
            }

            // Read incomming packets
            if (!Req) {
              Req = ML_GetMsg(LinkMP, ML_PIPE1);
            }

            if (Req && !ReadReq &&
                (Req->IOLink.io_Command == MLCMD_READ ||
                Req->IOLink.io_Command == CMD_READ)) {
              ReadReq = Req;
              Req = ML_GetMsg(LinkMP, ML_PIPE1);
              WaitPacket = 1;
            }

            if (!WriteReq &&
                (Req && Req->IOLink.io_Command == MLCMD_WRITE ||
                Req && Req->IOLink.io_Command == CMD_WRITE)) {
              WriteReq = Req;
              Req = NULL;

              if (Req && Req->IOLink.io_Command == MLCMD_WRITE)
                CopyMem(WriteReq->IOLink.io_Data, &OutPkt, sizeof(struct LinkPkt));
              else {
                if (WriteReq->IOLink.io_Length < 0)
                  OutPkt.Length = strlen(WriteReq->IOLink.io_Data);
                else
                  OutPkt.Length = WriteReq->IOLink.io_Length;

                OutPkt.Data = WriteReq->IOLink.io_Data;
              }
            }

            // If ready to send Pkt and out going packet exists, send it.
            if (ReadyToSend && WriteReq) {
              WriteReq->Flags = WriteReq->Flags | ML_PIPE2;
              WritePacket(SerWriteIO, &OutPkt, CurOutPktNum);

              if (WaitACK)
                ResetTimer(TimerIO);
              else
                SetTimer(TimerIO);

              WaitACK = 1;
              ReadyToSend = 0;
            }

            // If incomming Pkt is queued and we have Read req, reply it.
            if (ReadReq && WaitPacket && !(IsListEmpty(&InPktList))) {
              InPkt = (struct LinkPkt *)RemHead(&InPktList);

              if (ReadReq->IOLink.io_Command == CMD_READ) {
                ReadReq->IOLink.io_Data = InPkt->Data;
                ReadReq->IOLink.io_Length = InPkt->Length;
                FreeMem(InPkt, sizeof (struct LinkPkt));
              }
              else
                ReadReq->IOLink.io_Data = InPkt;

              ML_ReplyMsg(ReadReq);
              InPkt = NULL;
              ReadReq = NULL;
              WaitPacket = 0;
            }

            // Check CTRL-C -- shut down if CTRL-C detected.
            if (Sig & SIGBREAKF_CTRL_C) {
              if (WaitACK)
                DoAbortIO((struct IORequest *) TimerIO);

              SendCode(SerWriteIO, CAN, EOT);

              if (WriteReq) {
                WriteReq->IOLink.io_Error = IOERR_ABORTED;
                ML_ReplyMsg(WriteReq);
              }

              if (ReadReq) {
                ReadReq->IOLink.io_Error = IOERR_ABORTED;
                ML_ReplyMsg(ReadReq);
              }
            }

            // ^C or ^E arrives
            // this continues the shut down process from above and since the
            // CLEAR command issued by ^E does the same thing, they share
            // the same code:
            // deallocate all buffered incomming packets
            if ((Sig & SIGBREAKF_CTRL_C) || (Sig & SIGBREAKF_CTRL_E)) {
              while (InPkt = (struct LinkPkt *)RemHead(&InPktList)) {
                FreeMem(InPkt->Data, InPkt->Length);
                FreeMem(InPkt, sizeof(struct LinkPkt));
              }
            }

            SerWriteIO->IOSer.io_Command = SDCMD_QUERY;
            DoIO((struct IORequest *) SerWriteIO);

            if (SerWriteIO->io_Status & (1 << 5))
              Signal(FindTask(NULL), SIGBREAKF_CTRL_C);
          }

          DeleteIO_MP(SerWriteMP, (struct IORequest *) SerWriteIO);
        }

        DoAbortIO((struct IORequest *) SerReadIO);
        DeleteIO_MP(SerReadMP, (struct IORequest *) SerReadIO);
      }

      if (WaitACK)
        DoAbortIO((struct IORequest *)TimerIO);
      SafeCloseDevice(TimerMP, (struct IORequest *)TimerIO);

    }

    // remove all queued IO requests and remove MsgPort as well
    Forbid();
    if (!Req) 
      Req = ML_GetMsg(LinkMP, ML_PIPE1);

    while (Req) {
      Req->IOLink.io_Error = IOERR_ABORTED;
      ML_ReplyMsg(Req);
      Req = ML_GetMsg(LinkMP, ML_PIPE1);
    }

    RemPort(LinkMP);
    DeleteMsgPort(LinkMP);
    Permit();
  }
}
