/*  send.c:  File reception routines for xprbimodem.library;
    Version 1.01, 04 Sep 1992, by Dirk Peterson.
    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 "biproto.h"
#include "bimodem.h"
#include "xprbimodem.h"

#ifdef DEBUGLOG
extern void *DebugLog;
#endif



/* 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->Rxtimeout = 600;

  /* 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);

  /* Clean up and return */
  if (err = v->Errcnt)
    upderr(v,"One or more files skipped due to errors");
  else
    updmsg(v,"Done.");
  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->Rxbuflen = ((USHORT)v->Rxhdr[ZP1]<<8) | v->Rxhdr[ZP0];
#ifdef DEBUGLOG
        sprintf(v->Msgbuf,"Rxbuflen=%ld Tframlen=%ld\n",(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;

  /* 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);
  /* 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) {
      upderr(v,"No files match template");
      return;
    }
  }

  /* If using templates, keep getting names & sending until done */
  while (!done) {
    if (sendone(v) == ERROR) return;
    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) saybibi(v);
  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;
  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 (!(v->File = bfopen(v,"r"))) {
    ++v->Errcnt;
    upderr(v,"Can't open file; skipping");
    return OK;      /* pass over it, there may be others */
  }
  ++v->Filcnt;
  GetSysTime(&v->Starttime);

  /* Kick off the file transfer */
  sv = (void *)v->io.xpr_data;
  switch (sendname(v)) {
    case ERROR:
      ++v->Errcnt;
      return ERROR;
    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 after send");
        xpr_unlink(&v->io,v->Filename);
      }
      break;
  }
  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];

  /* 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 = "ZModem";
  v->xpru.xpru_filesize = v->Fsize;
  v->xpru.xpru_msg = (v->Lzconv == ZCNL) ? "Sending text file..." :
    ( (v->Lzconv == ZCBIN) ? "Sending binary file..." : "Sending file...");
  v->xpru.xpru_blocks = v->xpru.xpru_errors = v->xpru.xpru_timeouts = 0;
  v->xpru.xpru_blockcheck = "CRC-16";
  v->xpru.xpru_bytes = v->Strtpos = 0;
  update_rate(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,GetSysTime(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);

  /* 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");
        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;

  /* Figure out max data packet size to send */
  maxblklen = KSIZE;
  if (v->Rxbuflen && maxblklen > v->Rxbuflen)
    maxblklen = v->Rxbuflen;
  blklen = (v->Baud < 1200) ? 256 : KSIZE;
  if (blklen > maxblklen)
    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");
        bfclose(v);
        return ERROR;
      case ZSKIP:  /* Receiver changed its mind and wants to skip the file */
        return c;
      case ZACK:   /* ACK at end of frame; resume sending data */
        break;
      case ZRPOS:  /* An error; resend data from last good point */
        blklen >>= 2;
        if (blklen < MINBLOCK) blklen = MINBLOCK;
        if (goodneeded < MAXGOODNEEDED) goodneeded <<= 1;
        v->xpru.xpru_updatemask = XPRU_ERRORS;
        ++v->xpru.xpru_errors;
        xpr_update(&v->io,&v->xpru);
        break;
      case ZRINIT:
        updmsg(v,"Done.");
        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;
    ++v->xpru.xpru_blocks;
    v->xpru.xpru_blocksize = c;
    v->xpru.xpru_bytes = v->Txpos += c;
    update_rate(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) {
      blklen <<= 1;
      if (blklen > maxblklen) blklen = maxblklen;
      goodbytes = 0;
#ifdef DEBUGLOG
      sprintf(v->Msgbuf,"Bumping packet 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");
        ++v->Starttime.tv_secs;
        update_rate(v);
        v->xpru.xpru_updatemask = XPRU_EXPECTTIME | XPRU_ELAPSEDTIME | XPRU_DATARATE;
        xpr_update(&v->io,&v->xpru);
        return OK;
      case ZSKIP:
        return c;
      default:
aborted: upderr(v,"Transfer cancelled");
        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;
    }
  }
}
