/*
    Find multiple copies of the same file on a single disk
    drive. Compiled under Microsoft C 6.0 using:
        cl /Fs /W4 /Ox /AC no_dup.c
    Compiles without modifications undder TURBO C integrated
    environment.
    In both cases requires compact memory model libraries.
*/

#include "no_dup.h"

Flag first_call = True;

GlobalOpt globalopt = { False, False, 0, 0L };

int main( int argc, char **argv )
    {
    DirEntry *current_file, *next_file;
    DirList *path, *root, *new_path;
    char current_path[MAX_PATH], file_spec[MAX_FLEN];
    PtrList *ptrbase, *ptrlist;
    Flag duplicate = False;

    for( ; argc > 1; argc-- )
        {
        if(argv[argc-1][1] == 'f' || argv[argc-1][1] == 'F')
            globalopt.file = True;
        else if( argv[argc-1][1] == 'd'
                || argv[argc-1][1] == 'D' )
            globalopt.dir  = True;
        else
            fprintf( stderr,
                    "Invalid command line switch: %s\n",
                    argv[argc-1] );
        }

    strcpy( file_spec, "*.*" );
    current_file = NULL;
    (void) getcwd( current_path,  MAX_PATH );
    fprintf( stderr, "%s\n", VERSION );
    printf( "Processing %s\n", current_path );
    path = make_path( current_path, "", NULL );
    new_path = root = path;
    if( (ptrbase = malloc( sizeof( PtrList ) * MAX_FILES ))
            == NULL )
        {
        fprintf( stderr, "Insufficient near memory.\n" );
        exit( EXIT_FAILURE );
        }
    ptrlist = ptrbase;

    while( (path = get_path( root ) ) != NULL )
        {
        strcpy( current_path, path->pathname );
        strcat( current_path, file_spec );
        first_call = True;

        while( (current_file = get_direntry(current_path))
                != NULL )
            {
            current_file->path = path->pathname;
            if( current_file->attrib & FA_DIREC )
                {
                new_path = make_path( path->pathname,
                        current_file->name, new_path );
                }
            else
                {
                ++globalopt.file_count;
                globalopt.total_file_size
                        += current_file->size;
                if( globalopt.file_count == MAX_FILES  )
                    {
                    fprintf( stderr,
                        "Too many files\n"
                        "Program is limited to %u files.\n",
                        MAX_FILES );
                    exit( EXIT_FAILURE );
                    }
                *ptrlist = ( char * ) current_file;
                ++ptrlist;
                }
            }
            path->dir_processed = True;
        }
        *ptrlist = NULL;
        printf( "Processed %d files occupying %lu bytes.\n",
                globalopt.file_count,
                globalopt.total_file_size );

        qsort( (void *) ptrbase,
                (size_t) globalopt.file_count,
                (size_t) sizeof( PtrList ), name_comp );

        if( globalopt.file )    /* optionally list files */
            {
            printf( SEPARATOR );
            printf( "Listing of all files below %s\n",root);
            for( ptrlist = ptrbase; *ptrlist; ptrlist++ )
                fprint_direntry( stdout,
                        (DirEntry *) *ptrlist );
            }

        printf( SEPARATOR );
        printf( "Duplicate files below %s\n", root );
        for( ptrlist = ptrbase, duplicate = False;
                *ptrlist; ptrlist++ )
            {
            current_file = ( DirEntry * ) *ptrlist;
            next_file    = ( DirEntry * ) *(ptrlist+1);
            if( !strcmp(current_file->name,next_file->name))
                {
                duplicate = True;
                fprint_direntry( stdout, current_file );
                }
            else if( duplicate == True )
                {
                /* print the last file in a group */
                /* otherwise we'll miss it */
                current_file = ( DirEntry * ) *ptrlist;
                fprint_direntry( stdout, current_file );
                printf( SEPARATOR );
                duplicate = False;
                }
            }

        return EXIT_SUCCESS;
    }

