#include <exec/types.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <exec/memory.h>
#include "defines.h"

extern int Online_Baud;

static char  sbuff[133];        /* sprintf work buffer */
static char  buffer[BufSize+1024];   /* ASCII capture buffer */
static char  diskbuff[BufSize+1024];
static int   numbufs=NUMBUFS;        /* number of disk buffers allocated */

static int fd,           /* XMODEM file being sent/received */
    sector,              /* sector being sent/received */
    baud,                /* current baud rate */
    cancel  = FALSE,     /* TRUE if CAN received and abort_flag is TRUE */
    timeout = FALSE,     /* TRUE if timeout during character receive */
    chopflg = TRUE,      /* TRUE if we CHOP files */
    echoflg = FALSE;     /* TRUE to echo received characters to sender */

static short crcflag = FALSE;   /* TRUE if crc mode, FALSE for checksum */
                         /* can be changed in send mode by remote receiver */
static short xfrmode = FALSE;   /* same as crcflag, except only under user control */
static unsigned short crc;      /* 16 bit crc value */
static unsigned char checksum;
static int viewflg = FALSE;     /* TRUE if XMODEM transfers are viewed on screen */
static int asciiflg = FALSE;    /* TRUE to add CR/LF pairs to XMODEM send */

  /********************************************************/
 /* SEND CHAR AND READ CHAR GLUE FOR THE XMODEM FUNCTION */
/********************************************************/
static int sendchar(ch)
int ch;
{
SerPutChar((char)ch);
}

/*************************************
readchar() waits for a character
  timeout built in.
  Timeout condition causes error return
  'seconds' controls duration of wait.
  'doabort' TRUE indicates that CAN recvd
     will set cancel flag
**************************************/
static int readchar( seconds, doabort )
	unsigned int seconds, doabort;
{
int ch;

ch=readser((long)seconds);
if(ch==TIMEOUT) { timeout=1; } else { timeout=0; }
if(ch==CAN && doabort) { cancel=TRUE; } else { cancel=FALSE; }
return(ch);
}

/***********************************
  Flush receive buffer.
  Removes pending rx characters to
  help maintain line syncronization
************************************/  
static int purge_line()
{
PurgeLine();
}

/**********************************/
static int Xon(val)
int val;
{
/* this GLUE does nada, zilch, goose-egg, nothin pal */
;
}

/***********************************
  Start line.
  Restart line if stopped with XOFF
************************************/  
static int start_line()
{
/* GLUE does nothing */
;
}

/***************************************************/
/* see if any characters are in the receive buffer */
/* return FALSE if IO pending, TRUE if not */

static int check_line()
{
int stat;

stat=CheckSer();
return(stat);
}

   /******************************/
  /* END OF THE GLUE FUNCTIONS  */
 /* START OF SUPPORT FUNCTIONS */
/******************************/

/********************************
 do_crc() computes CRC using the
 CCITT polynomial.
********************************/

static int do_crc( val )
unsigned char val;
{
   unsigned short shifter,flag;

   if(crcflag)
     for( shifter = 0x80 ; shifter ; shifter >>= 1 )
       {
         flag = (crc & 0x8000);
         crc <<= 1;
         crc |= ((shifter & val) ? 1 : 0);
         if( flag )
            crc ^= 0x1021;
       }
   else
     checksum += val;
}

/********************************/
static int check_abort()
{
if(GetConKey(0)==0x1b) { return(TRUE); }
else { return(FALSE); }
}

static int   index, state;

/****************************/
/* XMODEM RECIEVE FUNCTIONS */
/****************************/

