/*
    $Header: Welmat:src/Welmat/xprfts/RCS/sendX.c,v 1.2 92/11/24 23:40:44 rwm Exp Locker: rwm $

    send a file with TeLink/Xmodem.

    Copyright (C) 1988,1989,1990 Michael Richardson
    Copyright (C) 1992 Russell McOrmond

    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.

 */

#ifdef RCSID
static char RCSid[]="$Id: sendX.c,v 1.2 92/11/24 23:40:44 rwm Exp Locker: rwm $";
#endif

#include <proto/all.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "xproto.h"
#include "xmodem.h"
#include "xprfts.h"


long sendXFiles(struct Vars *v)
{
  long stateinfo,lasterr=OK,state=GOTACK,count=10,mdm7count=10;
  char filename[256],temp[2],temp1[51];
  
  /* send a TSYNCH to start off the transfer */
  temp[0]=TSYNCH;
  xpr_swrite(&v->io,temp,1);
  updmsg(v,"TSYNCH!");
  while(state==GOTACK) {
    state=xpr_sread(&v->io,temp,1,4000000);

    /* 1 char, 0 timeout, or -1 carrier drop */
    if(state==0 && count--) {
      temp[0]=TSYNCH;
      xpr_swrite(&v->io,temp,1);
      updmsg(v,"TSYNCH!");
      state=GOTACK;
    }
    if(state==1)
      if((temp[0]&0xff)==NAK) state=GOTNAK;
      else if ((temp[0]&0xff)==CNAK) state=GOTCNAK;
      else state=GOTACK;
  }
  if(!state) { /* no characters came back */
    updmsg(v,"Other end didn't start transfer");
    return(ERROR);
  }
  else if(state==-1) { /* dropped carrier */
    updmsg(v,"Dropped Carrier");
    return(NODCD);
  }

  /* clear the filename to receive the new file */
  filename[0]='\0';

  stateinfo=xpr_ffirst(&v->io,filename,"");
  while(stateinfo) {
    if (v->option_7=='N' || 
       (!(v->outfiles) && (v->option_m=='Y')) || /* mail bundle */
       (v->option_c=='Y' && (state==GOTCNAK))) { /* allow mdm7 skipping */
      lasterr=sendXFile(v,filename);
    } else {
      lasterr=sendmdm7(v,filename);
    }

    if(lasterr==MDM7BAD && (mdm7count--));
    else if(lasterr) {
      updstatus(v,filename,XPRS_FAILURE);
      stateinfo=NULL;
    }
    else {
      updstatus(v,filename,XPRS_SUCCESS);
      v->outfiles++;
      filename[0]='\0';
      lasterr=stateinfo=xpr_fnext(&v->io,stateinfo,filename,"");
    }

    /* wait 1 second and clear buffer */
    (void)xpr_sread(&v->io,temp1,50,1000000);

    /* wait for poll for more files... */
    state=GOTACK;
    while(state==GOTACK) {
      state=xpr_sread(&v->io,temp,1,10000000);

      /* 1 char, 0 timeout, or -1 carrier drop */
      if(state==1)
        if((temp[0]&0xff)==NAK) state=GOTNAK;
        else if ((temp[0]&0xff)==CNAK) state=GOTCNAK;
        else state=GOTACK;
    }
    if(!state) { /* no characters came back */
      updmsg(v,"Other end didn't want more files");
      return(ERROR);
    }
    else if(state==-1) { /* dropped carrier */
      updmsg(v,"Dropped Carrier");
      return(NODCD);
    }
  }

  if (lasterr==OK) {
    count=50;
    state=0;
    while(--count>0 && (state!=-1) &&
       ((state==0) || (((temp[0]&0xff)!=ENQ) && ((temp[0]&0xff)!=TSYNCH) &&
           ((temp[0]&0xff)!=ACK)))) {

      if((state==0) || ((temp[0]&0xff)==NAK) || ((temp[0]&0xff)==CNAK)) {
        temp1[0]=EOT;
        xpr_swrite(&v->io,temp1,1);
        updmsg(v,"EOTing!");
        count=count-4; /* timeout/NAK's cause quicker timeout than line noise */ 
      }
      state=xpr_sread(&v->io,temp,1,2000000);
      SPrintF(v->scratch,"InEOT:state = %ld, count = %ld char: %ld",state,
                         count,(temp[0]&0xff));
      updmsg(v,v->scratch);
    }
    SPrintF(v->scratch,"AfterEOT:state = %ld, count = %ld char: %ld",state,
                       count,(temp[0]&0xff));
    updmsg(v,v->scratch);

    if(state==NODCD) {
      lasterr=NODCD;
    }
    else if(state==0);  /* timeout */
    else if((temp[0]&0xff)==ENQ) {
      /* Bark File requests */
      temp1[0]=ETB;
      xpr_swrite(&v->io,temp1,1);
    }
    else if((temp[0]&0xff)==TSYNCH);
    else if((temp[0]&0xff)==ACK);
    else 
      lasterr=ERROR;
  }
  return(lasterr);
}

