/*  Send.c: File transmission routines for xprzmodem.library;
    Version 2.10, 12 February 1991, by Rick Huebner.
    Based closely on Chuck Forsberg's sz.c example ZModem code,
    but too pervasively modified to even think of detailing the changes.
    Released to the Public Domain; do as you like with this code.  */


#include <proto/all.h>
#include <exec/types.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "xproto.h"
#include "zmodem.h"
#include "xprzmodem.h"

#ifdef DEBUGLOG
extern void *DebugLog;
#endif

int FileType(char*);

/* Main file transmission routine; called by comm program */
long  __saveds XProtocolSend(struct XPR_IO *io) {
  struct Vars *v;
  short err;

  /* Perform common setup and initializations */
  if (!(v = setup(io)))
    return XPRS_FAILURE;

  v->TransferWindow = NULL;
  OpenDisplay(v);

  v->Rxtimeout = 600;
  v->Wantfcs32 = TRUE;
  v->Rxflags = 0;

  if (v->io.xpr_sflush) xpr_sflush(&v->io);

  /* Transfer the files */
  zmputs(v,"rz\r");
  stohdr(v,0L);
  zshhdr(v,ZRQINIT);
  sendbuf(v);
  if (getzrxinit(v) == ERROR)
    upderr(v,"Upload Cancelled Or Timed Out");
  else{
    sendbatch(v);
    updmsg(v,"Finished Sending"); /* At least we may have been sending */
  }
  /* Clean up and return */
  if (err = v->Errcnt) upderr(v,"One Or More Files Skipped Due To Errors");

  CloseDisplay(v);

  if (v->io.xpr_setserial && v->Oldstatus != -1)
    xpr_setserial(&v->io,v->Oldstatus);
  FreeMem(v->Filebuf,v->Filebufmax);
  FreeMem(v,(long)sizeof(struct Vars));

#ifdef DEBUGLOG
  if (DebugLog) {
    xpr_fclose(&v->io,DebugLog);
    DebugLog = NULL;
  }
#endif

  return (err) ? XPRS_FAILURE : XPRS_SUCCESS;
}



/* Negotiate with receiver to start a file transfer */
short getzrxinit(struct Vars *v) {
  short n;

  for (n=v->ErrorLimit; --n>=0; ) {
    /* Check for abort from comm program */
    if (v->io.xpr_chkabort && xpr_chkabort(&v->io))
      return ERROR;
    switch (zgethdr(v)) {
      case ZCHALLENGE:        /* Echo receiver's challenge number */
        stohdr(v,v->Rxpos);
        zshhdr(v,ZACK);
        sendbuf(v);
        continue;
      case ZCOMMAND:          /* They didn't see our ZRQINIT; try again */
        stohdr(v,0L);
        zshhdr(v,ZRQINIT);
        sendbuf(v);
        continue;
      case ZRINIT:            /* Receiver ready; get transfer parameters */
        v->Rxflags = 0xFF & v->Rxhdr[ZF0];
        v->Txfcs32 = (v->Wantfcs32 && (v->Rxflags & CANFC32));
        v->Rxbuflen = ((USHORT)v->Rxhdr[ZP1]<<8) | v->Rxhdr[ZP0];
#ifdef DEBUGLOG
        sprintf(v->Msgbuf,"Txfcs32=%ld Rxbuflen=%ld Tframlen=%ld\n",(long)v->Txfcs32,(long)v->Rxbuflen,(long)v->Tframlen);
        dlog(v,v->Msgbuf);
#endif
        /* Use shortest of the two side's max frame lengths */
        if (v->Tframlen && (!v->Rxbuflen || v->Tframlen < v->Rxbuflen))
          v->Rxbuflen = v->Tframlen;
#ifdef DEBUGLOG
        sprintf(v->Msgbuf,"Rxbuflen=%ld\n",(long)v->Rxbuflen);
        dlog(v,v->Msgbuf);
#endif
        return OK;
      case ZCAN:
      case RCDO:
      case TIMEOUT:
        upderr(v,v->Msgbuf);
        return ERROR;
      case ZRQINIT:
        if (v->Rxhdr[ZF0] == ZCOMMAND) continue;
        /* fallthrough... */
      default:
        zshhdr(v,ZNAK);
        sendbuf(v);
        continue;
    }
  }
  return ERROR;
}