int Xmodem_Receive(file)
	char *file;
{
   UBYTE  currsect, compsect, response;
   USHORT abort, dirty, syncerr;
   ULONG  bytes;
   int ch, i, noresponse, errsect, naks;

StatRollMessage("Xmodem Receive");

   errsect = timeout = naks = syncerr = noresponse = index = bytes = 0;
   abort = dirty = FALSE;
   crcflag = TRUE; /* xfermode; will set mode from outside */

   if((jive(file,"*.arc"))==SUCCESS) { chopflg=0; }
   else { chopflg=1; }

   fd=open(file,O_CREAT+O_WRONLY+O_EXCL);
   if(fd == (-1))
     {
       StatPrintSpecial("File Create Error");
       sendchar(CAN);
       sendchar(CAN);
       sendchar(CAN);
       PutStr("Xmodem: File Create Error, Transfer Canceled\r\n");
       return(FAILURE);
     }
   else
   {
     PutStr("Xmodem: Ready to receive\r\n");
     PutStr("NB: Control-X to cancel\r\n");
   }

   sector = 1;
   response = (crcflag) ? 'C' : NAK;
   state = STARTUP;

   sendchar(response);

   while( TRUE )
   {
     ch = readchar(TTIME,((state==STARTBLK)?TRUE:FALSE));
     if(timeout) { ++noresponse; }
     abort=check_abort();

     switch(state)
     {
       case STARTUP:
            StatPrintSpecial("State Startup");
            if(abort || cancel){
	        close(fd);
		unlink(file);
	        sendchar(CAN);
		sendchar(CAN);
		sendchar(CAN);
		StatRollMessage("Canceled at Startup");
		StatPrintSpecial("");
		PutStr("Xmodem: Transfer Canceled\r\n");
	        return(FAILURE);
	    }
            if(timeout || ch==TIMEOUT)
            {
	      StatPrintSpecial("Startup Timeout");
              if(noresponse == RETRYMAX/2){
                crcflag = (crcflag == FALSE);
                response = crcflag ? 'C' : NAK;
              }
              if(noresponse == RETRYMAX) { break; }

              sendchar(response);
	      timeout=0;
              continue;
            }  /* fall thru to STARTBLK state, no break; at end of case */

       case STARTBLK:
            switch( ch )
            {
              case SOH:
	          sprintf(sbuff,"Block %d",sector);
        	  StatPrintSpecial(sbuff);
                  state = BLKNUM;
                  noresponse = 0;
                  response = NAK;
                  if(dirty)
                  {
                     movmem(buffer,&diskbuff[index++ * SECSIZ],SECSIZ);
                     dirty = FALSE;
                  }
                  break;
              case CAN:
                  if(syncerr) continue;
                  sendchar(EOT);
		  close(fd);
		  unlink(fd);
		  PutStr("Xmodem: Transfer Canceled\r\n");
		  StatPrintSpecial("");
                  return(FAILURE);
              case EOT:
                  if(syncerr) continue;
                  chop_file();
                  close( fd );
                  sendchar( ACK );
		  PutStr("Successful Transfer\r\n");
		  StatPrintSpecial("");
		  report(file);
                  return(SUCCESS);
              case TIMEOUT:
                  sendchar(response);
                  break;
              default: /* means Out Of Sync condition */
                  continue;
            }   /* switch( ch ) */
            break;

       case BLKNUM:
            if(ch == TIMEOUT) break;
            else noresponse = 0;
            currsect = ch;
            state = COMPBLK;
            if(syncerr)
               if((currsect != sector && 0xFF)
                & (currsect != (sector -1) && 0xFF))
                  state = STARTBLK;
            continue;

       case COMPBLK:
            if(ch == TIMEOUT) break;
            else noresponse = 0;
            compsect = ch;
            i = checksum = crc = 0;
            state = DATA;
            if(syncerr)
               if(currsect == ~compsect)
                  syncerr = 0;
               else state = STARTBLK;
            continue;

       case DATA:
            if(ch==TIMEOUT) break;
            else noresponse = 0;
            buffer[ i++ ] = ch;
            do_crc( ch );
            if( i == SECSIZ)
               state = CHKSUM;
            continue;

       case CHKSUM:
            if(ch == TIMEOUT) break;
            else noresponse = 0;
            if(crcflag)
            {
              state = CHKSUM1;
              do_crc( ch );
              continue;
            }  /* fall thru to CHKSUM1 state */

       case CHKSUM1:
            if(ch == TIMEOUT) break;
            else noresponse = 0;
            state = STARTBLK;
            response = NAK;
            if(abort || cancel)
            {
               sendchar(CAN);
               sendchar(CAN);
               sendchar(CAN);
	       close(fd);
	       unlink(file);
	       StatPrintSpecial("");
	       PutStr("Transfer Canceled\r\n");
               return(FAILURE);
            }
            if(!verify_checksum( ch ))
            {
              sendchar( NAK );
              errsect = sector; naks++;
              continue;
            }
            if(currsect != ~compsect)
            {
              sendchar( NAK );
              errsect = sector; naks++;
              continue;
            }
            if(currsect == (sector -1) & 0xFF)
            {
               sendchar( ACK ); response = ACK;
               continue;
            }
            if(currsect != (sector & 0xFF))
            {
              sendchar( NAK );
              errsect = sector; naks++;
              continue;
            }

            bytes += SECSIZ;
            dirty = TRUE;
            sector++;
            if(index == numbufs)
            {
               if(write(fd,diskbuff,numbufs * SECSIZ) != numbufs * SECSIZ)
               {
                 sendchar( CAN );
		 close(fd);
		 unlink(file);
                 StatPrintSpecial("");
		 PutStr("File Write Error, Transfer Canceled\r\n");
                 return(FAILURE);
               }
               index = 0;
            }
            if(check_line() == FALSE)
               sendchar( ACK );
            response = ACK;
            continue;

     default:
            state = STARTBLK;
            continue;
     } /* end switch */

     if( noresponse == RETRYMAX)
     {
       sendchar( CAN ); sendchar( CAN ); sendchar( CAN );
       close(fd);
       unlink(file);
       StatPrintSpecial("");
       PutStr("Xmodem: Timeout Exceeded, Transfer Canceled\r\n");
       return(FAILURE);
     }
     if(abort || cancel)
     {
        sendchar( CAN ); sendchar ( CAN ); sendchar ( CAN );
	close(fd);
	unlink(file);
	StatPrintSpecial("");
	PutStr("Xmodem: Transfer Canceled\r\n");
        return(FAILURE);
      }
   } /* end while(TRUE) */

}