void makeTelinkHeader(register unsigned char *buf,
                      char *filename,long size,long time, char mode)
{
  /*mode  0=Telink, 1=Sealink, 2=Sealink(LargeNames) */
 
  memset(buf,0,128);
/*      3   3 |     File Length, least significant byte       |  0    0
              +-----------------------------------------------+
        4   4 | File Length, second to least significant byte |  1    1
              +-----------------------------------------------+
        5   5 |  File Length, second to most significant byte |  2    2
              +-----------------------------------------------+
        6   6 |      File Length, most significant byte       |  3    3*/
  { register char *s;
    s=(char *)&size;
    *buf++=s[3];
    *buf++=s[2];
    *buf++=s[1];
    *buf++=s[0];
  }
/*            +-----------------------------------------------+
        7   7 |            Creation Time of File              |  4    4
              |                "DOS Format"                   |
              +-----------------------------------------------+
        9   9 |            Creation Date of File              |  6    6
              |                "DOS Format"                   |
              +-----------------------------------------------+*/

  /* The above is BOGUS. We seem to be doing a SeaLink block, so
     we have to do Unix timestamps... */

   if(mode==0) {
     register struct tm *now;
     register unsigned short temp;

     now=localtime(&time);
     temp=(now->tm_hour<<11)|(now->tm_min<<5)|(now->tm_sec/2);
     *buf++=temp&0xff;
     *buf++=temp>>8;
     temp=((now->tm_year-80)<<9)|(now->tm_mon<<5)|(now->tm_mday);
     *buf++=temp&0xff;
     *buf++=temp>>8;
   }
   else {
     register char *s;
     /* AmigaDOS Times are from 1978, Unix times from 1970. */
     time=time+(60*60*24*(365*8+2));

     s=(char *)&time;
     *buf++=s[3];
     *buf++=s[2];
     *buf++=s[1];
     *buf++=s[0];
   }


/*     11   B |                 File  Name                    |  8    8
              ~                  16 chars                     ~
              |        left justified  blank filled           |
              +-----------------------------------------------+
       27  1B |                    00H                        | 24   18
              +-----------------------------------------------+
       28  1C |            Sending Program Name               | 25   19
              ~                  16 chars                     ~
              |         left justified  Null filled           |
              +-----------------------------------------------+
*/

   { register int i;

     /* copy filename*/
     for(i=0;*filename!='\0' && i<((mode=2) ? 32 : 16);*buf++=*filename++) i++;

     /* blank fill */
     if (i<16) {
       for(;i<16;i++) *++buf=' ';
       *buf++=0;
       strncpy(buf,"Welmat",15);
       buf=buf+15;
     } else {
       for(;i<32;i++) *++buf=' ';
     }
   }

   if(mode==0) 
      buf[44]=1;  /* If we're doing telink, say that we're doing CRC! */
                  /* otherwise leave it as a 0 to indicate no compatability
                     with SLO */
/*
              +-----------------------------------------------+
       44  2B |            01H (for CRC) or 00H               | 41   29
              +-----------------------------------------------+
       45  2C |                    fill                       | 42   2A
              ~                  86 bytes                     ~
              |                  all zero                     |
              +-----------------------------------------------+
*/
}