/* Send a batch of files */
void sendbatch(struct Vars *v) {
   UBYTE single, done = FALSE;
   long fstate;
   /* Lets just clear this in case the host program fails to set this */
   v->Filcnt = 0; /* Up to now we have not sent any files */
   /* If template routines not provided, must be single filename */
   if(!(v->io.xpr_ffirst) || !(v->io.xpr_fnext)) {
      single = TRUE;
      strcpy(v->Filename,(v->io.xpr_filename) ? (v->io.xpr_filename) : "");
      if(!(v->Filename[0])){
         if(v->USEZERO) updmsg(v,"Detected No Files...");
         else           upderr(v,"No Files Are Specified!");
         done = TRUE;
      }
  /* Else use the template routines to get the first filename */
   }
   else{
      single = FALSE;
      fstate = xpr_ffirst(&v->io,v->Filename,v->io.xpr_filename);
      if(!(fstate) || !(v->Filename[0])){ /* There are no files found */
         if(v->USEZERO){ /* that's OK... we allow this kind of thing */
            updmsg(v,"Detected No Files...");
            done = TRUE;
         }
         else{ /* In that case, I'm leaving */
            if((!(v->io.xpr_filename) || !(v->io.xpr_filename[0]))) upderr(v,"No Files Are Specified!");
            else upderr(v,"No Files Match Template");
            return;
         }
      }
   }
  /* If using templates, keep getting names & sending until done */
   while (!done) {
      long ret;
      if ((ret=sendone(v)) == ERROR){
         if(strlen(v->Filename)){
            updstatus(v,v->Filename,XPRS_FAILURE);
         }
         updmsg(v,"Transfer Aborted Due To Error");
         return;
      }
      if(strlen(v->Filename)){
         updstatus(v,v->Filename,(ret==OK) ? XPRS_SUCCESS : XPRS_FAILURE);
      }
      if (single) break;
      fstate = xpr_fnext(&v->io,fstate,v->Filename,v->io.xpr_filename);
      done = !fstate;
   }
  /* End batch and return; if we never got started, just cancel receiver */
   if ((v->Filcnt == NULL)&&(v->USEZERO == TRUE)){
      updmsg(v,"Sent No Files..."); /* We may have wanted to send no files */
      /* Anyway, we had that option enabled and I just assumed that because */
      /* no files could be sent we were not going to be sending any... */
   }
   else{
      if(v->Filcnt == NULL) updmsg(v,"Could Not Send Any Files...");
      /* We did NOT want to send no files!!! */
      /* Remeber, if we even so much as find the file then ++v->Filcnt !*/
   }
   /* If we opened up a file correctly or if we are doing ZedZap... */
   if ((v->Filcnt)||(v->USEZERO == TRUE)) saybibi(v);
   /* Else... well... we do this... (but not during ZedZap/ZedZip) */
   else canit(v);
}



/* Send the file named in v->Filename */
short sendone(struct Vars *v) {
  struct SetupVars *sv;

#ifdef DEBUGLOG
  sprintf(v->Msgbuf,"*** Sending %s\n",v->Filename);
  dlog(v,v->Msgbuf);
#endif

  /* Display name of file being sent for user */
  v->xpru.xpru_updatemask = XPRU_FILENAME;
  v->xpru.xpru_filename = v->Filename;
  UpdateStats(v);
  xpr_update(&v->io,&v->xpru);

  /* Set text/binary mode according to options before opening file */
  set_textmode(v);

  /* Open the file, if possible */
  if (!bfopen(v,"r")) {
    if(v->Filename[0] != NULL){    /* is this even a file? */
       ++v->Errcnt;
       upderr(v,"Can't Open File; Skipping");
       updmsg(v,"File Transfer FAILED");
    }
    return OK;      /* pass over it, there may be others */
  }
  ++(v->Filcnt);
  GetSysTimeXPR(&v->Starttime);

  /* Kick off the file transfer */
  sv = (void *)v->io.xpr_data;
  switch (sendname(v)) {
    case ZSKIP:
       bfclose(v);
       return ZSKIP;
    case OK:
      bfclose(v);
      /* File sent; if option DY, delete file after sending */
      if (*sv->option_d == 'Y' && v->io.xpr_extension >= 2 && v->io.xpr_unlink) {
        updmsg(v,"Deleting File...");
        xpr_unlink(&v->io,v->Filename);
      }
      break;
    case ERROR:
    default:
       bfclose(v);
       ++v->Errcnt;
       return ERROR;
  }
  return OK;
}



