/******************************************************************************
 * AdtAm.c - Apple II Disk Transfer utility, file server, Amiga version.
 *
 * $Header: Big:Archives/Apple II/AdtAm/Amiga/RCS/AdtAm.c,v 1.8 2000/02/08 16:27:23 AGMS Exp $
 *
 * Ported from the original Apple Disk Transfer 1.22
 * By Paul Guertin pg@sff.net, December 4th, 1995 - 1999.
 * Distribute freely.
 *
 * Port done by Alexander G. M. Smith, agmsmith@achilles.net, January 2000.
 *
 * Compile this Amiga version with SAS C 6.58, tested to run under
 * AmigaDOS 2.1 on a 68030.
 *
 * $Log: AdtAm.c,v $
 * Revision 1.8  2000/02/08  16:27:23  AGMS
 * Updated to match version 1.22 of ADT.
 *
 * Revision 1.7  2000/02/07  13:10:32  AGMS
 * *** empty log message ***
 *
 * Revision 1.6  2000/02/07  13:08:22  AGMS
 * *** empty log message ***
 *
 * Revision 1.5  2000/02/07  13:01:42  AGMS
 * Added Amiga style version string.
 *
 * Revision 1.4  2000/02/02  16:23:09  AGMS
 * Fixed up help message a bit.
 *
 * Revision 1.3  2000/02/02  16:18:38  AGMS
 * Reading and writing disk images now works!
 *
 * Revision 1.2  2000/01/30  17:39:09  AGMS
 * Got the directory listing feature to work!
 *
 * Revision 1.1  2000/01/29  23:19:36  AGMS
 * Initial revision
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <ctype.h>
#include <setjmp.h>
#include <time.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include "CommAm.h"

#define ACK 0x06
#define NAK 0x15

int main( int argc, char **argv );
void send_directory( void );
void send_disk( void );
int send_sector( unsigned char *buffer, int part, int track, int sector );
void receive_disk( void );
int receive_sector( unsigned char *buffer, int part, int track, int sector );
int getfname( char *fname );
int set_port( void );
void give_help( void );
void make_crctable( void );
unsigned short do_crc( unsigned char *ptr, int count );
void touppers (char *String);

long __stack = 50000;
/* Set the stack size, a SAS/C compiler runtime feature.
   We store a big 30K disk buffer on the stack, so make it large,
   also Quicksort of the directory listing needs space. */

int comport = 0;                    /* Serial device unit number. */
int comspeed = 19200;               /* Baud rate. */
char comname[80] = "serial.device"; /* Device name. */
jmp_buf beginning;                  /* where to abort when Ctrl-C pushed */
unsigned short crctable[256];       /* CRC-16 lookup table for speed */
char DirectoryListingBuffer [25000];
char *NamePointerArray [1000];      /* Max of 1000 files in a directory. */

