/**
  * Author: Tom Mutdosch
  * Makefile generator
  *
  * This program automatically generates a Makefile based on the files residing
  * in the current directory
  * 
  * $Id: makemake.c,v 1.5 1999/10/20 03:58:40 tommut Exp tommut $
  * $Log: makemake.c,v $
  * Revision 1.5  1999/10/20 03:58:40  tommut
  * added support for additional directories in Makefiles
  *
  * Revision 1.4  1999/10/10 20:17:50  tommut
  * *** empty log message ***
  *
  * Revision 1.3  1999/10/10 04:33:49  tommut
  * *** empty log message ***
  *
  */

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>

#define DEFAULT_DIR "/usr/local/etc/Makefile"

int hasExtension( char *str, char *extension );
void readDirectory( char *dir ); 
int getLine( FILE *fp, char *destination, int maxlen );
void processLine( char *destination, char *line ); 
int excludeFile( char *fileName );
int createMakefile( ); 
void printHelp(); 

char cFiles[256][256], cppFiles[256][256], hFiles[256][256],
     excludeFiles[256][256], makeLines[256][81], progName[256], directory[256];
int cIndex, cppIndex, hIndex, excludeIndex;
int cUpdate = 0, cppUpdate = 0, hUpdate = 0;

/**
  * read the current directory and store all C, cpp, c, and h files in memory 
  */
void readDirectory( char *dirName ) 
{
    DIR *dp;
    struct dirent *dir;
    char tmpDir[512];

    if ( ( strcmp( dirName, "." ) == 0 ) || 
         ( strcmp( dirName, "./" ) == 0 ) ) {   
        strcpy( tmpDir, "" );
    }
    else if ( dirName[strlen(dirName)-1] != '/' ) {
        sprintf( tmpDir, "%s%c", dirName, '/' );
    }
    else {
        sprintf( tmpDir, "%s", dirName );
    }
    
    if ( (dp = opendir( dirName ) ) == NULL ) {
        fprintf( stderr, "%s: %s", "Can not open current directory.\n", 
            dirName );
        return;
    }
    
    while ( (dir = readdir( dp ) ) != NULL ) {
        if ( dir->d_ino != 0 && !excludeFile( dir->d_name ) ) { 
            if ( hasExtension( dir->d_name, "c" ) ) {
                sprintf( cFiles[cIndex++], "%s%s", 
                    tmpDir, dir->d_name );
            }
            if ( ( hasExtension( dir->d_name, "C" ) ) || 
                ( hasExtension( dir->d_name, "cpp" ) ) ) {
                sprintf( cppFiles[cppIndex++], "%s%s", 
                    tmpDir, dir->d_name );
            }
            if ( hasExtension( dir->d_name, "h" ) ) {
                sprintf( hFiles[hIndex++], "%s%s", 
                    tmpDir, dir->d_name );
            }
        }
    }
}
    
/**
  * return 1 if str ends with extension, 0 otherwise 
  */
int hasExtension( char *str, char *extension ) 
{
    int ret = 0;
    char *tmp = (char *)strrchr( str, '.' );
    if ( tmp != NULL ) {
        if ( strcmp( tmp + 1, extension ) == 0 ) {
            ret = 1;
        }
    }
    return ret;
}

/**
  * read information from previous Makefile and write out the new one
  */
int createMakefile( ) 
{
    FILE *fp;
    char line[81];
    int num = 0, index = 0, i = 0;
    
    /** if no directory is given on command line */
    if ( strcmp( directory, "" ) == 0 ) {
        /** check for Makefile in current directory, first */
        if ( (fp = fopen( "Makefile", "r" )) == NULL ) {
            /** else, use default directory */
            if ( (fp = fopen( DEFAULT_DIR, "r" )) == NULL ) {
                perror( "Could not open Makefile for reading." );
                exit( 1 );
            }
        }
    }
    else if ( (fp = fopen( directory, "r" )) == NULL ) {
        perror( "Could not open Makefile for reading." );
        exit( 1 );
    }
    memset( line, '\0', 81 );
    while ( (num = getLine( fp, line, 81 ) ) != 0 ) {
        processLine( makeLines[index++], line );
        memset( line, '\0', 81 );
    }
    fclose( fp );

    /** now write out all of the new data to Makefile */
    if ( (fp = fopen( "Makefile", "w" )) == NULL ) {
        perror( "Could not open Makefile for writing." );
        exit( 1 );
    }
    for ( i = 0; i < index; i++ ) {
        fprintf( fp, "%s", makeLines[i] );
    }
    fclose( fp );
    return ( 1 );
}

/**
  * get a line up to maxlen - 1 or a '\n' and place into destination 
  */
int getLine( FILE *fp, char *destination, int maxlen ) 
{
    char in;
    int index = 0;
    
    while ( ( index < maxlen - 1 ) && ( (in = fgetc( fp ) ) != EOF ) && 
        in != '\n' ) { 
        destination[index++] = in;
    }
    if ( in == '\n' ) {
        destination[index] = in;
        index++;
    }
    destination[index] = '\0';
    return index;
}

/**
  * process lines of Makefile and enter new information, such as updated files
  */