/* Build file info block consisting of file name, length, time, and mode */
short sendname(struct Vars *v) {
  struct SetupVars *sv;
  UBYTE *p, *q, buff[32];
  short ptype;

  /* Initialize comm program transfer status display */
  v->Fsize =  (v->io.xpr_finfo) ? xpr_finfo(&v->io,v->Filename,1L) : -1;
  v->xpru.xpru_updatemask = XPRU_PROTOCOL | XPRU_FILESIZE | XPRU_MSG | XPRU_BLOCKS |
                            XPRU_ERRORS | XPRU_TIMEOUTS | XPRU_BLOCKCHECK | XPRU_BYTES |
                            XPRU_EXPECTTIME | XPRU_ELAPSEDTIME | XPRU_DATARATE;
  v->xpru.xpru_protocol = "ZedZap";
  v->xpru.xpru_filesize = v->Fsize;
  v->xpru.xpru_msg = "Starting Transfer...";
  v->xpru.xpru_blocks = v->xpru.xpru_errors = v->xpru.xpru_timeouts = 0;
  v->xpru.xpru_blockcheck = v->Crc32t ? "CRC-32" : "CRC-16";
  v->xpru.xpru_bytes = v->Strtpos = 0;
  update_rate(v);
  UpdateStats(v);
  xpr_update(&v->io,&v->xpru);

  sv = (void *)v->io.xpr_data;
  if (*sv->option_s == 'Y') {
    /* If "SY" option selected, send full path */
    strcpy(v->Pktbuf,v->Filename);
    p = v->Pktbuf + strlen(v->Pktbuf) + 1;
  } else {
    /* else extract outgoing file name without directory path */
    for (p=v->Filename, q=v->Pktbuf ; *p; ++p, ++q)
      if ((*q = *p) == '/' || *q == ':') q = v->Pktbuf - 1;
    *q = '\0';
    p = ++q;
  }

  /* Zero out remainder of file info packet */
  memset(p,0,sizeof(v->Pktbuf) - (p - v->Pktbuf));

  /* Store file size, timestamp, and mode in info packet */
  /* XPR spec doesn't provide a way to get the file timestamp or file mode,
     so we'll just fake it with the current time and a dummy 0. */
  stcl_o(buff,GetSysTimeXPR(NULL)+UnixTimeOffset);
  /* amiga.lib sprintf() can't do %lo format, so we do it the hard way */
  /* Yes, octal; ZModem was originally done on Unix, and they like octal there */
  sprintf(p,"%ld %s 0",(v->Fsize < 0) ? 0L : v->Fsize,buff);

  ptype = FileType(v->Filename);
  if(ptype == F_IS_BINFILE)   updmsg(v,"Sending Regular File...");
  if(ptype == F_IS_ARCFILE)   updmsg(v,"Sending Archived File...");
  if(ptype == F_IS_MSGPACKET) updmsg(v,"Sending FidoNet Msg. Packet...");
  if(ptype == F_IS_FILEREQ)   updmsg(v,"Sending FidoNet File Request...");
  if(ptype == F_IS_ARCMAIL)   updmsg(v,"Sending FidoNet ARCMail...");
  if(ptype == F_IS_TICFILE)   updmsg(v,"Sending FidoNet TICK File...");

  /* Send filename packet */
  return zsendfile(v,(short)(p - v->Pktbuf + strlen(p) + 1));
}