int main( int argc, char **argv )
{
    int command;                      /* character from Apple */

    if (argc <= 1)
    {
      give_help();                    /* give help and quit */
      return 0;
    }

    while( *++argv ) {
        if( !isdigit( **argv ) )      /* Non-digit is serial device name. */
            strncpy (comname, *argv, sizeof (comname));
        else if( !(*argv)[1] )        /* kluge: a number is a port */
            comport = **argv - '0';   /* iff it has only one digit. */
        else
            comspeed = atoi( *argv );
    }

    make_crctable();

refresh:
    setjmp( beginning );                    /* return here when ESC pushed */

    printf ("\nApple Disk Transfer 1.22, Amiga version.\n");

    if( !set_port() )
        return 20; /* Unable to open serial port. */

    printf ("Type Control-C to interrupt server mode and enter a command.\n");

    for( ;; ) {
        while( !comm_avail() ) {
            if (SetSignal (0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
                printf ("Enter R to restart, Q to quit: ");
                fflush (stdout);
                command = getch ();
                printf ("%c\n", (char) command);
                switch (command) {
                    case 'R': case 'r':
                        goto refresh;
                    case 'Q': case 'q':
                        printf ("\nGoodbye!\n");
                        goto NormalExit;
                }
            }
            Delay (10); /* Don't hog the CPU, sleep a while (50 ticks per sec). */
        }
        command = comm_getc() & 0x7F;
        printf ("Got command \"%c\" from the Apple II.\n", (char) command);
        switch( command ) {
            case 'D': send_directory();  break;
            case 'R': send_disk(); break;
            case 'S': receive_disk(); break;
            default:
                printf ("ERROR: unknown command received: %02X\n", command);
        }
    }

NormalExit:
    comm_close ();
    return 0;
}



/******************************************************************************
 * Display the current communications settings and try to open the serial
 * port with those values.  Returns TRUE if successful.
 */

int set_port (void)
{
    comm_close ();

    printf ("Device name: %s\n", comname);
    printf ("Device unit number: %d\n", comport);
    printf ("Baud rate: %d\n", comspeed);

    if (!comm_open (comname, comport, comspeed))
    {
        printf( "Unable to open device \"%s\" unit %d at %d bps.\n",
            comname, comport, comspeed);
        return FALSE;
    }
    return TRUE;
}



/******************************************************************************
 * Format and send a listing of the current of current directory.
 */

void send_directory (void)
{
    int   FileCount;
    int   i;
    int   LineCount;
    char *StringPntr;
    char  TempString [80];

    comm_puts ("LISTING OF AMIGA DIRECTORY NAMED\r");
    getcwd (DirectoryListingBuffer, sizeof (DirectoryListingBuffer));
    touppers (DirectoryListingBuffer);
    comm_puts (DirectoryListingBuffer);
    if ((strlen (DirectoryListingBuffer) % 40) != 0)
        comm_putc ('\r');
    comm_puts ("---------------------------------------\r");
    LineCount = 3 + (strlen (DirectoryListingBuffer) - 1) / 40;

    printf ("Sending directory listing of %s\n", DirectoryListingBuffer);

    /* Fill the buffer with the names of the files, separated by
       NUL characters, double NUL at the end. */

    FileCount = getfnl ("#?", DirectoryListingBuffer,
        sizeof (DirectoryListingBuffer), 0);

    if (FileCount > 0)
    {
        /* Try to sort the file names by sorting an array of pointers to
           the names.  Also convert to upper case (helps with sort). */

        StringPntr = DirectoryListingBuffer + 0;
        for (i = 0; i < sizeof (NamePointerArray) / sizeof (char *); i++)
        {
            if (*StringPntr == 0)
                break; /* Reached the end of the list of names. */
            NamePointerArray[i] = StringPntr;
            touppers (StringPntr);
            StringPntr += strlen (StringPntr) + 1;
        }

        if (i != FileCount)
        {
            sprintf (TempString,
                "CAN ONLY SHOW %d OF %d FILES.\r", i, FileCount);
            comm_puts (TempString);
            LineCount++;
            FileCount = i;
        }
        tqsort (NamePointerArray, FileCount);
    }
    else if (FileCount == 0)
    {
        comm_puts ("NO FILES.\r");
        DirectoryListingBuffer[0] = 0;
    }
    else /* FileCount < 0 */
    {
        comm_puts ("DISK ERROR OR DIRECTORY TOO BIG.\r");
        DirectoryListingBuffer[0] = 0;
    }

    for (i = 0; i < FileCount; i++)
    {
        /* Print one line of file name. */

        StringPntr = NamePointerArray [i];
        printf ("Line %2d, File %3d, Name \"%s\".\n", LineCount, i, StringPntr);
        comm_puts (StringPntr);
        comm_putc ('\r'); /* Amiga file names are at most 30 chars. */
        LineCount++;

        /* Pause every screen full, the Apple user will send back
           a 'D' to continue or anything else to abort the listing. */

        if (LineCount >= 20 && i + 1 < FileCount)
        {
            LineCount = 0;
            comm_flush (); /* Throw out accumulated noise */
            comm_putc ('\0'); /* End of page. */
            comm_putc ('\1'); /* Yes, there's more. */

            printf ("Waiting for end of page acknowledgement (Control-C to abort).\n");

            while (1) /* Wait until we get a response. */
            {
                if (comm_avail ())
                {
                    if ((comm_getc ()) == 0)
                    {
                        printf ("Aborted by Apple user.\n");
                        return; /* Apple user stopped the listing. */
                    }
                    else /* Usually a 'D' but can be anything. */
                        break; /* Continue on with listing files. */
                }
                else
                {
                    Delay (10);
                    if (SetSignal (0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
                    {
                        printf ("Early stop by Amiga user.\n");
                        return;
                    }
                }
            }
        }
    }

    comm_putc (0); /* End of page. */
    comm_putc (0); /* No more pages. */
    comm_flush ();
    printf ("Directory listing finished.\n");
    return;
}



/******************************************************************************
 * Main send routine.  Open the disk image file and send the data from it.
 */

void send_disk()
{
    char fname[256];
    unsigned char buffer[28672];
    FILE *f;
    int part, track, sector, report;
    time_t tstart, tend;
    int minutes, seconds, cps;

    if (!getfname( fname ))
        return;

    printf ("Sending file \"%s\" (Control-C to abort).\n", fname);

    f = fopen( fname, "rb" );
    if( !f ) {
        comm_putc( 26 );        /* can't open */
        printf ("ERROR: File \"%s\" can't be opened for input.\n", fname );
        return;
    }

    if (fseek (f, 0, SEEK_END) == -1)
    {
        printf ("Can't seek to end of file \"%s\".\n", fname);
        return;
    }
    if (ftell (f) != 143360L ) {
        comm_putc( 30 );        /* not a 140k image */
        printf ("ERROR: File \"%s\" is not a 140K disk image.\n", fname );
        return;
    }
    fseek (f, 0, SEEK_SET); /* Back to the beginning. */
    comm_putc( 0 );     /* File is now ready */

    time( &tstart );
    while( comm_getc() != ACK )
        printf ("ERROR: Bad header byte received.\n" );
    for( part=0; part<5; part++ ) {
        fread( buffer, 1, 28672, f );
        for( track=0; track<7; track++ ) {
            printf ("Sending track $%02X: ", track + part * 7);
            for( sector=15; sector>=0; sector-- ) {
                printf ("%1X", sector );
                fflush (stdout);
                if (!send_sector( buffer, part, track, sector ))
                {
                    report = 1; /* Signal that an error happened. */
                    goto ErrorExit;
                }
            }
            printf ("\n");
        }
    }
    report = comm_getc();
ErrorExit:
    fclose( f );
    time( &tend );

    seconds = (int)difftime( tend, tstart );
    if (seconds > 0)
        cps = (int)(143360L / seconds);
    else
        cps = 0;
    minutes = seconds/60;
    seconds = seconds%60;
    if( report )
        printf("File \"%s\" sent WITH ERRORS in %d:%02d (%d cps).\n",
            fname, minutes, seconds, cps );
    else
        printf("File \"%s\" sent successfully in %d:%02d (%d cps).\n",
            fname, minutes, seconds, cps );
}



/******************************************************************************
 * Send a sector with RLE compression.  Returns TRUE if successful.  Keeps
 * on trying to send it until successful or the user aborts.
 */

int send_sector( unsigned char *buffer, int part, int track, int sector )
{
    int byte, ok;
    unsigned short crc;
    unsigned char data, prev, newprev;
    unsigned char *thissector = buffer+(track<<12)+(sector<<8);

    do {
        prev = 0;
        for( byte=0; byte<256; ) {
            newprev = thissector[byte];
            data = newprev - prev;
            prev = newprev;
            comm_putc( data );
            if( data )
                byte++;
            else {
                while( byte<256 && thissector[byte] == newprev )
                    byte++;
                comm_putc( byte & 0xFF );       /* 256 becomes 0 */
            }
        }
        crc = do_crc( thissector, 256 );
        comm_putc( crc & 0xFF );
        comm_putc( crc >> 8 );

        while (1) /* Wait until we get a response. */
        {
            if (comm_avail ())
            {
                ok = comm_getc();
                break;
            }
            else
            {
                Delay (1);
                if (SetSignal (0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
                {
                    printf ("\nEarly stop by Amiga user.\n");
                    return FALSE;
                }
            }
        }

        if( ok != ACK ) {
            printf( "\nTrack $%02X, sector $%02X: bad CRC.\n",
            7*part+track, sector );
        }
    } while( ok != ACK );

    return TRUE;
}



/******************************************************************************
 * Main receive routine.
 */

void receive_disk()
{
    char fname[256];
    unsigned char buffer[28672];
    FILE *f;
    int part, track, sector, report;
    time_t tstart, tend;
    int minutes, seconds, cps;

    if (!getfname( fname ))
        return;

    printf ("Receiving file \"%s\", Control-C to abort.\n", fname);

    if( !access( fname, 0 ) ) {
        comm_putc( 28 );        /* File exists */
        printf ("ERROR: File \"%s\" already exists.\n", fname );
        return;
    }
    if( (f = fopen( fname, "wb" )) == NULL ) {
        comm_putc( 26 );        /* Can't open */
        printf ("ERROR: File \"%s\" can't be opened for output.\n", fname );
        return;
    }
    for( track=0; track<35; track++ ) {
        if( fwrite( buffer, 1, 4096, f ) != 4096 ) {
            comm_putc( 30 );    /* Disk full */
            printf ("ERROR: File \"%s\" doesn't fit on disk.\n", fname );
            fclose( f );
            return;
        }
    }
    rewind( f );
    comm_putc( 0 );         /* File is now ready */

    time( &tstart );
    while( comm_getc() != ACK )
        printf ("ERROR: Bad header byte received.\n");
    for( part=0; part<5; part++ ) {
        for( track=0; track<7; track++ ) {
            printf ("Receiving track $%02X: ", track + part * 7);
            for( sector=15; sector>=0; sector-- ) {
                printf ("%1X", sector );
                fflush (stdout);
                if (!receive_sector( buffer, part, track, sector ))
                {
                    report = 1;
                    goto ErrorExit;
                }
            }
            printf ("\n");
        }
        fwrite( buffer, 1, 28672, f );
    }
    report = comm_getc();
ErrorExit:
    fclose( f );
    time( &tend );

    seconds = (int)difftime( tend, tstart );
    if (seconds > 0)
        cps = (int)(143360L / seconds);
    else
        cps = 0;
    minutes = seconds/60;
    seconds = seconds%60;
    if( report )
        printf ("File \"%s\" received WITH ERRORS in %d:%02d (%d cps).\n",
            fname, minutes, seconds, cps );
    else
        printf ("File \"%s\" received successfully in %d:%02d (%d cps).\n",
            fname, minutes, seconds, cps );
}



/******************************************************************************
 * Receive a sector with RLE compression.  Returns TRUE if successful, FALSE
 * if the user aborted.
 */

int receive_sector( unsigned char *buffer, int part, int track, int sector )
{
    int byte;
    unsigned short received_crc, computed_crc;
    unsigned char data, prev, crc1, crc2;
    unsigned char *thissector = buffer+(track<<12)+(sector<<8);

    do {
        prev = 0;
        for( byte=0; byte<256; )
        {
            while (1) /* Wait until we get a response. */
            {
                if (comm_avail ())
                {
                    data = comm_getc();
                    if( data ) {
                        prev += data;
                        thissector[byte++] = prev;
                    }
                    else {
                        data = comm_getc();
                        do {
                            thissector[byte++] = prev;
                        } while( byte<256 && byte!=data );
                    }
                    break;
                }
                else
                {
                    Delay (1);
                    if (SetSignal (0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
                    {
                        printf ("\nEarly stop by Amiga user.\n");
                        return FALSE;
                    }
                }
            }
        }
        crc1 = comm_getc();
        crc2 = comm_getc();

        received_crc = crc1 + 256*crc2;
        computed_crc = do_crc( thissector, 256 );
        if( received_crc != computed_crc ) {
            printf ("\nTrack %02X, sector %02X: bad CRC"
                " (expected %04X, got %04X).\n",
                7*part+track, sector, computed_crc, received_crc );
            comm_putc( NAK );
        }
    } while( received_crc != computed_crc );
    comm_putc( ACK );
    return TRUE;
}



/******************************************************************************
 * Receive a null-terminated Amiga filename from the Apple.  Returns TRUE
 * if successful, FALSE if aborted by the user or the name is too long.
 */

int getfname( char *fname )
{
    int i;

    printf ("Receiving file name.\n");

    for( i=0; i<256; i++ )
    {
        while (1) /* Wait until we get a response. */
        {
            if (comm_avail ())
            {
                fname[i] = comm_getc() & 0x7F;
                if( !fname[i] )
                    return TRUE; /* End of string NUL received. */
                break;
            }
            else
            {
                Delay (1);
                if (SetSignal (0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
                {
                    printf ("Early stop by Amiga user while getting file name.\n");
                    return FALSE;
                }
            }
        }
    }
    return FALSE; /* Name is too long. */
}



/******************************************************************************
 * Text printed when called with no arguments.
 */

void give_help (void)
{
    puts( "\n"
          "Apple Disk Transfer 1.22 -- December 4th, 1995 - 1999.\n"
          "By Paul Guertin (pg@sff.net).  Distribute freely.\n"
          "\n"
          "Ported to the Amiga by Alexander G. M. Smith\n"
          "(agmsmith@achilles.net and several other places).\n"
          "\n"
          "Compiled on " __DATE__ " at " __TIME__ ", Amiga version info:\n"
          "$VER: AdtAm 1.22 " __AMIGADATE__ " ($Id: AdtAm.c,v 1.8 2000/02/08 16:27:23 AGMS Exp $)\n"
          "\n"
          "This program transfers a 16-sector Apple II disk\n"
          "to a 140k binary file and back.  The file format\n"
          "is compatible with Randy Spurlock's APL2EM emulator\n"
          "and the Apple2000 emulator on the Amiga.\n"
          "\n"
          "Syntax: AdtAm [unitnum] [baudrate] [serial.device]\n"
          "Example: AdtAm 19200\n"
          "\n"
          "See MSDOS/readme.txt file for details.\n");
}


void make_crctable()
/* Fill the crctable[] array needed by do_crc */
{
    int byte, bit;
    unsigned short crc;

    for( byte=0; byte<256; byte++ ) {
        crc = byte<<8;
        for( bit=0; bit<8; bit++ )
            crc = (crc & 0x8000 ? (crc<<1)^0x1021 : crc<<1);
        crctable[byte] = crc;
    }
}


unsigned short do_crc( unsigned char *ptr, int count )
/* Return the CRC of ptr[0]..ptr[count-1] */
{
    unsigned short crc = 0;

    while( count-- )
        crc = (crc<<8) ^ crctable[(crc>>8) ^ *ptr++];
    return crc;
}


void touppers (char *String)
{
  while (*String != 0)
  {
    *String = toupper (*String);
    String++;
  }
}