void sendXPacket(struct Vars *v,BYTE ckmode,block *lastPacket,int blocknum)
{
  char *buf;
  int i;

  lastPacket->bl_blocknum=(blocknum&0xff);
  lastPacket->bl_blockcom=(~(blocknum&0xff));

  if(!ckmode) {
    int ck;

    lastPacket->bl_start=SYN;
    buf=lastPacket->bl_contents;
    ck=0;
    for(i=0; i<128; i++) {
      ck=ck+buf[i];
    }
    lastPacket->bl_check.cksum=(ck&0xff);
    xpr_swrite(&v->io,&lastPacket->bl_start,132);
  } else  {
    lastPacket->bl_start=SOH;
    lastPacket->bl_check.crc=
        compute_crc(lastPacket->bl_contents,128);
    xpr_swrite(&v->io,&lastPacket->bl_start,133);
  }
  xpr_sflush(&v->io);
  SPrintF(v->scratch,"Sending block #%ld %s -",blocknum,
                   (!ckmode ? "Check" : "CRC") );
  updmsg(v,v->scratch); /* sending block # */
}

long sendXFile(struct Vars *v,char *filename)
{
  struct WindowX myWindow;
  block *lastPacket;
  int   badCount,retry;
  void *workFILE; /* XPR handle on file */
  char  ckmode;         /* a 0 for checksum, 1 for CRC */
  int   state;
  short eof=0;
  int   blocknum;
  short count;

  if((workFILE=(void *)xpr_fopen(&v->io,filename,"r"))==NULL) {
    SPrintF(v->scratch, "Couldn't open file %s",filename);
    updmsg(v,v->scratch);
    return(ERROR);
  }
  else { /* We could open the file */
    
    (void) AllocWindowX(&myWindow);  /* TODO: Do checks for not allocated */

/*
 | XS0 | WaitTeLnk| 1 over 40-60 seconds    | report sender timeout   | exit|
 |     |          | 2 over 2 tries          | note TeLink block failed| XS1 |
 |     |          | 3 NAK or "C" received   | send TeLink, incr tries | XS0 |
 |     |          | 4 ACK received          | TeLink ok, set crc/cksm | XS2 |
  really like this:
  XS0a              1 timeout (40-60 sec)                               exit
                    2 NAK or "C"            | send TeLink block         XS0b

  XS0b              1 timeout (40-60 sec)                               exit
                    2 NAK or "C"            | send TeLink block         XS0c
                    3 ACK                   | TeLink ok, set crc/cksm   XS2
  XS0c              1 timeout (40-6- sec)                               exit
                    2 NAK or "C"            | note TeLink block failed  fail?
                    3 ACK                   | TeLink ok                 XS2 */


    if((lastPacket=(block *)RemHead(&myWindow.wx_freeblocks))==NULL) {
           updmsg(v,"Fully Tilted Head");
           FreeWindowX(&myWindow);
           xpr_fclose(&v->io,workFILE);
           return(ERROR);
    }

    retry=9;
    state=GOTNAK;
    if (!(v->outfiles) || (v->option_f=='Y')) {
      while((state!=GOTACK) && (retry--)) {
        char temp[2];
        ckmode=((v->option_o=='T') ? 0 :
                (retry<5 && (v->option_o!='S')) ? 0 : 
                             (v->option_b=='Y') ? 2 : 1);

        makeTelinkHeader(lastPacket->bl_contents,filename,
                       xpr_finfo(&v->io,filename,1),
                       0,
                       ckmode);
        sendXPacket(v,ckmode,lastPacket,0);
        state=xpr_sread(&v->io,temp,1,5000000);
        if(state==NODCD) {
          FreeWindowX(&myWindow);
          xpr_fclose(&v->io,workFILE);
          return(NODCD);
        }
        if(state && (temp[0]==ACK)) state=GOTACK;
      }
      SPrintF(v->scratch,"retry %ld ckmode %ld  Filename:%s",retry,ckmode,
                           filename);
      updmsg(v,v->scratch);
    }
    if(state!=GOTACK) { /* Header Block Failed */
          updmsg(v,"Header block failed!");
          if(!(v->outfiles) && (v->option_m=='Y')) {
            /* It's ok, it is the mail bundle */
            goto send_file;
          }
          else if(v->option_7=='Y') {
            goto send_file;    /* We relayed the file name another way. */
          }
          else {               /* Too, bad. I don't do UNKNOWN.$$$ */
            FreeWindowX(&myWindow);
            xpr_fclose(&v->io,workFILE);
            return(ERROR);
          }
    }
    /* header was successfull! */
    SPrintF(v->scratch,"%s header block sent!",
             (ckmode ? "SeaLink" : "Telink"));
    updmsg(v,v->scratch);

   send_file:
    blocknum=1;
    badCount=0;
    xpr_sflush(&v->io);

    while(!eof) {
      { short i;

        if((i=xpr_fread(&v->io,lastPacket->bl_contents, 1,128,workFILE))!=128) {
          for(;i<128; i++) {
            lastPacket->bl_contents[i]=SUB;
          }
          eof=1;
        }
      }
/*
 | XS2 | SendBlock| 1 more data available   | send next data block    | XS3 |
 |     |          |                         |   as checksum or crc    |     |
 |     |          | 2 last block has gone   | send EOT                | XS4 |
 | XS3 | WaitACK  | 1 10 retries or 1 minute| report send failed      | exit|
 |     |          | 2 ACK received          |                         | XS2 |
 |     |          | 3 NAK (or C if 1st blk) | resend last block       | XS3 |
 | XS4 | WaitEnd  | 1 10 retries or 1 minute| report send failed      | exit|
 |     |          | 2 ACK received          | report send successful  | exit|
 |     |          | 3 NAK received          | resend EOT              | XS4 |
 */

      state=GOTNAK;
      count=0;
      while(state==GOTNAK) {
        char temp[2];

        xpr_sflush(&v->io); /* temporary for non-sealink sends */
        sendXPacket(v,1,lastPacket,blocknum);
        state=0;
        while (state>-1) {
          state=xpr_sread(&v->io,temp,1,4000000);
          if(state==-1) {
            updmsg(v,"Lost carrier!");
            xpr_fclose(&v->io,workFILE);
            FreeWindowX(&myWindow);
            return(NODCD);
          }
          if(state==1)
            if((temp[0]&0xff)==ACK) {
              state=GOTACK;
              updmsg(v,"R:ACK");
            } else if ((temp[0]&0xff)==NAK) {
              updmsg(v,"R:NAK");
              badCount++;
              state=GOTNAK;
	   } else {
              SPrintF(v->scratch,"R:Junk(%ld)\n",temp[0]&0xff);
              updmsg(v,v->scratch);
	   }
	  else {
            state=GOTNAK;
            updmsg(v,"TIME");
            badCount++;
          }
          if((state==GOTNAK) && count++>10) {
            updmsg(v,"Too many retries. Line bad.");
            xpr_fclose(&v->io,workFILE);
            FreeWindowX(&myWindow);
            return(ERROR);
          }
	}
      }
      blocknum++;
    }
    xpr_fclose(&v->io,workFILE);
    count=0;
    state=TIMEOUT;
    while((state==GOTNAK) || (state==TIMEOUT)) {
      char temp[2];

      if(state==TIMEOUT) {
        temp[0]=EOT;
        xpr_swrite(&v->io,temp,1);
        updmsg(v,"EOF/EOT");
      }
      state=xpr_sread(&v->io,temp,1,2000000);
      SPrintF(v->scratch,"state:%ld, c:%ld",state,temp[0]&0xff);
      updmsg(v,v->scratch);

      if(state==-1) return(NODCD);
      if(state && ((temp[0]&0xff)==ACK)) state=GOTACK;
      else if(state) state=GOTNAK;
      else {
        state=TIMEOUT;
        if(count++>9) {
          updmsg(v,"Too many retries. Can't end transfer.");
          FreeWindowX(&myWindow);
          return(ERROR);
        }
      }
    }
  end:
    FreeWindowX(&myWindow);
    return(OK);
  }
}