/* Send the filename packet and see if receiver will accept file */
short zsendfile(struct Vars *v,short blen) {
  short c;

  while (TRUE) {
    v->Txhdr[ZF0] = v->Lzconv; /* Text or Binary mode; from config string */
    v->Txhdr[ZF1] = LZMANAG;   /* Default file management mode */
    v->Txhdr[ZF2] = LZTRANS;   /* Default file transport mode */
    v->Txhdr[ZF3] = 0;
    zsbhdr(v,ZFILE);
    zsdata(v,blen,ZCRCW);
    sendbuf(v);
again:
    /* Check for abort from comm program */
    if (v->io.xpr_chkabort && xpr_chkabort(&v->io)) {
      bfclose(v);
      return ERROR;
    }
    switch (c = zgethdr(v)) {
      case ZRINIT:
        goto again;
      case ZCAN:
      case ZCRC:
      case RCDO:
      case TIMEOUT:
      case ZABORT:
      case ZFIN:
        upderr(v,v->Msgbuf);
        return ERROR;
      case ZSKIP:             /* Receiver doesn't want this one */
        upderr(v,"SKIP Command Received");
        updmsg(v,"Skipping File");
        bfclose(v);
        return c;
      case ZRPOS:             /* Receiver wants it; this is starting position */
        bfseek(v,v->Rxpos);
        v->Strtpos = v->Txpos = v->Rxpos;
        if (v->io.xpr_sflush) xpr_sflush(&v->io);
        v->Modemcount = 0;
        return zsendfdata(v);
    }
  }
}



