/* btoa.c */

/* Written by Paul Rutter, Joe Orost & Stefan Parmark. */

#include <stdio.h>
#ifdef AMIGA
#include <stdlib.h>
#include <string.h>
#endif AMIGA

#include "btoa.h"
#if USE_MACROS
#include "chksum.h"
#endif USE_MACROS

#define VERSION  "5.2"

LONG Ceor, Csum, Crot;  /* Checksums to verify archive validity. */
BYTE new_version, openoutput, buffer[BUFSIZE];
FILE *outfile;


void main(argc, argv)
int argc;
BYTE **argv;
{
  register BYTE openinput, error, ch, a_to_b, diagnosis, repair;
  register BYTE *infilename, *text;
  register FILE *infile;
  extern BYTE new_version, openoutput;
  extern FILE *outfile;
#ifdef AMIGA
  extern int _bufsiz;

  /* Change file buffer size. */
  _bufsiz = 10240;
#endif AMIGA

  error = openinput = openoutput = a_to_b = diagnosis = repair = FALSE;
  new_version = TRUE;
  infilename = NULL;

  /* Scan for '-' options. The rest must be file names. */
  while (!error && argc > 1 && *argv[1] == '-')
  {
    text = &argv[1][1];
    while (!error && (ch = *text++) != 0)
    {
      switch(ch)
      {
        case 'a' : /* Activate atob. */
                   a_to_b = TRUE;
                   break;
        case 'd' : /* Extract missing part from undamaged archive. */
                   diagnosis = TRUE;
                   break;
        case 'h' : /* Print help and abort execution. */
                   error = TRUE;
                   break;
        case 'o' : /* Use old btoa format. */
                   new_version = FALSE;
                   break;
        case 'r' : /* Repair damaged archive. */
                   repair = TRUE;
                   break;
        default  : error = TRUE;
      }
    }
    argv++;
    argc--;
  }

  if (argc > 3)
    error = TRUE;

  if (error)
    printhelp();
  else
  {
    /* If file name was given, try to open file. Otherwise use stdin. */
    if (argc > 1)
    {
      infilename = argv[1];
      if ((infile = fopen_read(infilename)) == NULL)
        error = TRUE;
      else
        openinput = TRUE;
    }
    else
      infile = stdin;
  }

  if (!error)
  {
    /* If file name was given, try to open file. Otherwise use stdout. */
    if (argc > 2 && !diagnosis && !repair)
    {
      if ((outfile = fopen_write(argv[2])) == NULL)
        error = TRUE;
      else
        openoutput = TRUE;
    }
    else
      outfile = stdout;
  }

  if (!error)
  {
    if (diagnosis)
      error = producerepair(infile);
    else if (repair)
      error = performrepair(infile);
    else if (a_to_b)
      error = atob(infile);
    else
      error = btoa(infile, infilename);
  }

  /* Close all opened files. */
  if (openinput)
    fclose(infile);
  if (openoutput)
    fclose(outfile);

  if (error)
    exit(1);
}


BYTE btoa(infile, infilename)
register FILE *infile;
register BYTE *infilename;
{
  register LONG codeword, filesize;
  register int ch1, ch2, ch3, ch4, readbytes;
  extern FILE *outfile;
  extern BYTE new_version, buffer[BUFSIZE];
  extern LONG Ceor, Csum, Crot;

  Ceor = Csum = Crot = 0;

  /* Write archive header. */
  if (new_version)
  {
    fprintf(outfile, "xbtoa5 %d %s Begin\n", MAXPERLINE,
        (infilename == NULL) ? "-" : truncname(infilename));
  }
  else
    fprintf(outfile, "xbtoa Begin\n");

  /* Encode entire input file. */
  filesize = 0;
  do
  {
    readbytes = fread(buffer, 1, 4, infile);

    if (readbytes < 4)
    {
      ch1 = (readbytes > 0) ? ((int)buffer[0] & 0xFF) : 0;
      ch2 = (readbytes > 1) ? ((int)buffer[1] & 0xFF) : 0;
      ch3 = (readbytes > 2) ? ((int)buffer[2] & 0xFF) : 0;
      ch4 = 0;
    }
    else
    {
      ch1 = (int)buffer[0] & 0xFF;
      ch2 = (int)buffer[1] & 0xFF;
      ch3 = (int)buffer[2] & 0xFF;
      ch4 = (int)buffer[3] & 0xFF;
    }

    if (readbytes > 0)
    {
      if (!new_version)
      {
        calcchecksum(ch1);
        calcchecksum(ch2);
        calcchecksum(ch3);
        calcchecksum(ch4);
      }

      codeword = (ch1 << 8) | ch2;
      codeword = (((codeword << 8) | ch3) << 8) | ch4;
      wordout(codeword);

      filesize += readbytes;
    }
  }
  while (readbytes == 4);

  asciiout(EOF);  /* Flush buffer. */

  /* Filesize is written twice as crude cross check. */
  fprintf(outfile, "xbtoa End N %ld %lx E %lx S %lx R %lx\n",
        filesize, filesize, Ceor, Csum, Crot);

  return(FALSE);  /* No errors discovered. */
}


