#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

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

#include <devices/serial.h>
#include <hardware/cia.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>

#define NOTMAIN

#define ClrSignal(s)  SetSignal(0,s)

#include "/common/types.h"
#include "/common/defines.h"
#include "/common/structures.h"
#include "/common/strings.h"
#include "/common/errors.h"
#include "/common/shared_protos.h"
#include "/common/Files.h"

#include "Node_Input_protos.h"
#include "Node_Console_protos.h"
#include "/library/hbbscommon_protos.h"
#include "/library/hbbscommon_pragmas.h"
#include "nodelibrary/hbbsnode_protos.h"
#include "nodelibrary/hbbsnode_pragmas.h"

extern struct NodeData *N_ND;
extern struct BBSGlobalData *BBSGlobal;
extern struct Library *HBBSCommonBase;
extern struct Library *HBBSNodeBase;


void CleanupSerial( void )
{
  if (!N_ND->NodeDevice.SysopNode)
  {
    if (N_ND->SerBuffer)
    {
      if (N_ND->SerPort)
      {
        if (N_ND->SerWrite)
        {
          if (N_ND->SerOPEN)
          {
            if (N_ND->SerRead)
            {
              AbortSerRead(); // will only abort id request submitted..
              FreeVec(N_ND->SerRead);
            }
            CloseDevice((struct IORequest*)N_ND->SerWrite);
            N_ND->SerOPEN=FALSE;
          }
          DeleteExtIO((struct IORequest*)N_ND->SerWrite);
        }
        DeletePort(N_ND->SerPort);
      }
      FreeVec(N_ND->SerBuffer);
    }
  }
  N_ND->SerOK=FALSE;
}

void SerReset( void )
{
  // this routine reads in all waiting data from the serial port and then dumps it!
  UBYTE *mem;
  ULONG bytes;

  if (bytes=SerQueryData())
  {
    if (mem=AllocVec(bytes,MEMF_PUBLIC))
    {
      WaitSerReadBlock(mem,bytes); // *C* ooh naughty.. should really have somekind of wait() here..
      FreeVec(mem);
    }
  }
}

V_BOOL OpenSerial( void )
{
  N_ND->SerWaiting=FALSE;
  N_ND->SerOK=FALSE;
  N_ND->SerOPEN=FALSE;
  if (!N_ND->NodeDevice.SysopNode)
  {
    if (N_ND->SerBuffer=AllocVec(DEF_SERBUFLEN,MEMF_PUBLIC))
    {
      N_ND->SerBufferLen=DEF_SERBUFLEN;
      if (N_ND->SerPort=CreatePort(0,0))
      {
        if (N_ND->SerWrite = (struct IOExtSer *) CreateExtIO(N_ND->SerPort,(ULONG)sizeof(struct IOExtSer)))
        {
          N_ND->SerWrite->io_SerFlags       = SERF_SHARED|SERF_7WIRE|SERF_XDISABLED|SERF_RAD_BOOGIE;
          if (!(OpenDevice((STRPTR)N_ND->NodeDevice.SerialDevice,(ULONG)N_ND->NodeDevice.SerialUnit,(struct IORequest *)N_ND->SerWrite,0L) ))
          {
            N_ND->SerOPEN=TRUE;
            // kill data waiting..
            N_ND->SerWrite->IOSer.io_Command  = CMD_RESET;
            DoIO((struct IORequest *)N_ND->SerWrite);


            // setup port with correct baud rate etc..
            N_ND->SerWrite->IOSer.io_Command  = SDCMD_SETPARAMS;
            N_ND->SerWrite->io_SerFlags       |= SERF_XDISABLED;
            N_ND->SerWrite->io_Baud           = N_ND->NodeDevice.SerialBaud;
            DoIO((struct IORequest *)N_ND->SerWrite);


            if (N_ND->SerRead = (struct IOExtSer*)AllocVec(sizeof(struct IOExtSer),MEMF_PUBLIC) )
            {
              // duplicate data...
              memcpy(N_ND->SerRead,N_ND->SerWrite,sizeof(struct IOExtSer));
              SerReset();
              N_ND->SerOK=TRUE;
              return(TRUE);
            }
          }
          else HBBS_DoErrorMessage(EMSG_NODEVICE,N_ND->NodeNum,NULL);
        }
      }
    }
    CleanupSerial();
  }
  return(FALSE);
}

ULONG ModemGetLine( void )
{
  ULONG ReturnedSigs;
  UBYTE CurrentChar[2]={0,0}; // 2 chars, use &currentchar for null terminated string type..
  ULONG retval=IN_NOTHING; // this must NOT be returned to the caller!!!!!

  struct TimerData *TD;

  N_ND->CurrentLine[0]=0; // null terminate the string..

  if (TD=SubmitTimer(N_ND->NodeTimer,N_ND->NodeDevice.MaxCommandWait,0))
  {
    do
    {
      if (!N_ND->SerWaiting) SendSerReadData();

      // we cant use SetupSigs() and WaitAllSigs() because if we are resetting
      // the modem after a LOGIN_LOCAL sersigs is set to 0... :-)
      // *C* add timeout
      N_ND->SerSig=DEF_SERSIG;
      N_ND->TimerSig=DEF_TIMERSIG;
      ReturnedSigs=Wait(N_ND->SerSig | N_ND->TimerSig);

      while (HandleSerSigs(ReturnedSigs) && retval==IN_NOTHING)
      {
        // ok, we got some data from somehwere...

        CurrentChar[0]=N_ND->IBuffer[0];

        if (CurrentChar[0]>=0x20 && CurrentChar[0]!=127) // i.e. not unprintable or a control char..
        {
          strcat(N_ND->CurrentLine,CurrentChar);
        }
        else
        {
          if (CurrentChar[0]=='\n')
          {
            if (strlen(N_ND->CurrentLine)>0) retval=IN_GOTLINE;
          }
        }
      }

      if ((retval==IN_NOTHING) && (CheckTimer(N_ND->NodeTimer,TD)))
      {
        TD=NULL;
        retval=IN_TIMEOUT;
      }
    }
    while (retval==IN_NOTHING);
    if (TD) AbortTimer(N_ND->NodeTimer,TD);

    if (N_ND->NodeDevice.ModemDebug && N_ND->ConOK)
    {
      ConWriteStr(N_ND->CurrentLine);
      ConWriteStr(str_CRLF);
    }
    if (N_ND->SerWaiting) AbortSerRead();
  }
  return(retval);
}