long sendmdm7(struct Vars *v,char *filename)
{
  long i,state;
  UBYTE mdm7name[16],*tempf,cksum,c,temp[2],temp1[2];

  /* get basename */
  for(tempf=filename+strlen(filename);
      tempf!=filename && *(tempf-1)!='/' && *(tempf-1)!=':';
      tempf--);

#ifdef KDEBUG
  KPrintF("Filename: '%s' BaseName '%s'\n",filename,tempf);
#endif

  for(i=0; i<11; i++) mdm7name[i]=' ';
  mdm7name[11]='\0';
  for(i=0; i<11 && *tempf!='\0'; i++) {
    if(*tempf=='.') {
      i=7;
    }
    else {
      mdm7name[i]=toupper(*tempf);
    }
    tempf++;
  }
  cksum=0;
  for(i=0; i<11; i++ ) {
    cksum+=mdm7name[i];
  }
  cksum+=SUB;
  cksum=cksum&0xff;
  mdm7name[11]='\0';


/*
 |-----+----------+-------------------------+-------------------------+-----|
 | MS0 | WaitNak  | 1 20 retries or 1 minute| filename send failed    | exit|
 |     |          | 2 NAK received          | send ACK & 1st ch of fn | MS1 |
 |-----+----------+-------------------------+-------------------------+-----|
 | MS1 | WaitChAck| 1 ACK rcd, fname done   | send SUB = 1AH          | MS2 |
 |     |          | 2 ACK rcd, fname ~done  | send next ch of fname   | MS1 |
 |     |          | 3 other char or 1 sec   | send "u", incr retry cnt| MS0 |
 |-----+----------+-------------------------+-------------------------+-----|
*/

  xpr_sflush(&v->io);
  temp1[0]=ACK;
  xpr_swrite(&v->io,temp1,1);

  updmsg(v,"MDM7 filename send");

  for(i=0; i<11; i++) {
    xpr_swrite(&v->io,&mdm7name[i],1);
    state=xpr_sread(&v->io,temp,1,5000000);
    if(state==1 && (temp[0]&0xff==CAN)) {
      temp1[0]=ACK;
      xpr_swrite(&v->io,temp1,1);
      return(GOTEOT);
    }
    else if(state!=1 || ((temp[0]&0xff) != ACK))
      break;
  }

#ifdef KDEBUG
  KPrintF("Sread1: %ld c=%ld\n",state,temp[0]&0xff);
#endif

  if((state==1) && ((temp[0]&0xff)==ACK)) {
/*
 | MS2 | WaitCksm | 1 cksum recd and ok     | send ACK, report fn ok  | exit|
 |     |          | 2 cksum recd but bad    | send "u", incr retry cnt| MS0 |
 |     |          | 3 no cksum in 1 sec     | send "u", incr retry cnt| MS0 |
 */
    temp1[0]=SUB;
    xpr_swrite(&v->io,temp1,1);
    state=xpr_sread(&v->io,temp,1,3000000);
#ifdef KDEBUG
  KPrintF("Sread2: %ld c=%ld\n",state,temp[0]&0xff);
#endif
    if(state==1)
      if((c=temp[0]&0xff)==cksum) {
        temp1[0]=ACK;
        xpr_swrite(&v->io,temp1,1);
        return(sendXFile(v,filename));
      } else {
        SPrintF(v->scratch,"Modem7 checksum failed: %ld vs %ld",c,cksum);
        updmsg(v,v->scratch);
      }
    else
     updmsg(v,"Time/DCD");
  }
  temp1[0]='u';
  xpr_swrite(&v->io,temp1,1);
  updmsg(v,"Modem7 Filename send failed");
  return(MDM7BAD);
}
