/*****************************************************************************
**
**  Copyright 1997 by Ernest R. Schreurs.
**  All rights reserved.
**  Use of this source code is allowed under the following conditions:
**  You must inform people that you based your work on this stuff.
**  If you charge a fee in any form for your product, you must inform people
**  that this stuff is available free of charge.
**  Refer to the documentation for more information.
**
*****************************************************************************/
#define MODULE  "CAS2SIO.C"
/*****************************************************************************
**  NAME: CAS2SIO.C
**
**  Author            : Ernest R. Schreurs
**  Date              : May 1, 1997
**  Release           : 01.00
**
**  Description       : This program will output a file in the .cas format to
**                      the serial port.
**
*****************************************************************************/

/*****************************************************************************
==  INCLUDE FILES
*****************************************************************************/
#include <bios.h>               /* For serial port stuff                */
#include <dos.h>                /* For port stuff                       */
#include <conio.h>              /* For port stuff                       */
#include <stdio.h>              /* For printf() and gets()              */
#include <ctype.h>              /* For isalnum() and toupper()          */
#include <stdlib.h>             /* For the exit function                */
#include <string.h>             /* String and memory stuff              */
#include <time.h>               /* For msleep()                         */

/*****************************************************************************
==  DEFINED SYMBOLS
*****************************************************************************/
#ifndef FALSE
#define FALSE               0
#endif
#ifndef TRUE
#define TRUE                1
#endif

#ifndef NULL
#define NULL                0
#endif
#define PATH_LEN            128

#define BUF_LEN             80              /* fgets buffer length          */
#define SUCCESS             1               /* Success is non-zero          */
#define FAILURE             0               /* Failure is zero              */

/*****************************************************************************
==  MACRO DEFINITIONS
*****************************************************************************/
/*
**  Macro for casting stuff to requirements of stupid
**  standard library functions.
*/

#define FGETS( buf, buf_len, file_ptr )                                 \
    (void *)fgets( (char *)buf, (int)buf_len, (FILE *)file_ptr )

#define STRLEN( str )                                                   \
    strlen( (const char *)(str) )

/*
**  Macro for getting input from the terminal,
**  allowing the user to exit with either control Z or
**  inputting the string ^Z to indicate intention of
**  terminating the program.
*/
#define GET_BUF()                                                           \
{                                                                           \
    if ( FGETS( buf, BUF_LEN, stdin )  ==  NULL )                           \
    {                                                                       \
        printf( "Terminated by ^Z\n" );                                     \
        exit(0);                                                            \
    }                                                                       \
    if ( memcmp( buf, "^Z", 2 ) == 0 || memcmp( buf, "^z", 2 ) == 0  )      \
    {                                                                       \
        printf( "Terminated by ^Z\n" );                                     \
        exit(0);                                                            \
    }                                                                       \
}

/*
**  Macro to find out if a character is a valid path name character
*/
#define ISPATH(path_char)   (isalnum((path_char)) || (path_char) == '$' ||  \
                                                     (path_char) == '#' ||  \
                                                     (path_char) == '@' ||  \
                                                     (path_char) == '.' ||  \
                                                     (path_char) == '_' ||  \
                                                     (path_char) == ' ')

/*****************************************************************************
==  TYPE and STRUCTURE DEFINITIONS
*****************************************************************************/
typedef     unsigned char   bool;   /* Boolean value                        */
typedef     unsigned char   ubyte;  /* Exactly eight bits, unsigned         */
typedef     short           int16;  /* At least 16 bits, signed             */
typedef     unsigned short  uint16; /* At least 16 bits, unsigned           */
typedef     long            int32;  /* At least 32 bits, signed             */
typedef     unsigned long   uint32; /* At least 32 bits, unsigned           */

/*
**  Cassette file header.
*/

typedef struct
{
    ubyte       cas_record_id[4];       /* Cassette record type             */
    ubyte       cas_len_lo;             /* Record length low byte           */
    ubyte       cas_len_hi;             /* Record length high byte          */
    ubyte       cas_aux1;               /* Type dependant data              */
    ubyte       cas_aux2;               /* Type dependant data              */
    ubyte       cas_data[8192];         /* Data                             */
} cas_blk;

/*****************************************************************************
==  IMPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) VARIABLES
*****************************************************************************/
static FILE *   cas_file;               /* Cassette image file              */
static cas_blk  cas_rec;                /* The cassette record buffer       */
static int      com_port;               /* COM port to be used              */
static int      port;                   /* Port to address UART             */

