/* atob.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


BYTE atob(infile)
register FILE *infile;
{
  register BYTE error;
  register LONG filepos;
  int maxperline;
  LONG n1, n2, oeor, osum, orot, lastline;
  static BYTE outfilename[BUFSIZE];
  extern LONG Ceor, Csum, Crot;
  extern FILE *outfile;
  extern BYTE new_version, openoutput, buffer[BUFSIZE];

  error = FALSE;

  /* Search for archive header. */
  do
  {
    filepos = ftell(infile);

    if (readbuffer(buffer, "archive", infile))
      error = TRUE;
  }
  while (!(error || strncmp(buffer, "xbtoa", 5) == 0));

  if (!error)
    if (strcmp(buffer, "xbtoa Begin\n") == 0)
    {
      new_version = FALSE;
      fprintf(stderr, "btoa: Old btoa format.\n");
    }
    else if (sscanf(buffer, "xbtoa5 %d %s Begin\n", &maxperline, outfilename) == 2)
    {
      new_version = TRUE;
      /* Naming a file overrides the read-name-from-file function. */
      if (!openoutput && strcmp(outfilename, "-") != 0)
        if ((outfile = fopen_write(outfilename)) == NULL)
          error = TRUE;
        else
          openoutput = TRUE;
    }
    else
    {
      fprintf(stderr, "btoa: Illegal archive header.\n");
      error = TRUE;
    }

  if (!error)
  {
    Ceor = Csum = Crot = 0;

    if (new_version)
      error = new_decodefile(infile, &lastline, filepos, maxperline);
    else
      error = old_decodefile(infile, &lastline);
  }

  if (!error)
  {
    if (sscanf(buffer, "xbtoa End N %ld %lx E %lx S %lx R %lx\n",
        &n1, &n2, &oeor, &osum, &orot) != 5)
    {
      fprintf(stderr, "btoa: Bad format on line %ld. Can't repair file.\n",
          lastline);
      error = TRUE;
    }
    else if ((n1 != n2) || (oeor != Ceor) || (osum != Csum) || (orot != Crot))
    {
      fprintf(stderr, "btoa: Bad file checksum. Can't repair file.\n");
      error = TRUE;
    }
    else
      /* Flush last characters. */
      decode_line(NULL, (int) ((n1 - 1) & 0x03));
  }

  return(error);
}


/* Peek at the next byte without moving the file pointer. */
int nextbyte(infile)
register FILE *infile;
{
  register int ch;
  register LONG filepos;

  filepos = ftell(infile);
  ch = fgetc(infile);
  fseek(infile, filepos, 0);

  return(ch);
}


BYTE new_decodefile(infile, lastline, filepos, maxperline)
register FILE *infile;
LONG *lastline, filepos;
int maxperline;
{
  register int length;
  int ch;
  register BYTE stop, error, newerror, errorstart;
  register LONG line, prevfilepos, startpos;
  struct Diagnosis diagnosislist;
  extern LONG Csum;
  extern BYTE buffer[BUFSIZE];

  error = FALSE;

  line = 1;  /* Current line number. */

  /* A sequence of errors is indicated by errorstart. When it */
  /* changes from TRUE to FALSE a diagnosis record will be    */
  /* generated.                                               */
  stop = errorstart = FALSE;

  /* File position of the line before the error sequence.     */
  /* That is the last correct line.                           */
  startpos = 0;

  while (!stop)
  {
    prevfilepos = filepos;
    filepos = ftell(infile);

    /* Newerror indicates an error on the current line. */
    newerror = FALSE;

    line++;
    if (readbuffer(buffer, "archive", infile))
      newerror = stop = TRUE;
    else if (buffer[0] == 'x')  /* End of archive found. */
      stop = TRUE;
    else if ((length = strlen(buffer) - 1) != maxperline ||
             buffer[length] != '\n')
    {
      /* If last character wasn't end-of-line, then we */
      /* have to read until it is found.               */
      if (buffer[length] != '\n')
      {
        newerror = TRUE;
        while ((ch = fgetc(infile)) != EOF && (BYTE)ch != '\n')
          ;
        if (ch == EOF)
          stop = TRUE;
      }
      else if (length > maxperline || nextbyte(infile) != 'x')
      {
        newerror = TRUE;
        Csum = DECODE(buffer[length - 1]);  /* Make Csum correct (modulo 85). */
      }

      if (newerror)
        fprintf(stderr, "btoa: Bad line length on line %ld.\n", line);
    }

    if (!(newerror || stop))
    {
      if (decode_line(buffer, length - 1))
      {
        if (!error)
          fprintf(stderr, "btoa: Bad character on line %ld.\n", line);
        newerror = TRUE;
      }

      /* Examine checksum. */
      if ((ch = buffer[length - 1]) == ENCODE(Csum % 85))
      {
        if (errorstart)
        {
          intodiagnosislist(&diagnosislist, startpos, filepos);
          errorstart = FALSE;
        }
      }
      else
      {
        newerror = TRUE;
        fprintf(stderr, "btoa: Bad checksum on line %ld.\n", line);
        Csum = DECODE(ch);  /* Make Csum correct (modulo 85). */
      }
    }

    if (newerror)
    {
      if (!error)
      {
        fprintf(stderr, "btoa: Starting diagnosis.\n");
        diagnosislist.next = diagnosislist.last = NULL;
      }

      error = TRUE;
      if (!errorstart)
      {
        errorstart = TRUE;
        startpos = prevfilepos;
      }
    }
  }

  if (error)
  {
    if (errorstart)
      intodiagnosislist(&diagnosislist, startpos, filepos);
    producediagnosis(&diagnosislist, infile);
  }

  *lastline = line;

  return(error);
}