/*********************************/
static int verify_checksum( byte )
UBYTE byte;
{
   if(crcflag)
      {
         do_crc( byte );
         return (int)~crc;
      }
   else
      return ( checksum == byte );
}


/*******************************
 remove pad characters from the
 end of the last sector of the
 file.
*******************************/

static int chop_file()
{
   int count,i;
   UBYTE c;

/*
   strip control Z  -- CP/M and Aterm 1.4 use this for PAD character.
   Don't remove NULLs.  Some programs pad with NULLs.  This practice
   should stop, because some icon.INFO files require a NULL as the last
   character in the file to terminate an ASCII string.
*/
  if(chopflg)
   {
     for(i = SECSIZ-1; i >= 0; i--)
       {
         c = buffer[ i ];
         if( c == PAD )
            continue;
         break;
       }
   }
  else i = SECSIZ-1;    /* not chopping, write last sector intact */

  count = 0;
  if(index)
     count = write(fd,diskbuff,index * SECSIZ);

  count += write(fd,buffer,++i);
  if ( count != index * SECSIZ + i)
      ; /* error report which gets ignored */
}

/**************************************************/
int Xmodem_Send(file)
char *file;
{
    short readstatus, status, timeoutcount;
    int ch;

    cancel = FALSE;
    report(file);
    if ((fd = open(file, O_RDONLY)) < 0)
        {
        sendchar(EOT);
	PutStr("Xmodem: File Access Error, Transfer Canceled\r\n");
        return(FAILURE);
        }
    else {
        PutStr("Xmodem: Ready to Send\r\n");
        PutStr("NB: Control-X to Cancel\r\n");
    }

   Xon(FALSE);
   sector = 1;  timeoutcount = 0;

   purge_line();     /* flush any garbage in receiver */
   do                /* start handshake with receiver */
      {
         ch = readchar(TTIME,TRUE);
         if(ch==TIMEOUT)
           {
	     StatPrintSpecial("Handshake Timeout");
             if(++timeoutcount == RETRYMAX)
               {
		  close(fd);
		  sendchar(CAN); sendchar(CAN); sendchar(CAN);
		  PutStr("Xmodem: Timeout\r\n");
		  StatPrintSpecial("");
                  return(FAILURE);
               }
            }
         if(check_abort() || cancel ){
	     close(fd);
	     sendchar(CAN); sendchar(CAN); sendchar(CAN);
	     PutStr("Xmodem: Transfer Canceled\r\n");
	     StatPrintSpecial("");
	     return(FAILURE);
	 }
      } while (ch != NAK && ch != 'C');

   status = OK;
   crcflag = (ch == 'C');

   while( (readstatus = fillbuf()) && status == OK )
    {                                  /* fill file buffer until EOF */
      if(readstatus == FAILURE){          /*  or ERROR during read */
         close(fd);
	 sendchar(CAN); sendchar(CAN); sendchar(CAN);
	 PutStr("Xmodem: File Error\r\n");
	 StatPrintSpecial("");
         return(FAILURE);
      }
      status = sendsector( buffer );   /* send sector */
      sprintf(sbuff,"Block %d",sector);
      StatPrintSpecial(sbuff);
    }

   if(status == TIMEOUT || status == ABORT){
      close(fd);
      sendchar(CAN); sendchar(CAN); sendchar(CAN);
      PutStr("Xmodem: Error, Transfer Canceled\r\n");
      StatPrintSpecial("");
      return(FAILURE);
   }

   timeoutcount = 0;
   do
     {
      purge_line();
      sendchar( EOT );     /* end transmission */
      if(timeout)
        if(timeoutcount++ == RETRYMAX || cancel || check_abort()){
	  close(fd);
	  PutStr("Xmodem: Transfer Error\r\n");
	  StatPrintSpecial("");
	  return(FAILURE);
	}
     } while ((ch = readchar( TTIME, TRUE )) != ACK);
   close( fd );
   PutStr("Xmodem: Successful Transfer\r\n");
   StatPrintSpecial("");
   return(SUCCESS);
}