/* Send the file data */
short zsendfdata(struct Vars *v) {
  short c, e, blklen, goodbytes = 0;
  USHORT framelen, maxblklen, goodneeded = 512;
  long calcblock;

  /* Figure out max data packet size to send */

  if(v->NEWBAUD == 0) calcblock = (v->Baud * 8192 / 9600);
  else                calcblock = (v->NEWBAUD * 8192 / 9600);

  maxblklen = (short)calcblock;

  if(v->KSIZE < maxblklen) maxblklen = v->KSIZE;

  if (v->Rxbuflen && maxblklen > v->Rxbuflen) maxblklen = v->Rxbuflen;

  blklen = maxblklen;

#ifdef DEBUGLOG
  sprintf(v->Msgbuf,"Rxbuflen=%ld blklen=%ld\n",(long)v->Rxbuflen,(long)blklen);
  dlog(v,v->Msgbuf);
#endif

  /* If an interruption happened, handle it; else keep sending data */
somemore:
  while (char_avail(v)) {
    /* Check for another incoming packet while discarding line noise */
    switch (readock(v,1)) {
      case CAN:
      case RCDO:
      case ZPAD:
        break;
      default:
        continue;
    }
waitack:
#ifdef DEBUGLOG
    dlog(v,"--- At Waitack\n");
#endif
    switch (c = getinsync(v)) {
      default:
        upderr(v,"Transfer Cancelled");
        updmsg(v,"File Transfer FAILED");
        bfclose(v);
        return ERROR;
      case ZSKIP:  /* Receiver changed its mind and wants to skip the file */
        updmsg(v,"Skipping File");
        return c;
      case ZACK:   /* ACK at end of frame; resume sending data */
        break;
      case ZRPOS:  /* An error; resend data from last good point */
        if(v->fall_back) blklen >>= 2;
        if (blklen < MINBLOCK) blklen = MINBLOCK;
        if (goodneeded < MAXGOODNEEDED) goodneeded <<= 1;
        v->xpru.xpru_updatemask = XPRU_ERRORS;
        ++v->xpru.xpru_errors;
        UpdateStats(v);
        xpr_update(&v->io,&v->xpru);
        break;
      case ZRINIT:
        updmsg(v,"File Transfer OK");
        return OK;
    }
  }

  /* Transmit ZDATA frame header */
  framelen = v->Rxbuflen;
  stohdr(v,v->Txpos);
  zsbhdr(v,ZDATA);

  /* Keep sending data packets until finished or interrupted */
  do {
    /* Read next chunk of file data */
    c = bfread(v,v->Pktbuf,(long)blklen);

    /* Figure out how to handle this data packet */
    if (c < blklen)
      e = ZCRCE;  /* If end of file, this is last data packet */
    else if (v->Rxbuflen && (framelen -= c) <= 0)
      e = ZCRCW;  /* If end of frame, ask for ACK */
    else
      e = ZCRCG;  /* Else tell receiver to expect more data packets */

    zsdata(v,c,e);  /* Send the packet */
    sendbuf(v);

    /* Update comm program status display */
    v->xpru.xpru_updatemask = XPRU_BLOCKS | XPRU_BLOCKSIZE | XPRU_BYTES |
                              XPRU_EXPECTTIME | XPRU_ELAPSEDTIME | XPRU_DATARATE | XPRU_BLOCKCHECK;
    ++v->xpru.xpru_blocks;
    v->xpru.xpru_blocksize = c;
    v->xpru.xpru_blockcheck = v->Crc32t ? "CRC-32" : "CRC-16";
    v->xpru.xpru_bytes = v->Txpos += c;
    update_rate(v);
    UpdateStats(v);
    xpr_update(&v->io,&v->xpru);

    /* If we've been sending smaller than normal packets, see if it's
       time to bump the packet size up a notch yet */
    if (blklen < maxblklen && (goodbytes += c) >= goodneeded) {
       if(v->fall_forward) blklen <<= 1;
       if(blklen > maxblklen) blklen = maxblklen;
       goodbytes = 0;
#ifdef DEBUGLOG
       sprintf(v->Msgbuf,"Bumping Backet Size To %ld At %ld\n",(long)blklen,v->Txpos);
       dlog(v,v->Msgbuf);
#endif
    }

    /* Give comm program its timeslice if it needs one */
    if (v->io.xpr_chkmisc) xpr_chkmisc(&v->io);
    /* Check for abort from comm program */
    if (v->io.xpr_chkabort && xpr_chkabort(&v->io)) goto aborted;
    /* If this was last packet in frame, go wait for ACK from receiver */
    if (e == ZCRCW) goto waitack;

    /* Check if receiver trying to interrupt us; look for incoming packet
       while discarding line noise */
    while (char_avail(v)) {
      switch (readock(v,1)) {
        case CAN:
        case RCDO:
        case ZPAD:
          /* Interruption detected; stop sending and process complaint */
#ifdef DEBUGLOG
          dlog(v,"--- Interrupted Send\n");
#endif
          zsdata(v,0,ZCRCE);
          sendbuf(v);
          goto waitack;
      }
    }
  } while (e == ZCRCG);  /* If no interruption, keep sending data packets */

  /* Done sending file data; send EOF and wait for receiver to acknowledge */
  while (TRUE) {
    updmsg(v,"Sending EOF");
    stohdr(v,v->Txpos);
    zsbhdr(v,ZEOF);
    sendbuf(v);
    switch (c = getinsync(v)) {
      case ZACK:
        continue;
      case ZRPOS:
        goto somemore;
      case ZRINIT:
        updmsg(v,"EOF Acknowledged");
        updmsg(v,"File Transfer OK");
        ++v->Starttime.tv_secs;
        update_rate(v);
        v->xpru.xpru_updatemask = XPRU_EXPECTTIME | XPRU_ELAPSEDTIME | XPRU_DATARATE;
        UpdateStats(v);
        xpr_update(&v->io,&v->xpru);
        return OK;
      case ZSKIP:
        return c;
      default:
aborted: upderr(v,"Transfer Cancelled");
        updmsg(v,"File Transfer FAILED");
        bfclose(v);
        return ERROR;
    }
  }
}