/*****************************************************************************
==  EXPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  IMPORTED FUNCTIONS
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) FUNCTIONS
*****************************************************************************/
static void     set_baudrate( uint32 baudrate );
static void     usage( char * cmd );
static uint32   cas2sio( void );

/*****************************************************************************
==  EXPORTED FUNCTIONS
*****************************************************************************/
int                 main();                 /* Normal entry point to it all */

/*****************************************************************************
==  LOCAL ( HIDDEN ) FUNCTIONS
*****************************************************************************/

/*****************************************************************************
**  NAME:  cas2sio()
**
**  PURPOSE:
**      Read data from the .cas file and send it to the serial port.
**      The serial port should be connected to the SIO port of a classic Atari
**      by means of an interface.
**
**  DESCRIPTION:
**      This function will read the cassette records and output them.
**
**  INPUT:
**      Nothing.
**
**  OUTPUT:
**      Sends data to the serial port.
**      Returns SUCCESS if data converted successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       cas2sio( void )
{
    uint32          baudrate;               /* Current baudrate             */
    uint32          cas_len;                /* Number of bytes in record    */
    uint32          data_ndx;               /* Data index                   */
    uint32          prwt_cnt;               /* Length of PRWT               */


/*
**  Get the port address for the specified com port from
**  the BIOS parameter block starting at 0040:0000.
**  If the BIOS tells us the port does not exist, use the default
**  address, it will be up to the user to see if it works.
*/
    port = peek( (unsigned)0x0040, (unsigned)( (com_port - 1) * 2 ) );

    if( port == 0 )
    {
        switch( com_port )
        {
            case 1:
                port = 0x03F8;
                break;
            case 2:
                port = 0x02F8;
                break;
            case 3:
                port = 0x03E8;
                break;
            case 4:
                port = 0x02E8;
                break;
            default:
                port = 0x02F8;
                break;
        }
    }

/*
**  Initialize baudrate in case the file does not specify it itself.
*/
    baudrate = 600;
    set_baudrate( baudrate );

/*
**  Start reading records from the cassette image file.
**  First get the header to see what the length of the record is,
**  then read the rest of the record, if applicable.
*/
    while( 8 == fread( (char *)&cas_rec, (int)1, (int)8, cas_file ) )
    {
        cas_len = cas_rec.cas_len_hi * 256 + cas_rec.cas_len_lo;
        if( cas_len )
        {
            if( cas_len != fread( (char *)cas_rec.cas_data, (int)1, (int)(cas_len), cas_file ) )
                break;
        }

        if( memcmp( cas_rec.cas_record_id, "FUJI", 4 ) == 0 )
        {
            if( cas_len )
                printf( "%s\n", cas_rec.cas_data );
        } /* end if header record */

        if( memcmp( cas_rec.cas_record_id, "baud", 4 ) == 0 )
        {
            baudrate = (uint32)cas_rec.cas_aux2 * 256 + cas_rec.cas_aux1;
            printf("\nSetting baudrate to %ld baud.\n", baudrate );
            set_baudrate( baudrate );
        } /* end if baudrate record */

        if( memcmp( cas_rec.cas_record_id, "data", 4 ) == 0 )
        {
            prwt_cnt = (uint32)cas_rec.cas_aux2 * 256 + cas_rec.cas_aux1;
            printf("\nPeeeeeeeeep %ld milliseconds, baudrate %ld.\n", prwt_cnt, baudrate );
            msleep( (int)prwt_cnt );

            for( data_ndx = 0; data_ndx < cas_len; data_ndx++ )
            {

/*
**  Output the byte to the serial port.
*/
                while( ( inp( port + 5 ) & 0x20 ) == 0 )
                    ;   /* waiting for outputregister to become empty */
                outp( port, cas_rec.cas_data[data_ndx] );
            } /* end for all bytes in the record */
        } /* end if data record */
    } /* end while records left in the file */

    printf("\nEnd of tape.\n");

    return( SUCCESS );
}

/*****************************************************************************
**  NAME:  set_baudrate()
**
**  PURPOSE:
**      Set the baudrate of the com-port.
**
**  DESCRIPTION:
**      This function will set the currently active com-port to the
**      desired baudrate.
**
**  INPUT:
**      The desired baudrate.
**      The port is taken from the global variable.
**
**  OUTPUT:
**      Initializes the serial port.
**      Returns nothing.
**
*/