BYTE old_decodefile(infile, lastline)
register FILE *infile;
LONG *lastline;
{
  register int length;
  register BYTE stop, error;
  register LONG line;
  extern BYTE buffer[BUFSIZE];

  error = FALSE;

  line = 1;
  stop = FALSE;
  while (!stop)
  {
    line ++;

    if (readbuffer(buffer, "archive", infile))
      error = stop = TRUE;
    else if (buffer[0] == 'x')  /* End of archive found. */
      stop = TRUE;
    else
    {
      length = strlen(buffer) - 1;
      if (buffer[length] != '\n')
        error = stop = TRUE;  /* The line was longer than the buffer. */
    }

    if (!stop)
    {
      if (decode_line(buffer, length))
      {
        fprintf(stderr, "btoa: Bad character on line %ld.\n", line);
        error = stop = TRUE;
      }
    }
  }

  *lastline = line;

  return(error);
}


BYTE decode_line(buffer, length)
register BYTE *buffer;
register int length;
{
  register int ch;
  register BYTE error;
  register LONG tmp_codeword;
  extern BYTE new_version;
  extern FILE *outfile;
  static LONG codeword;
  static int ch1, ch2, ch3, ch4;
  static BYTE bytecount = 0;

  error = FALSE;

  if (buffer == NULL)  /* Flush last characters. */
  {
    if (bytecount > 0)
    {
      fputc(ch1, outfile);
      if (length > 0)
        fputc(ch2, outfile);
      if (length > 1)
        fputc(ch3, outfile);
      if (length > 2)
        fputc(ch4, outfile);
    }
  }
  else
  {
    while (length > 0)
    {
      length--;
      ch = *buffer++;

      /* Delayed output. This is to make sure that files with lengths */
      /* that are not multiples of 4 won't become too long.           */
      if (bytecount == 5)
      {
        fputc(ch1, outfile);
        fputc(ch2, outfile);
        fputc(ch3, outfile);
        fputc(ch4, outfile);

        bytecount = 0;
      }

      if (new_version)
        calcchecksum(ch);

      if (((BYTE)ch >= '!') && ((BYTE)ch < ('!' + 85)))  /* Valid characters. */
      {
        /* See if we can take all 5 bytes and decode them right away. */
        /* That is, if all remaining bytes are on the current line.   */
        if (length >= 4 - bytecount)
        {
          length -= 4 - bytecount;

          if (bytecount == 0)
            codeword = DECODE(ch);
          else
            codeword = codeword * 85 + DECODE(ch);

          for (bytecount++; bytecount < 5; bytecount++)
          {
            ch = *buffer++;
            if (new_version)
              calcchecksum(ch);
            codeword = codeword * 85 + DECODE(ch);
          }
        }
        else
        {
          /* Shift codeword and insert character. */

          if (bytecount == 0)
          {
            codeword = DECODE(ch);
            bytecount = 1;
          } 
          else /* bytecount < 5 */
          {
            codeword = codeword * 85 + DECODE(ch);
            bytecount ++;
          }
        }

        if (bytecount == 5)
        {
          tmp_codeword = codeword;

          ch4 = (int)tmp_codeword & 0xFF;
          ch3 = (int)(tmp_codeword >>= 8) & 0xFF;
          ch2 = (int)(tmp_codeword >>= 8) & 0xFF;
          ch1 = (int)(tmp_codeword >> 8) & 0xFF;

          if (!new_version)
          {
            calcchecksum(ch1);
            calcchecksum(ch2);
            calcchecksum(ch3);
            calcchecksum(ch4);
          }
        }
      }
      else if ((BYTE)ch == 'z' || (new_version && (BYTE)ch == 'y'))
      {
        if (bytecount != 0)
          error = TRUE;
        else
        {
          ch1 = ch2 = ch3 = ch4 = (ch == 'z') ? 0 : ' ';
          if (!new_version)
          {
            calcchecksum(ch1);
            calcchecksum(ch1);
            calcchecksum(ch1);
            calcchecksum(ch1);
          }
          bytecount = 5;
        }
      }
      else
        error = TRUE;
    }
  }

  return(error);
}
