/* FILEDEMO.C - Performs various file operations using DOS functions.
 * FILEDEMO demonstrates how to interface C and assembly procedures,
 * which in turn demonstrate various assembly-language instructions,
 * DOS calls, and BIOS interrupts.
 *
 * FILEDEMO.EXE is built from the following files:
 *    FILEDEMO.C - Main program
 *    FILE.ASM   - Assembly procedures for FILEDEMO
 *    COMMON.ASM - Assembly procedures shared by other example programs
 *    DEMO.INC   - Assembly include file with macros and structure declarations
 *    DEMO.H     - C include file with prototypes and structure declarations
 *
 * Procedures:  ClearBox             ChangeDrive          GetDiskSize
 *              GetVidConfig         GetVer               GetCurDir
 *              GetCurDrive          CopyFile             DelFile
 *              MakeDir              RemoveDir            ChangeDir
 *              GetAttribute         SetAttribute         RenameFile
 *              GetFileTime          FindFirst            FindNext
 *              UniqueFile           OpenFile             CloseFile
 *              ReadFile             SetCurPos            StrCompare
 *              StrFindChar          ReadCharAttr
 */

#include "demo.h"
#include <string.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

/* Function prototypes */
char *get_spec( int n );
int list_dir( char *spec, int disp_attr );
void press( void );

/* Constants */
#define MAXSEARCH 80
#define MAXSPEC   64
#define BUFFSIZE  31 * 1024

/* Global variables */
struct FILEINFO file;            /* Structure for FindFirst info */
char source[64];                 /* Buffer for source file/path  */