static void         set_baudrate( baudrate )
uint32              baudrate;               /* Baudrate                     */
{
    uint32          divisor;                /* Baudrate divisor value       */

/*
**  Convert baud rate to the divisor value.
*/
    divisor = 115200L / baudrate;

/*
**  Set baudrate to the desired value.  Meanwhile, setup the port for
**  8 databits, 1 startbit, 1 stopbit, and no parity.
*/
    outp( port + 3, inp( port + 3 ) | 0x083 );  /* set divisor latch    */
    outp( port + 0, divisor % 256 );            /* set divisor lsb      */
    outp( port + 1, divisor / 256 );            /* set divisor msb      */
    outp( port + 3, inp( port + 3 ) & 0x07F );  /* set divisor latch    */
    outp( port + 1, 0x00 );                     /* Disable interrupts   */
    outp( port + 4, 0x0F );                     /* Set RS232 lines      */

    return;
}

/*****************************************************************************
**  NAME:  usage()
**
**  PURPOSE:
**      Display the command line format for the program.
**
**  DESCRIPTION:
**      This function will explain the usage of the program to the user.
**      The program name is taken from the first command line argument.
**
**  INPUT:
**      - The address of the command line, containing the program name.
**
**  OUTPUT:
**      The usage is displayed on the terminal.
**      The function returns nothing.
**
*/

static void         usage( cmd )
char * cmd;                         /* Program name                     */
{
    char * whoami;      /* For searching program name in command line   */
    char * name;        /* Pointer to actual program name in command    */
    int    len;         /* Length of program name                       */
    int    found_dot;   /* Nonzero if we found a dot in the name        */

/*
**  Get program name and print usage message.
**  The complete pathname including extension is part of the first
**  argument as passed by the operating system.
*/
    for( whoami = cmd, len = 0, found_dot = 0; *whoami; whoami++ )
    {
        if( *whoami == '.' )
        {
            found_dot = 1;
            continue;
        }
        if( *whoami == '\\' )   /* if this was part of the path, */
        {
            name = whoami + 1;  /* record position */
            len = 0;            /* then restart counting length */
            found_dot = 0;
            continue;
        }
        if( *whoami == ' ' )    /* end of name found            */
            break;
        if( found_dot )         /* skip .exe or .com stuff      */
            continue;
        len++;                  /* Increment program name length */
    }

/*
**  Let me explain...
*/
    fprintf(stderr, "\nUsage: %.*s [cassette imagefile] [/1 | /2 | /3 | /4]\n", len, name);
    fprintf(stderr, "to output a .cas file to the serial port\n\n");
    fprintf(stderr, "cassette image file   a file containing an image of the data\n");
    fprintf(stderr, "                      from a tape for an Atari classic.\n");
    fprintf(stderr, "/1                    to use COM1:\n");
    fprintf(stderr, "/2                    to use COM2: (the default)\n");
    fprintf(stderr, "/3                    to use COM3:\n");
    fprintf(stderr, "/4                    to use COM4:\n\n");
    fprintf(stderr, "Refer to the documentation for more information.\n");

    return;
}

/*****************************************************************************
==  EXPORTED FUNCTIONS
*****************************************************************************/