/* Respond to receiver's complaint, get back in sync with receiver */
short getinsync(struct Vars *v) {
  short c;

  while (TRUE) {
#ifdef DEBUGLOG
    dlog(v,"--- At Getinsync\n");
#endif
    c = zgethdr(v);
    if (v->io.xpr_sflush)
      xpr_sflush(&v->io);
    v->Modemcount = 0;
    switch (c) {
      case ZCAN:
      case ZABORT:
      case ZFIN:
      case RCDO:
      case TIMEOUT:
        upderr(v,v->Msgbuf);
        return ERROR;
      case ZRPOS:
        bfseek(v,v->Rxpos);
        v->Txpos = v->Rxpos;
        sprintf(v->Msgbuf,"Resending From %ld",v->Txpos);
        upderr(v,v->Msgbuf);
        return c;
      case ZSKIP:
        upderr(v,"SKIP Command Received");
        /* fallthrough... */
      case ZRINIT:
        bfclose(v);
        /* fallthrough... */
      case ZACK:
        return c;
      default:
        zsbhdr(v,ZNAK);
        sendbuf(v);
        continue;
    }
  }
}



/* End of batch transmission; disengage cleanly from receiver */
void saybibi(struct Vars *v) {
  while (TRUE) {
    stohdr(v,0L);
    zsbhdr(v,ZFIN);
    sendbuf(v);
    switch (zgethdr(v)) {
      case ZFIN:
        sendline(v,'O');
        sendline(v,'O');
        sendbuf(v);
        /* fallthrough... */
      case ZCAN:
      case RCDO:
      case TIMEOUT:
        return;
    }
  }
}

int FileType(char *FileName){
   int x = 0,y = 0;
   char FExtn[255];

   /* This Is A  Little Routine  To  Show The Type  Of File Being Sent */
   /* This Can Be Useful Since FidoNet  Specifies That Message Bundles */
   /* Should Be Sent Before Regular Files (Which Should Be Sent Before */
   /* File Requests...                                                 */

   /* Move Forward In Filename Untill We Hit The Extension */
   while((FileName[x] != 0)&&(FileName[x] != '.')) x++;

   /* If There Is No Extension... */
   if(FileName[x] == 0) return(F_IS_BINFILE);

   /* Get The Extension */
   while(FileName[x] != 0){
      FExtn[y]   = FileName[x];
      FExtn[y+1] = 0;
      x++;
      y++;
   }

   /* Possible Extensions For FidoNet Bundles */
   if(stricmp(FExtn,".PKT") == NULL) return(F_IS_MSGPACKET);
   if(stricmp(FExtn,".OUT") == NULL) return(F_IS_MSGPACKET);
   if(stricmp(FExtn,".CUT") == NULL) return(F_IS_MSGPACKET);
   if(stricmp(FExtn,".HUT") == NULL) return(F_IS_MSGPACKET);
   if(strnicmp(FExtn,".MO",3) == NULL) return(F_IS_ARCMAIL);
   if(strnicmp(FExtn,".TU",3) == NULL) return(F_IS_ARCMAIL);
   if(strnicmp(FExtn,".WE",3) == NULL) return(F_IS_ARCMAIL);
   if(strnicmp(FExtn,".TH",3) == NULL) return(F_IS_ARCMAIL);
   if(strnicmp(FExtn,".FR",3) == NULL) return(F_IS_ARCMAIL);
   if(strnicmp(FExtn,".SA",3) == NULL) return(F_IS_ARCMAIL);
   if(strnicmp(FExtn,".SU",3) == NULL) return(F_IS_ARCMAIL);

   /* Possible Extensions For FidoNet FRequests */
   if(stricmp(FExtn,".REQ") == NULL) return(F_IS_FILEREQ);

   /* Possible Extensions For Archived Files */
   if(stricmp(FExtn,".ARC") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".ARJ") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".ZOO") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".ZIP") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".ZOM") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".LZH") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".LHA") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".LHW") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".WRP") == NULL) return(F_IS_ARCFILE);
   if(stricmp(FExtn,".DMS") == NULL) return(F_IS_ARCFILE);

   if(stricmp(FExtn,".TIC") == NULL) return(F_IS_TICFILE);

   /* Otherwise, The File Is Just A Plain Unidentified File */
   return(F_IS_BINFILE);
}