main()
{
    static char text[MAXSEARCH];  /* Pointer to buffer for search text  */
    static char date_time[19];    /* Receive file date and time         */
    int err, mode, handle, len;   /* Codes, file handle, bytes read     */
    int row, col, ch;             /* Cursor coordinates, kb character   */
    int i, j, attr;               /* Index variables, file attribute    */
    int disp_attr;                /* Display attribute                  */
    char *spec, *ptr, *buffer;    /* File spec, char match, read buffer */
    long dsize, disk_use;         /* Disk size and usage                */
    struct DISKSTAT disk;         /* Structure for disk size params     */

    static char copy_msg[] =
    {
        "Files can be copied or moved in 2 different modes:\n" \
        "      0  -  overwrite target file if it exists\n" \
        "      1  -  abort if target file exists\n\n" \
        "Mode 1 is supported only with DOS versions 3.0 or higher.\n"
    };
    static char move_msg[] =
    {
        "Quick Move uses DOS function 56h (Rename File) to effectively " \
        "move a file from\none directory to another directory on the " \
        "same drive.  It copies the entry\nfrom the source directory to " \
        "the target directory, but does not physically\ncopy the file.\n\n" \
        "Source and target specifications must be given in full, " \
        "including filenames,\neven if the names are the same."
    };
    static char grep_msg[] =
    {
        "The Find Text option uses the StrFindChar and StrCompare procedures " \
        "to locate\na text string within specified files, like Unix's " \
        "\"grep\" command.  Find Text\nis limited to case-sensitive searches " \
        "within the current directory.\n\nEnter the desired search string " \
        "without quotation marks.  When specifying the\nfilename, use " \
        "wildcard characters to expand the search to a group of files --\n" \
        "for example, \"*.*\" searches all files within the current " \
        "directory, \"*.bat\"\nlimits the search to batch files, and so forth."
    };
    static char attr_msg[] =
    {
        "\t\t\t1    normal      \n" \
        "\t\t\t2    read-only   \n" \
        "\t\t\t3    hidden      \n" \
        "\t\t\t4    system      \n" \
        "\t\t\t     volume      \n" \
        "\t\t\t     subdirectory\n" \
        "\t\t\t5    archive     \n"
    };

    GetVidConfig();
    ReadCharAttr( &disp_attr );
    clear_scrn( disp_attr, 0, 24 );
    SetCurPos( 8, 0 );
    puts( "Welcome to the FILEDEMO program.\n\n\nThis program is meant " \
          "to encourage experimentation while demonstrating how to\naccess " \
          "DOS from assembly-language procedures.  As a safety precaution, " \
          "however,\nwe suggest you DO NOT experiment with files that " \
          "cannot easily be replaced." );
    press();

    do
    {
        /* Display current drive and directory */
        clear_scrn( disp_attr, 0, 24 );
        SetCurPos( 0, 0 );
        printf( "Current Directory:  %c:\\", (char)(GetCurDrive() + 'A') );
        GetCurDir( source );
        puts( source );

        /* Display DOS version */
        SetCurPos( 1, 0 );
        printf( "DOS Version:        %2.1f", ( (float) GetVer() ) / 100 );

        /* Display disk statistics for current drive */
        SetCurPos( 0, 58 );
        GetDiskSize( 0, &disk );
        dsize = (long)disk.bytes * disk.sects * disk.total;
        disk_use = (long)(disk.total - disk.avail) * disk.bytes * disk.sects;
        printf( "Disk Size: %6lu K", dsize / 1024 );
        SetCurPos( 1, 58 );
        printf( "Disk Use:  %6lu K", disk_use / 1024 );

        /* Display menu and poll for keystroke */
        clear_scrn( disp_attr, 2, 23 );
        SetCurPos( 5, 0 );
        puts( " \t               ***      FILE " \
              "Demonstration Program      ***" );
        SetCurPos( 7, 0 );
        puts( " \tA  List Directory       \t\tH  Get/Set File Attribute" );
        puts( " \tB  Copy File            \t\tI  Get File Date and Time" );
        puts( " \tC  Move File            \t\tJ  Rename File" );
        puts( " \tD  Make Subdirectory    \t\tK  Delete File" );
        puts( " \tE  Remove Subdirectory  \t\tL  Create Unique File" );
        puts( " \tF  Change Default Drive \t\tM  Quick Move" );
        puts( " \tG  Change Directory     \t\tN  Find Text" );
        printf( "\n\n\tSelect an option, or press ESC to quit: " );
        ch = getch();
        switch( (ch = toupper( ch )) )
        {
            /* List first 60 files in specified directory */
            case 'A':
                err  = list_dir( get_spec( 1 ), disp_attr );
                if( !err )
                    press();
                break;

            /* Copy or Move File according to requested mode:
             *          0 = overwrite target
             *          1 = abort if target exists
             * If Move requested, delete source file after copy.
             */
            case 'B':
            case 'C':
                clear_scrn( disp_attr, 2, 17 );
                SetCurPos( 9, 0 );
                puts( copy_msg );
                mode = -1;
                while( (mode < 0)  ||  (mode > 1) )
                {
                    SetCurPos( 16, 0 );
                    printf( "Enter copy mode:  " );
                    mode = (int)(getche() - '0');
                }
                spec = get_spec( 2 );                   /* Get source     */
                strcpy( source, spec );                 /* Save in buffer */
                spec = get_spec( 3 );                   /* Get target     */
                err  = CopyFile( mode, source, spec );
                if( (ch == 'C')  &&  !err )
                    err = DelFile( source );
                break;

            /* Make Directory */
            case 'D':
                err = MakeDir( get_spec( 1 ) );
                break;

            /* Remove Directory */
            case 'E':
                err = RemoveDir( get_spec( 1 ) );
                break;

            /* Change Default Drive */
            case 'F':
                SetCurPos( 18, 0 );
                printf( "Enter new drive letter:  " );
                ch = getch();
                ch = toupper( ch );
                ChangeDrive( ch );
                err = 0;
                break;

            /* Change Directory */
            case 'G':
                err = ChangeDir( get_spec( 1 ) );
                break;

            /* Get and Set File Attributes */
            case 'H':
                strcpy( source, get_spec( 3 ) );
                if( (err = GetAttribute( source )) != -1 )
                {
                    attr = err;
                    if( !attr )
                        attr_msg[6] = '*';
                    else
                        attr_msg[6] = ' ';
                    for( j = 1, i = 27; j <= 32; j <<= 1, i+= 21 )
                    {
                        attr_msg[i] = ' ';
                        if( attr & j )
                            attr_msg[i] = '*';
                    }
                    err = 0;
                    clear_scrn( disp_attr, 2, 17 );
                    SetCurPos( 7, 0 );
                    puts( attr_msg );
                    printf( "\n\nToggle attribute bits by selecting 1-5, " \
                            "or any other key to exit:  " );
                    mode = (int)( getch() - '0' );
                    if( (mode > 0)  &&  (mode < 6) )
                    {
                        switch( --mode )
                        {
                            case 0:
                                attr = 0;
                                break;

                            case 1:
                            case 2:
                            case 3:
                                attr = attr ^ (1 << (--mode) );
                                break;

                            case 4:
                                attr = attr ^ 32;
                        }
                        err = SetAttribute( attr, source );
                    }
                }
                break;

            /* Get File Date and Time */
            case 'I':
                if( (handle = OpenFile( 0, get_spec( 3 ) )) == -1 )
                    err = 1;
                else
                    err = 0;
                if( !err )
                {
                    if( !(err = GetFileTime( handle, date_time )) )
                    {
                        clear_scrn( disp_attr, 2, 17 );
                        SetCurPos( 12, 10 );
                        printf( "File's date and time stamp:  %s", date_time );
                        CloseFile( handle );
                        press();
                    }
                }
                break;

            /* Rename File */
            case 'J':
                strcpy( source, get_spec( 2 ) );
                err = RenameFile( source, get_spec( 3 ) );
                break;

            /* Delete File */
            case 'K':
                err = DelFile( get_spec( 3 ) );
                break;

            /* Create File with Unique Name */
            case 'L':
                strcpy( source, get_spec( 1 ) );
                handle = UniqueFile( 0, source );   /* Normal file attr = 0 */
                if( handle >= 0 )
                {
                    printf( "\n\nDOS creates file %s", source );
                    press();
                    err = 0;
                }
                else err = 1;
                break;

            /* Quick Move from one directory to another */
            case 'M':
                clear_scrn( disp_attr, 2, 17 );
                SetCurPos( 8, 0 );
                puts( move_msg );
                strcpy( source, get_spec( 2 ) );
                err = RenameFile( source, get_spec( 3 ) );
                break;

            /* Search files for specified text */
            case 'N':
                clear_scrn( disp_attr, 2, 17 );
                buffer = (char *) malloc( BUFFSIZE + 1 );
                if( buffer == NULL )
                {
                    SetCurPos( 12, 26 );
                    puts( "Insufficient memory for option" );
                    err = 1;
                    break;
                }
                SetCurPos( 7, 0 );
                puts( grep_msg );
                SetCurPos( 18, 0 );
                printf( "Enter search text:  " );
                GetStr( text, MAXSEARCH );

                /* Find first data file. */
                if( err = FindFirst( 0, get_spec( 3 ), &file ) )
                {
                    clear_scrn( disp_attr, 2, 17 );
                    SetCurPos( 12, 24 );
                    puts( "No files found matching specification" );
                }

                /* If file found, initialize screen coordinates and
                 * open file for reading.
                 */
                else
                {
                    clear_scrn( disp_attr, 2, 17 );
                    row = 6;
                    col = 0;
                    do
                    {
                        if( (handle = OpenFile( 0, file.filename )) != -1 )
                        {

                            /* If file opened successfully, read a block
                             * of BUFFSIZE bytes. If end-of-file encountered
                             * (number of bytes read not equal to BUFFSIZE)
                             * or read error, set error flag to break loop.
                             * Terminate block with a NULL character to 
                             * make it an ASCIIZ string.
                             */
                            err = 0;
                            while( !err )
                            {
                                len = ReadFile( handle, BUFFSIZE, buffer );
                                if( (len == 0)  ||  (len != BUFFSIZE) )
                                    ++err;
                                ptr = buffer;
                                *( ptr + len ) = 0;

                                /* Search block for first character in text */
                                while( spec = StrFindChar( text[0], ptr, 0 ) )
                                {

                                    /* If initial character found, compare
                                     * remaining characters in search text.
                                     * If all characters match, display file
                                     * name and break out of loop.
                                     */
                                    ptr = StrCompare( ++spec, &text[1],
                                          (strlen( text ) - 1) );
                                    if( !ptr )
                                    {
                                        SetCurPos( row++, col );
                                        puts( file.filename );
                                        if( row == 16)
                                        {
                                            row  = 6;
                                            col += 20;
                                        }
                                        err  = 1;
                                        break;
                                    }
                                }
                            }
                            CloseFile( handle );
                        }
                        else
                        {
                            err = 1;
                            break;
                        }
                    } while( !FindNext( &file ) );

                    if( (row == 6)  &&  (col == 0) )
                    {
                        SetCurPos( 12, 22 );
                        puts( "Text not found in specified file(s)" );
                    }
                    press();
                    err = 0;
                }
                free( buffer );             /* Free allocated block */
                break;

            default:
                continue;
        }

        if( err )
        {
            clear_scrn( disp_attr, 24, 24 );
            SetCurPos( 24, 0 );
            printf( "***  Error  ***\a" );
            press();
        }

    } while( ch != ESCAPE );            /* Exit if ESC key pressed    */

    clear_scrn( disp_attr, 0, 24 );     /* Clear screen before exit   */
    SetCurPos( 23, 0 );                 /*   and set cursor to bottom */
    return( 0 );
}