void processLine( char *destination, char *line ) 
{
    int i;
    if ( ( strncmp( line, "CC", 2 ) ) == 0 ) {
        if ( cppIndex > 0 ) {
            strcpy( destination, "CC= g++\n" );
        }
        else {
            strcpy( destination, "CC= gcc\n" );
        }
    } 
    else if ( cUpdate && ( strncmp( line, "CFILES", 6 ) ) == 0 ) {
        strcpy( destination, "CFILES=" );
        for ( i = 0; i < cIndex; i++ ) {
            sprintf( destination, "%s %s", destination, cFiles[i] );
        }
        strcat( destination, "\n" );
    }
    else if ( cppUpdate && ( ( strncmp( line, "CPPFILES", 8 ) ) == 0 ) ) {
        strcpy( destination, "CPPFILES=" );
        for ( i = 0; i < cppIndex; i++ ) {
            sprintf( destination, "%s %s", destination, cppFiles[i] );
        }
        strcat( destination, "\n" );
    }
    else if ( hUpdate && ( strncmp( line, "HFILES", 6 ) ) == 0 ) {
        strcpy( destination, "HFILES=" );
        for ( i = 0; i < hIndex; i++ ) {
            sprintf( destination, "%s %s", destination, hFiles[i] );
        }
        strcat( destination, "\n" );
    }
    else if ( strncmp( line, "PROG", 4 ) == 0 ) {
        if ( strcmp( progName, "" ) == 0 ) { 
            strcpy( destination, line );
        }
        else { 
            sprintf( destination, "%s %s\n", "PROG=", progName );
        }
    }
    else {
        strcpy( destination, line );
    }
}

/**
  * return 1, if fileName is to be excluded from the Makefile, as per
  * commandline request
  */
int excludeFile( char *fileName ) 
{
    int i, ret = 0;
    for ( i = 0; i < excludeIndex; i++ ) {
        if ( !ret && (strcmp( fileName, excludeFiles[i] ) == 0 ) ) {
            ret = 1;
        }
    }
    return ret;
}

/**
  * parse command line args and set globals accordingly
  */
int main( int argc, char **argv ) 
{ 
    int i, exclude = 0, dirs = 0, goodArg = 0, dirsCount = 0;
    char dirNames[256][256];

    strcpy( progName, "" );
    strcpy( directory, "" );
    if ( argc > 1 ) {
        for ( i = 1; i < argc; i++ ) {
            if ( exclude ) {
                strcpy( excludeFiles[excludeIndex++], argv[i] );
            }
            else if ( dirs ) {
                strcpy( dirNames[dirsCount++], argv[i] );
            }
            else if ( (strcmp( argv[i], "-n" ) == 0 ) ) { 
                if ( i + 1  < argc ) {
                    strcpy( progName, argv[i+1] );
                    goodArg = 1;
                }
                exclude = 0;
                dirs = 0;
            }
            else if ( (strcmp( argv[i], "-m" ) == 0 ) ) { 
                if ( i + 1  < argc ) {
                    strcpy( directory, argv[i+1] );
                    goodArg = 1;
                }
                exclude = 0;
                dirs = 0;
            }
            else if ( (strcmp( argv[i], "-x" ) == 0 ) ) { 
                goodArg = 1;
                exclude = 1;
                dirs = 0;
            }
            else if ( (strcmp( argv[i], "-d" ) == 0 ) ) { 
                goodArg = 1;
                exclude = 0;
                dirs = 1;
            }
            else if ( ( strcmp( argv[i], "-h" ) == 0 ) || 
                ( strcmp( argv[i], "--help" ) == 0 ) ) {
                printHelp();
                exit( 0 );
                exclude = 0;
                dirs = 0;
            }
            else if ( (strcmp( argv[i], "-uc" ) == 0 ) ) { 
                cUpdate = 1;
                goodArg = 1;
                exclude = 0;
                dirs = 0;
            }
            else if ( (strcmp( argv[i], "-uC" ) == 0 ) ) { 
                cppUpdate = 1;
                goodArg = 1;
                exclude = 0; 
                dirs = 0;
            }
            else if ( (strcmp( argv[i], "-ucpp" ) == 0 ) ) { 
                cppUpdate = 1;
                goodArg = 1;
                exclude = 0; 
                dirs = 0;
            }
            else if ( (strcmp( argv[i], "-uh" ) == 0 ) ) { 
                hUpdate = 1;
                goodArg = 1;
                exclude = 0; 
                dirs = 0;
            }
        }
        if ( !goodArg ) {
            printHelp();
            exit( 0 );
        }
    }
    /** if no filetypes specified to be updated, default to update all */
    if ( cUpdate != 1 && cppUpdate != 1 && hUpdate != 1 ) {
        cUpdate = 1;
        cppUpdate = 1;
        hUpdate = 1;
    }

    readDirectory( "." );
    for ( i = 0; i < dirsCount; i++ ) { 
        readDirectory( dirNames[i] );
    }
    createMakefile();
    return ( 0 );
}

void printHelp() 
{
    printf( "%s%s%-27s%s%-27s%s%-27s%s%-27s%s%-27s%s%-27s%s%-27s%s", 
        "Usage: makemake [OPTION]...\n",
        "Create a Makefile based on files in current directory.\n\n",
        " -n name",
        "The name of the executable created by the Makefile.\n",
        " -x file1 [file2] [...]",
        "Exclude file(s) from being included in the Makefile.\n",
        " -m makefile",
        "The Makefile to use, overriding both the default and ",
        "",
        " the Makefile in current directory.\n",
        " -h, --help",
        "Print help (this screen).\n\n",
        " -d dir1 [dir2] [...]",
        "Add files in these directories to the Makefile as\n", 
        "",
        " well.\n\n" );
    printf( "%s%s%-27s%s%-27s%s%-27s%s", 
        "When the following update commands are issued, only those ", 
        "filetypes listed \nwill be updated.  The default is all types.\n",
        " -uc",
        "Update c files.\n",
        " -uC, -Ucpp",
        "Update C or cpp files.\n",
        " -uh",
        "Update h files.\n" );
}