/*****************************/
static int fillbuf()
{
   unsigned int bytes_read;

   bytes_read = read(fd, buffer, SECSIZ);

   if (bytes_read == -1)   /* error during read */
      return(FAILURE);
   if (bytes_read == 0)    /* end of file */
      return(0);
   if(bytes_read < SECSIZ)
      for(; bytes_read < SECSIZ; bytes_read++)
         buffer[bytes_read] = PAD;
   return(TRUE);
}

/**************************************
sendsector() transmits a single sector
 with retransmit on error from receiver
***************************************/
static int sendsector( buf )
unsigned char *buf;
{
   unsigned int i, badblock;

   badblock = 0;
   cancel = FALSE;

   while( TRUE )
   {
      if( badblock == ERRORMAX )
         return(TIMEOUT);
      if( cancel || check_abort() )
         return(ABORT);

      purge_line();             /* flush any garbage from line ??? */
      sendchar( SOH );          /* start of block */
      sendchar( sector );       /* sector number  */
      sendchar( ~sector );      /* compliment of sector number */
      checksum = crc = 0;       /* clear CRC and checksum */

/* PLACE TO ADD SERPUTBLK() AND SPEED IT UP */
      for( i = 0 ; i < SECSIZ ; i++ )
        {
          if(buf[ i ] == '\n')
            if(asciiflg) buf[ i ] = 0x0D;
          sendchar(buf[ i ]);
          do_crc( buf[ i ] );
        }
      purge_line();              /* flush garbage from receiver */
      if( crcflag )
      {
         do_crc(0); do_crc(0);   /* cycle CRC generator */
         sendchar( crc >> 8 );   /* High byte */
         sendchar( crc );        /* Low byte */
      }
      else
         sendchar( checksum );

      if( readchar(TTIME, TRUE ) == ACK)
         break;      
      else
         badblock++;
   }
   sector++;            /* bump sector count   */
   return(OK);
}

/*************************/
int report(file)
	char *file;
{
   extern void *AllocMem();
   extern struct FileLock *Lock();
   long   size, sectors, seconds;
   struct FileInfoBlock *FBlock;
   struct FileLock      *FLock;

   if((FLock = Lock(file,ACCESS_READ)) == NULL)
      return(FAILURE);

   if((FBlock=AllocMem((long)sizeof(struct FileInfoBlock),MEMF_CHIP)) != NULL)
     if( Examine(FLock,FBlock) )
        {
           size = FBlock->fib_Size;
           if(size != 0)
             {
               sectors = size / SECSIZ +1L;
               sprintf(sbuff,"File: %ld bytes, %ld blocks\r\n",
                  size, sectors);
               PutStr(sbuff);
               seconds = sectors * 133L * 11L / (long)Online_Baud;
       sprintf(sbuff,"Time: %ld minutes %ld seconds for transfer at %d bps\r\n",
                  seconds / 60L, seconds % 60L, Online_Baud);
               PutStr(sbuff);
             }
           FreeMem(FBlock, (long)sizeof(struct FileInfoBlock));
        }
   UnLock(FLock);
   return(SUCCESS);
}
