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

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

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

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

// These defines are used exclusively by the automatom in Establish()
#define Q0 0
#define Q1 1
#define Q2 2
#define Q3 3
#define Qf 4

ULONG __saveds __asm
ML_LaunchTask
(
  register __a0 struct IOExtLink *LinkIO,
  register __a1 struct IOExtSer *SerIO
)
{
  struct MsgPort *Port;
  ULONG ResultCode = 0;
  int i = 0;

  stcl_h(LinkIO->LinkProcName, LinkIO->Unit);
  strcpy(LinkIO->LinkPortName, LinkIO->LinkProcName);
  strins(LinkIO->LinkProcName, "LinkProc");
  strins(LinkIO->LinkPortName, "LinkPort");

  Port = FindPort(LinkIO->LinkPortName);
  if (!Port) {
    CreateNewProcTags(
        NP_Entry, AckTask,
        NP_StackSize, 4096,
        NP_Name, LinkIO->LinkProcName,
        NP_Arguments, LinkIO->LinkPortName,
        NP_CopyVars, TRUE,
        TAG_END);

    do {
      Delay(5);
      Port = FindPort(LinkIO->LinkPortName);
    } while (!Port && ++i < 20);

    if (Port) {
      LinkIO->IOLink.io_Command = MLCMD_INIT;
      LinkIO->IOLink.io_Data = (APTR)SerIO;
      ML_BeginIO((struct IORequest *)LinkIO);
      WaitIO((struct IORequest *)LinkIO);

      ResultCode++;
    }
  }

  return(ResultCode);
}

ULONG __saveds __asm
ML_EstablishTagList
(
  register __a0 struct IOExtLink *LinkIO,
  register __a1 struct IOExtSer *SerIO,
  register __a2 struct TagItem *tagList
)
{
  struct timerequest *TimerIO = NULL;
  struct MsgPort *TimerMP = NULL;
  ULONG ReturnStatus;
  char ACKStates[4] = {Q3, Q3, Qf, Q3};
  char ENQStates[4] = {Q2, Q2, Q2, Qf};
  char buf[3] = {ENQ, ACK, 0};
  char State = Q0;

  if (OpenTimerDevice(&TimerMP, &TimerIO)) {
    TimerIO->tr_node.io_Command = TR_ADDREQUEST;
    TimerIO->tr_time.tv_secs = 5;
    TimerIO->tr_time.tv_micro = 0;
    SendIO((struct IORequest *) TimerIO);

    SerIO->IOSer.io_Command = CMD_WRITE;
    SerIO->IOSer.io_Length = 1;
    SerIO->IOSer.io_Data = &(buf[0]);
    DoIO((struct IORequest *) SerIO);

    State = Q1;

    while (State != Qf) {
      SerIO->IOSer.io_Command = CMD_READ;
      SerIO->IOSer.io_Length = 1;
      SerIO->IOSer.io_Data = &(buf[2]);

      if (TimedIO((struct IORequest *) SerIO, 1)) {
        if (buf[2] == ACK)
          State = ACKStates[State];
        else if (buf[2] == ENQ) {
          SerIO->IOSer.io_Command = CMD_WRITE;
          SerIO->IOSer.io_Length = 1;
          SerIO->IOSer.io_Data = &(buf[1]);
          DoIO((struct IORequest *) SerIO);

          State = ENQStates[State];
        }
      }
      else if (State == Q1 || State == Q2) {
        SerIO->IOSer.io_Command = CMD_WRITE;
        SerIO->IOSer.io_Length = 1;
        SerIO->IOSer.io_Data = &(buf[0]);
        DoIO((struct IORequest *) SerIO);
      }

      if (CheckIO((struct IORequest *) TimerIO))
        break;
    }

    if (!CheckIO((struct IORequest *) TimerIO))
      AbortIO((struct IORequest *) TimerIO);
    WaitIO((struct IORequest *) TimerIO);

    SafeCloseDevice(TimerMP, (struct IORequest *)TimerIO);
  }

  if (State == Qf)
    if (ML_LaunchTask(LinkIO, SerIO))
      ReturnStatus = EstErr_OK;
    else
      ReturnStatus = EstErr_TASK_ERR;
  else
    ReturnStatus = EstErr_TIMEOUT;

  return(ReturnStatus);
}

void __saveds __asm
ML_Terminate
(
  register __a0 struct IOExtLink *LinkIO
)
{
  struct Task *LinkProc;

  if (LinkIO && (LinkProc = FindTask(LinkIO->LinkProcName))) {
    Signal((struct Task *)LinkProc, SIGBREAKF_CTRL_C);

    do {
      Delay(10);
    } while (FindTask(LinkIO->LinkProcName));
  }
}