/*
    get file info from DOS directory and allocate memory
*/
DirEntry *get_direntry( char *path )
    {
    static struct ffblk file_info;
    DirEntry *new_entry;

    if( first_call == True )
        {
        if( findfirst( path, &file_info, FA_DIREC ) )
            return NULL;
        first_call = False;
        }
    else
        {
        if( findnext( &file_info ) )
            {
            return NULL;
            }
        }
    if( (new_entry = malloc(sizeof(DirEntry))) == NULL )
        {
        fprintf( stderr, "Insufficient far memory.\n" );
        exit( EXIT_FAILURE );
        }

    strcpy( new_entry->name, file_info.ff_name );
    new_entry->attrib = file_info.ff_attrib;
    if( new_entry->attrib & FA_DIREC )
        new_entry->dir_processed = False;
    new_entry->time   = file_info.ff_ftime;
    new_entry->date   = file_info.ff_fdate;
    new_entry->size   = file_info.ff_fsize;

    return new_entry;
    }

/*
    build a directory path by appending subdir to basedir
*/
DirList *make_path( char *basedir, char *subdir,
        DirList *prev )
    {
    DirList *new_dir;

    if( (new_dir = malloc( sizeof( DirList ) ) ) == NULL )
        {
        fprintf( stderr, "Insufficient near memory.\n" );
        exit( EXIT_FAILURE );
        }
    strcpy( new_dir->pathname, basedir );
    strcat( new_dir->pathname, subdir );
    if( *(new_dir->pathname + strlen(new_dir->pathname) - 1)
            != '\\' )
        strcat( new_dir->pathname, "\\" );
    if( strcmp( subdir, "." ) && strcmp( subdir, ".." ) )
        {
        /* only "real" directories meet the condition */
        new_dir->dir_processed = False;
        if( globalopt.dir )
            printf( "Directory:%s\n", new_dir->pathname );
        }
    else
        new_dir->dir_processed = True;
    if( prev )
        {
        new_dir->prev = prev;
        new_dir->next = NULL;
        prev->next = new_dir;
        }
    else
        {
        new_dir->prev = NULL;
        new_dir->next = NULL;
        }

    return new_dir;
    }

/*
    find an unprocessed directory
*/
DirList *get_path( DirList *root )
    {
    DirList *path;

    path = root;
    while( path->next && (path->dir_processed == True) )
        {
        path = path->next;
        }
    if( path->dir_processed == True)
        return NULL;
    else
        return path;
    }

/*
    DirEntry comparison function
*/
int name_comp( const void *p1, const void *p2 )
    {
    PtrList  *ptr1, *ptr2;
    DirEntry *f1, *f2;
    int status;

    ptr1 = (PtrList *) p1;
    ptr2 = (PtrList *) p2;
    f1   = (DirEntry *) *ptr1;
    f2   = (DirEntry *) *ptr2;

    if( status = strcmp( f1->name, f2->name ) )
        return( status );
    else if( status = f1->date - f2->date )
        return( status );
    else if( status = f1->time - f2->time )
        return( status );
    else
        return( strcmp( f1->path, f2->path  ) );
    }

/*
    print file information to the ouptut stream
*/
void fprint_direntry( FILE *fout, DirEntry *current_file )
    {
    char date_buf[10], time_buf[10];

    fprintf( fout, "%-14s %6ld %c%c %s %s %s\n",
            current_file->name,
            current_file->size,
            (current_file->attrib & FA_RDONLY ) ? 'R' : '.',
            (current_file->attrib & FA_ARCH   ) ? 'A' : '.',
            datestr( current_file->date, date_buf ),
            timestr( current_file->time, time_buf ),
            current_file->path );
    }

/*
    The following functions are copied from QC 2.0 online
    help. These functions convert wr_time and wr_date into
    strings.
*/
char *timestr( unsigned t, char *buf )
{
    int h = (t >> 11) & 0x1f, m = (t >> 5) & 0x3f;
    sprintf( buf, "%2.2d:%02.2d", h, m );
    return buf;
}

char *datestr( unsigned d, char *buf )
{
    sprintf( buf, "%2.2d/%02.2d/%02.2d",
            (d >> 5) & 0x0f, d & 0x1f, (d >> 9) + 80 );
    return buf;
}