/*****************************************************************************
**  NAME:  MAIN()
**
**  PURPOSE:
**      An entry point for testing or running this utility.
**
**  DESCRIPTION:
**      Prompt for the file to open and then go process it.
**
**  INPUT:
**      argc and argv.
**
**  OUTPUT:
**      Returns an int as it should.
**
*/
int                 main( argc, argv )
int                 argc;               /* Command line argument count  */
char              * argv[];             /* Command line argument ptrs   */
{
    ubyte           answer;                 /* Response to question         */
    uint32          arg_ndx;                /* Argument number index        */
    uint32          arg_no;                 /* Argument number              */
    uint32          wrk_ndx;                /* Work index                   */
    bool            end_of_str;             /* Null terminator seen?        */
    ubyte           input_path[PATH_LEN];   /* Input wave file spec         */
    ubyte           buf[BUF_LEN];           /* Buffer string                */
    ubyte           proceed;                /* Proceed with conversion      */

/*
**  Process command line arguments.
**  We do not treat the options switch as an argument.  It may be placed
**  anywhere on the command line.  So we have to count the arguments ourselves
**  so that we know what argument we are processing.
*/
    arg_no = 0;
    com_port = 2;

    for( arg_ndx = 1; arg_ndx < argc; arg_ndx++ )
    {

/*
**  If we encounter the options switch, process the options.
**  The options must start with a slash.
*/
        if( argv[arg_ndx][0] == '/' )
        {

            for( wrk_ndx = 0; argv[arg_ndx][wrk_ndx]; wrk_ndx++ )
            {

/*
**  If the user is confused, seeking help, she/he should read the * manual.
**  We can give them a hint though.
*/
                if( argv[arg_ndx][wrk_ndx] == '?' )
                {
                    usage( argv[0] );
                    exit( 0 );
                }

/*
**  The /1 option selects COM1:,
**      /2 option selects COM2:,
**      /3 option selects COM3:,
**      /4 option selects COM4:.
*/
                if( ( argv[arg_ndx][wrk_ndx] >= '1' ) &&
                    ( argv[arg_ndx][wrk_ndx] <= '4' ) )
                {
                    com_port = argv[arg_ndx][wrk_ndx] & 0x0F;
                    continue;
                }

/*
**  Ignore other options.
*/
                continue;
            } /* end for all characters after options switch */

/*
**  No further processing for the options switches.
*/
            continue;
        } /* end if options switch */
        arg_no++;

/*
**  First argument is the file spec.
*/
        if( arg_no == 1 )
        {
            for ( wrk_ndx = 0, end_of_str = FALSE;
                wrk_ndx < PATH_LEN; wrk_ndx++ )
            {
                if ( argv[arg_ndx][wrk_ndx] == '\0' ) /* End of argument string?            */
                    end_of_str = TRUE;
                if ( end_of_str )
                    input_path[wrk_ndx] = '\0';
                else
                    input_path[wrk_ndx] = toupper( argv[arg_ndx][wrk_ndx] );
            }

            cas_file = fopen( (char *)input_path, "rb" );
            if( cas_file == NULL )
            {
                fprintf(stderr, "Cannot open cassette image file %s\n", input_path);
                exit( 255 );
            }
        } /* end if file spec argument */
    } /* end for all command line arguments */

/*
**  If there is no filename on the command line ask for it.
*/
    if( arg_no == 0 )
    {

/*
**  Open hailing frequencies.
**  No command line arguments, so ask what it is we have to do.
*/
        printf( "\n\nClassic Atari cassette tape recorder emulator version April 27, 1997\n" );
        printf( "\n\nCopyright 1997 by Ernest R. Schreurs\n" );
        printf( "\n\nAll rights reserved\n" );

        while( TRUE )                       /* until terminated by control Z*/
        {
            printf( "\nEnter ^Z or hit Control Z to terminate\n" );

            do                              /* until .cas file entered    */
            {
                printf("\nEnter .cas file to be send to the Atari : ");
                GET_BUF();

                for ( wrk_ndx = 0, end_of_str = FALSE;
                    wrk_ndx < PATH_LEN; wrk_ndx++ )
                {
                    if ( wrk_ndx < BUF_LEN )
                    {
                        if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                            end_of_str = TRUE;
                        if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                            end_of_str = TRUE;
                        if ( end_of_str )
                            input_path[wrk_ndx] = '\0';
                        else
                            input_path[wrk_ndx] = toupper( buf[wrk_ndx] );
                    }
                }
            } while ( input_path[0] == ' ' );

            do                              /* until answer is 1, 2, 3 or 4 */
            {
                printf("\nWhat COM port should be used 1, 2, 3 or 4 : ");
                GET_BUF();
                answer = toupper( buf[0] );

/*
**  If blank, default is COM2:.
*/
                if ( answer == '\n' )
                    answer = '2';

            } while ( answer != '1' && answer != '2' &&
                      answer != '3' && answer != '4' );

            com_port = answer & 0x0F;

            do                              /* until answer is Y or N       */
            {
                printf("\nLoading file %s\n", input_path);
                printf("\nIs this correct Y)es or N)o : ");
                GET_BUF();
                proceed = toupper( buf[0] );

/*
**  If blank, default is correct
*/
                if ( proceed == '\n' )
                    proceed = 'Y';

            } while ( proceed != 'Y' && proceed != 'N' );

            if ( proceed == 'N' )
                continue;

            printf( "\nLoading data from cassette file.\n" );

            cas_file = fopen( (char *)input_path, "rb" );
            if( cas_file == NULL )
            {
                printf("Cannot open cassette image file\n");
                continue;
            }
            break;
        } /* end while need a valid filename */

    } /* end else if command line arguments */

/*
**  Start reading the image file and process it.
*/
    cas2sio();

    fclose( cas_file );

    return 0;
}

/*****************************************************************************
**  MODIFICATION HISTORY
**
**  DATE        BY   Description
**  ----------  ---  ---------------------------------------------------------
**  1997/02/23  ERS  Project start
**  1997/05/01  ERS  Release 1.00
**
*****************************************************************************/