struct LinkPkt __saveds __asm
*ML_AllocPkt(void)
{
  struct LinkPkt *Pkt;
  if (Pkt =  AllocMem(sizeof(struct LinkPkt), MEMF_PUBLIC | MEMF_CLEAR)) {
    Pkt->Data = NULL;
    Pkt->Length = 0;
  }

  return Pkt;
}

void __saveds __asm
ML_FreePkt
(
  register __a0 struct LinkPkt *Pkt
)
{
  if (Pkt) {
    if (Pkt->Data && (Pkt->Length > 0))
      FreeMem(Pkt->Data, Pkt->Length);
    FreeMem(Pkt, sizeof(struct LinkPkt));
  }
}

void __saveds __asm
ML_FreePktList
(
  register __a0 struct MinList *PktList
)
{
  struct LinkPkt *Pkt, *TmpPkt;

  if (PktList) {
    Pkt = (struct LinkPkt *)PktList->mlh_Head;

    while (Pkt->ml_Node.mln_Succ) {
      TmpPkt = (struct LinkPkt *)Pkt->ml_Node.mln_Succ;
      ML_FreePkt(Pkt);
      Pkt = TmpPkt;
    }
  }
}


ULONG __saveds __asm
ML_PacketizeData
(
  register __a0 struct MinList *PktList,
  register __a1 UBYTE *Data,
  register __d0 ULONG Length,
  register __d1 ULONG PktSize
)
{
  struct LinkPkt *tmp;

  if (PktSize <= 0)
    PktSize = Length;

  while (Length > 0) {
    if (tmp = ML_AllocPkt()) {
      tmp->Length = PktSize;
      if (tmp->Data = AllocMem(PktSize, MEMF_CLEAR | MEMF_PUBLIC)) {
        CopyMem(Data, tmp->Data, PktSize);
        Length -= PktSize;

        if (PktSize > Length)
          PktSize = Length;
        Data += PktSize;

        AddTail((struct List *)PktList, (struct Node *)tmp);
      }
      else {
        FreeMem(tmp, sizeof(struct LinkPkt));
        tmp = NULL;
      }
    }
    if (!tmp) {
      while (tmp = (struct LinkPkt *) RemHead((struct List *)PktList)) {
        if (tmp->Data)
          FreeMem(tmp->Data, tmp->Length);
        FreeMem(tmp, sizeof(struct LinkPkt));
      }
      Length = -1L;
    }
  }

  return (ULONG)(Length == 0);
}

ULONG __saveds __asm
ML_DePacketizeData
(
  register __a0 struct MinList *PktList,
  register __a1 UBYTE *Data,
  register __d0 ULONG Length
)
{
  struct LinkPkt *Pkt;
  ULONG TotalSize = 0L;
  ULONG PktSize;
  UBYTE *CurBuf = Data;

  if (PktList && Data && Length) {
    Pkt = (struct LinkPkt *)PktList->mlh_Head;

    while (Length > 0 && Pkt->ml_Node.mln_Succ) {
      PktSize = Pkt->Length;

      if (PktSize > Length)
        PktSize = Length;

      CopyMem(Pkt->Data, CurBuf, PktSize);

      Length -= PktSize;
      CurBuf += PktSize;
      TotalSize += PktSize;

      Pkt = (struct LinkPkt *)Pkt->ml_Node.mln_Succ;
    }
  }

  return(TotalSize);
}

ULONG __saveds __asm
ML_PacketDataSize
(
  register __a0 struct MinList *PktList
)
{
  struct LinkPkt *Pkt;
  ULONG TotalSize = 0L;

  if (PktList) {
    Pkt = (struct LinkPkt *)PktList->mlh_Head;

    while (Pkt->ml_Node.mln_Succ) {
      TotalSize += Pkt->Length;
      Pkt = (struct LinkPkt *)Pkt->ml_Node.mln_Succ;
    }
  }

  return(TotalSize);
}

struct IOExtLink *ML_GetMsg(struct MsgPort *MPort, ULONG PipeBit)
{
  struct IOExtLink *LinkIO;

  Forbid();

  if (LinkIO = (struct IOExtLink *)GetMsg(MPort))
    LinkIO->Flags = (LinkIO->Flags & (~ML_PIPE2)) | PipeBit;

  Permit();

  return (LinkIO);
}

void ML_ReplyMsg(struct IOExtLink *LinkReq)
{
  Forbid();

  if (LinkReq) {
    LinkReq->Flags &= ~ML_PIPE2;
    ReplyMsg((struct Message *)LinkReq);
  }

  Permit();
}