/* Print help on how to use btoa. */
void printhelp()
{
  fprintf(stderr, "              Btoa version %s\n", VERSION);
  fprintf(stderr, "Written by Paul Rutter, Joe Orost & Stefan Parmark.\n");

  fprintf(stderr, "\nUsage: btoa [-{adhor}] [input file] [output file]\n");

  fprintf(stderr, "\nOptions:\n");
  fprintf(stderr, "-h  Shows this help list.\n");
  fprintf(stderr, "-a  Use atob rather than btoa.\n");
  fprintf(stderr, "-o  Use old version of btoa.\n");
  fprintf(stderr, "-d  Extract repair file from diagnosis file.\n");
  fprintf(stderr, "-r  Repair archive from repair file.\n");

  fprintf(stderr, "\nExamples:\n");
  fprintf(stderr, "  btoa -h\n");
  fprintf(stderr, "  btoa [input binary file] [output archive file]\n");
  fprintf(stderr, "  btoa -o [input binary file] [output archive file]\n");
  fprintf(stderr, "  btoa -a [input archive file] [output binary file]\n");
  fprintf(stderr, "  btoa -d [undamaged archive file]\n");
  fprintf(stderr, "  btoa -r [damaged archive file]\n");
}


#if !USE_MACROS
/* Update file checksums. */
void calcchecksum(ch)
register int ch;
{
  extern LONG Ceor, Csum, Crot;

  Ceor ^= ch;
  Csum += ch + 1;

  if (Crot & 0x80000000L)
    ch ++;
  Crot <<= 1;
  Crot += ch;
}
#endif !USE_MACROS


/* Encode 4 binary bytes to 5 ascii bytes. */
void wordout(codeword)
register LONG codeword;
{
  register int tmp, quote;
  extern BYTE new_version;

  if (codeword == 0)
    /* Encode 4 zeros as a 'z'. */
    asciiout('z');
  else if (new_version && codeword == 0x20202020)
    /* Encode 4 spaces as a 'y'. */
    asciiout('y');
  else
  {
    tmp = 0;

    /* Extra calculations because some machines don't support */
    /* unsigned longwords.                                    */
    if (codeword < 0)
    {
      tmp = 32;
      codeword -= (LONG)(85L * 85 * 85 * 85 * 32);
    }
    if (codeword < 0)
    {
      tmp = 64;
      codeword -= (LONG)(85L * 85 * 85 * 85 * 32);
    }

    /* Write 5 ascii bytes representing 4 binary bytes. */

    quote = codeword / (LONG)(85L * 85 * 85 * 85);
    codeword -= quote * (LONG)(85L * 85 * 85 * 85);
    asciiout(ENCODE(quote + tmp));

    quote = codeword / (LONG)(85L * 85 * 85);
    codeword -= quote * (LONG)(85L * 85 * 85);
    asciiout(ENCODE(quote));

    quote = codeword / (LONG)(85L * 85);
    codeword -= quote * (LONG)(85L * 85);
    asciiout(ENCODE(quote));

    quote = (int)codeword / 85;
    codeword -= quote * 85;
    asciiout(ENCODE(quote));

    asciiout(ENCODE((int)codeword));
  }
}


/* Write ch to outfile. Write '\n' for every line. */
void asciiout(ch)
register int ch;
{
  static WORD linepos = 0;
  extern FILE *outfile;
  extern LONG Csum;
  extern BYTE new_version;

  if (ch == EOF)  /* Signal to flush buffer. */
  {
    /* Linepos == 0 means '\n' just written in asciiout(). This */
    /* avoids bug in BITNET, which changes blank line to spaces. */
    if (linepos != 0)
    {
      if (new_version)
        fputc(ENCODE(Csum % 85), outfile); /* Checksum for every line. */
      fputc('\n', outfile);
    }
  }
  else
  {
    fputc(ch, outfile);
    linepos ++;

    if (new_version)
    {
      calcchecksum(ch);
      if (linepos >= (MAXPERLINE-1))
      {
        fputc(ENCODE(Csum % 85), outfile); /* Checksum for every line. */
        fputc('\n', outfile);
        linepos = 0;
      }
    }
    else  /* Old version */
      if (linepos >= MAXPERLINE)
      {
        fputc('\n', outfile);
        linepos = 0;
      }

  }
}


/* Remove paths from a file name. */
BYTE *truncname(name)
register BYTE *name;
{
  register BYTE ch, *newname;

  newname = name;
  while ((ch = *name++) != 0)
    if (ch == '/' || ch == ':')
      newname = name;

  return(newname);
}