void ModemError(char *command, char *result)
{
  char errstr[2048]; // big enough ?

  if (N_ND->NodeDevice.ModemLog)
  {
    sprintf(errstr,"When Sending the command \"%s\" the modem returned \"%s\"",command,result);

    HBBS_LogError(N_ND->NodeSettings.ModemLogFile,ERR_GENERAL,errstr,TYPE_WARNING);
  }
}

void TurnEchoOn( void )
{
  struct Node *node;
  short retriesleft;
  BOOL Done;
  ULONG result;


  PutText("\033[37;0m\n");
  for (node=N_ND->NodeDevice.TurnOnEcho->lh_Head;node->ln_Succ;node=node->ln_Succ)
  {
    if (node->ln_Name)
    {
      Done=FALSE;
      retriesleft=N_ND->NodeDevice.EchoRetries;
      do
      {
        if (retriesleft!=N_ND->NodeDevice.EchoRetries)
        {
          ConWriteStr("Retrying Modem...\n\r");
        }

        retriesleft--;
        Delay(N_ND->NodeDevice.TurnOnEchoDelay);
        SerReset();
        SerWriteStr(node->ln_Name);
        SerWriteStr(str_CRLF);
        if (result=ModemGetLine()==IN_GOTLINE)
        {
          // ok, got some input..

          if (stricmp(node->ln_Name,N_ND->CurrentLine)==0)
          {  // echo is already on, so discard this line and read another line..
            result=ModemGetLine();
          }
          if (result==IN_GOTLINE)
          {
            if (stricmp("OK",N_ND->CurrentLine)==0) Done=TRUE;
          }
        }
      } while (!Done && retriesleft>0);
    }
  }
  SerReset();
}

V_BOOL SendModemString(char *str)
{
  ULONG err=1;
  ULONG loop,loop2=N_ND->NodeDevice.CommandRetries;
  BOOL linemismatch;

  do
  {

    // this loop sends the command to the modem and wait for the characters to
    // be echoed back.  if we don't get back what we send we try it again!

    loop=N_ND->NodeDevice.CommandRetries;
    do
    {
      Delay(N_ND->NodeDevice.DelayBetweenCmds);
      SerReset(); //clear all crap before we send our new string..
      SerWriteStr(str);
      SerWriteStr(str_CRLF);
      ModemGetLine();
      if (linemismatch=(stricmp(str,N_ND->CurrentLine)!=0 ? TRUE : FALSE))
      {
        TurnEchoOn();
      }
    } while (loop-- && linemismatch);

    // right if we are here then we know we're gonna get a blank line and a timeout
    // or OK OR ERROR..  if we don't get OK then we retry the command...
    ModemGetLine();

  } while (loop2-- && stricmp(N_ND->CurrentLine,"OK")!=0 && (err=stricmp(N_ND->CurrentLine,"ERROR"))==0);
  if (!err) ModemError(str,N_ND->CurrentLine);
  return(err);
}

void OffHook( void )
{
  SendModemString(N_ND->NodeDevice.OffHookString);
}

void ReOpenSerial( void )
{
  V_SMALLNUM loop=N_ND->NodeDevice.ReOpenRetries;
  if (!N_ND->NodeDevice.SysopNode)
  {
    CleanupSerial();

    while (loop-- && N_ND->SerOK==FALSE)
    {
      Delay(N_ND->NodeDevice.ReOpenDelay);
      OpenSerial();
    }

    if (!N_ND->SerOK)
    {
      HBBS_LogError(BBSGlobal->ErrorLogFile,ERR_GENERAL,ESTR_NOSERIAL,TYPE_CRITICAL);
    }
  }
}

void HangUp( void )
{
  struct Node *node;

  if (!N_ND->NodeDevice.NullModemCable)
  {
    if (!N_ND->NodeDevice.DropDTRHangup)
    {
      for (node=N_ND->NodeDevice.CommandModeString->lh_Head;node->ln_Succ;node=node->ln_Succ)
      {
        if (node->ln_Name) SerWriteStr(node->ln_Name);
      }
      SendModemString(N_ND->NodeDevice.HangUpString);
    }
    ReOpenSerial();
    OffHook();
  }
  else
  {
    ReOpenSerial();
  }
}

V_BOOL InitModem( void )
{
  struct Node *node;

  if (N_ND->NodeDevice.ModemDebug && N_ND->ConOK)
  {
    ConWriteStr("Initialising Modem...\n\r");
  }

  TurnEchoOn();
  for (node=N_ND->NodeDevice.ModemInit->lh_Head;node->ln_Succ;node=node->ln_Succ)
  {
    if (node->ln_Name) SendModemString(node->ln_Name);
  }
  return(TRUE);
}