/* get_spec - Prompts for file or path specifications.
 *
 * Params:  n - Prompt number
 *
 * Return:  Pointer to specification
 */

char *get_spec( int n )
{
    static char spec[MAXSPEC];

    switch( n )
    {
        case 1:
            SetCurPos( 18, 0 );
            printf( "Enter path:  " );
            break;
        case 2:
            SetCurPos( 18, 0 );
            printf( "Enter source file:  " );
            break;
        case 3:
            SetCurPos( 20, 0 );
            printf( "Enter target file:  " );
            break;
    }
    GetStr( spec, MAXSPEC );
    if ( *spec == '\0' && n == 1 )
    {
        strcpy ( spec, "\\" );      /* Use current directory for <return> */
        strcat ( spec, source );
    }
    return spec;
}


/* list_dir - Lists specified directory by invoking DOS Function 4Eh (Find
 * First Matching Entry), then DOS Function 4Fh (Find Next Matching Entry).
 *
 * Params:  spec - Pointer to path specification
 * 
 * Return:  err  - Error code returned from FindFirst procedure:
 *                 0 = successful         1 = error
 */

int list_dir( char *spec, int disp_attr )
{
    int row, col;
    int err, end;
    clear_scrn( disp_attr, 2, 23 );

    if ( spec[strlen(spec) - 1] == '\\' )       /*Remove trailing backslash*/
         spec[strlen(spec) - 1] = '\0';
    strcat( spec, "\\*.*" );
    err = FindFirst( 16, spec, &file ); /* Find directories, normal files */
    if( !err )
    {
        for( end = 0, col = 0; (col < 80)  &&  (end == 0); col += 22 )
        {
            for( row = 5; (row < 20)  &&  (end == 0); row++ )
            {
                SetCurPos( row, col );
                puts( file.filename );
                end = FindNext( &file );
            }
        }
    }
    return err;
}


/* press - Prompt for keyboard signal to continue.
 *
 * Params:  None
 */

void press( void )
{
    SetCurPos( 24, 49 );
    printf( ". . . press a key to continue" );
    SetCurPos( 24, 47 );
    getch();
}
