#!/usr/bin/icmake -qt/tmp/icmbuild

#include "icmconf"

#ifdef USE_VERSION
#include "VERSION"
#else
#define VERSION "0.00.00"
#endif

#ifndef SHAREDREQ
#define SHAREDREQ   ""
#endif

//                      NO CONFIGURABLE PARTS BELOW THIS LINE

string  g_command;      // command to execute
string  g_sources;      // Pattern for the sources to use
string  g_libpath;      // the extra library paths
string  g_libs;         // the extra libraries
list    g_classes;      // all class-directories
int     g_nClasses;     // number of class-directories
int     g_compiled;     // any source compiled (but main)?
int     g_keepGramspec; // keep the gramspec directory
list    g_classLines;   // list of all lines in CLASSES
list    g_classLine;    // line of the CLASSES file
list    g_inspected;    // list of classes whose dependencies have been 
                        // inspected (used with USE_ALL)
int     g_base;         // compile the sources in the base directory
int     g_chdir;        // display chdirs to directories without sources to
                        // compile

string  g_version = VERSION;
string  g_compiler;
string  g_cwd = chdir("");  // initial working directory

void md(string dir)
{
    if (!exists(dir))
        system("mkdir -p " + dir);
}

void showCd(string dir)
{
    if (USE_ECHO)    
        printf("\n"
                "chdir ", dir, "\n");
}

string setOpt(string install_im, string envvar)
{
    list optvar;
    string ret;

    optvar = getenv(envvar);    

    if (optvar[0] == "1")
        ret = optvar[1];
    else 
        ret = install_im;

    return ret;
}

void setGcompiler()
{
    // try a C++ compiler; if not found: try a C compiler; if not found
    // try COMPILER. 
    // Same for matching options.
#ifdef CXX
    g_compiler = setOpt(CXX, "CXX") + " " + setOpt(CXXFLAGS, "CXXFLAGS");
#else
    #ifdef CC
        g_compiler = setOpt(CC, "CC") + " " + setOpt(CFLAGS, "CFLAGS");
    #else
        #ifdef COMPILER
        #ifdef COMPILER_OPTIONS
            g_compiler = COMPILER + " " + COMPILER_OPTIONS;
        #endif
        #endif
    #endif
#endif
}

list patternList(string pattern)
{
    list in;
    list ret;
    int idx;

    in = strtok(pattern, " \t");
    for (idx = sizeof(in); idx--; )
        ret += makelist(in[idx]);

    return ret;
}

void parser()
{
    list gramfiles;
    int idx;

    chdir(PARSER_DIR);

    gramfiles = makelist(PARSSPEC);

#ifdef PARSFILES
    gramfiles += makelist(PARSFILES);
#else
    #ifdef GRAMBUILD
        chdir("gramspec");
        showCd(PARSER_DIR "gramspec");

        system("./grambuild");
        chdir("..");
    #endif
#endif

    for (idx = sizeof(gramfiles); idx--; )
    {
        if (gramfiles[idx] younger PARSOUT)    // need new parser
        {
            showCd(PARSER_DIR);
            if (USE_ECHO)
                printf("New parser: `", gramfiles[idx], "' changed\n");
            system(PARSGEN " " PARSFLAGS " " PARSSPEC);
            break;
        }
    }

    chdir("..");
}

void scanner()
{
    list scanfiles;
    int idx;
    int rerun;

    chdir(SCANNER_DIR);

    rerun = PARSER_DIR != "" && "../"PARSER_DIR"/"PARSOUT  younger SCANOUT;

    if (!rerun)
    {
        scanfiles = makelist(PARSSPEC) + makelist(SCANSPEC);
    
        #ifdef SCANFILES
            scanfiles += makelist(SCANFILES);
        #endif
    
        for (idx = sizeof(scanfiles); idx--; )
        {
            if (scanfiles[idx]  younger SCANOUT)
            {
                showCd(SCANNER_DIR);
                rerun = 1;
                break;
            }
        }
    }

    if (rerun)
        system(SCANGEN " " SCANFLAGS " " SCANSPEC);

    chdir("..");
}

#ifdef USE_ALL

int find(string needle, list haystack)
{
    int idx;
    for (idx = sizeof(haystack); idx--; )
    {
        if (needle == haystack[idx])
            break;
    }
    return idx;
}

void touch(string dir)
{
    echo(OFF);
    if (!exists(dir + "/" USE_ALL))
        system("touch " + dir + "/" USE_ALL);
    echo(USE_ECHO);
}
            
    //  g_classes[idx] has USE_ALL set. 
    //  find all classes that depend on g_classes[idx], 
    //  if those classes haven't been inspected yet, then set their USE_ALL 
    //  file and inspect their dependencies.
void dependenciesOf(string thisClass)
{
    int ret;
    int idx;
    list hit;

    if (find(thisClass, g_inspected) != -1) // this class already inspected
        return;                             // then done with this class

    g_base = 1;                             // recompile the base's files

    printf("RECOMPILE: ", thisClass, "\n");
    g_inspected += (list)thisClass;         // now inspected

        // find all classes depending on thisClass
    for (idx = 0; idx != g_nClasses; ++idx)
    {
        hit = strtok(g_classLines[idx], " \t");

        if (find(thisClass, hit) > 0)
        {
            touch(hit[0]);
            dependenciesOf(hit[0]);
        }
    }
}            
    
void checkALL()
{
    int idx;
    string all;

    for (idx = 0; idx != g_nClasses; ++idx)
    {
        all = g_classes[idx] + "/" USE_ALL;

        if (exists(all))
            dependenciesOf(g_classes[idx]);
    }

    if (g_base)
        touch(".");
}
    
#endif

int isEmpty(string line)
{
    list parts;

    parts = strtok(line, " \t");
    
    return (int)(sizeof(parts) == 0 ||
                parts[0][0] == "#" || strfind(parts[0], "//") == 0);
}

    
list nextCLASSESline()
{
    string ret;
    string line;
    int last;
    int backslash;

    while (sizeof(g_classLine = fgets("CLASSES", (int)g_classLine[1])))
    {
        line = strtok(g_classLine[0], "\n")[0];     // get the string

        if (isEmpty(line))
        {
            if (strlen(ret) != 0)
                break;
        }
        else 
        {
            last = strlen(line) - 1;
            backslash = line[last] == "\\";

            if (backslash)
                line = substr(line, 0, last);

            ret += line + " ";

            if (!backslash)
                break;
        }
    }

    if (!isEmpty(ret))
        g_classLines += (list)ret;

    return strtok(ret, " \t");
}
    
void setClasses()
{
    string dir;
    list class;
                                        // make sure that scanner/parser
                                        // directories come first, so they
                                        // don't get reordered
    if (SCANNER_DIR != "")
        g_classes = (list)SCANNER_DIR;  // add the scanner-dir

    if (PARSER_DIR != "")
        g_classes += (list)PARSER_DIR;

    while (sizeof(class = nextCLASSESline()))
    {
        dir = class[0];

        if 
        (
            dir != SCANNER_DIR          // SCANNER_DIR is already there
            &&
            dir != PARSER_DIR           // PARSER_DIR is already there
        )
            g_classes += (list)dir;     // add this dir.
    }

    // classLines contains the full lines of the CLASSES file, and thus
    // stores its dependencies.

    g_nClasses = sizeof(g_classes);

    // g_classes has been defined. checkALL creates the file ALL in all 
    // directories depending on classes containing the file ALL
    #ifdef USE_ALL
        checkALL();
    #endif
}

list inspect(string destDir, 
             int prefix, string srcDir, list srcList, string library)
{
    int idx;
    string ofile;
    string oprefix;
    string file;
    string source;
    string all;

    oprefix = destDir + "/" + (string)prefix;
    srcDir += "/";
#ifdef USE_ALL
    all = srcDir + USE_ALL;
#endif

    for (idx = sizeof(srcList); idx--; )
    {
        file  = srcList[idx];   
        source = srcDir + file;
        
        ofile   = oprefix + change_ext(file, "o");    // make o-filename

        // A file s must be recompiled if:
        //  the ofile exists and is older than ALL
        //  the ofile doesn't exist but the lib. exists and is older than ALL
        //  neither the ofile nor the lib. exists and ALL exists

        // if ALL doesn't exist it must be recompiled if:
        //  it's newer than its object file o or newer than its target 
        //  library l, 
        //  if neither o nor l exist.

        // Since `a newer b' is true if a is newer than b, or if a exists and
        // b doesn't exist s must be compiled if s newer o and s newer l.
        // So, it doesn't have to be compiled if s older o or s older l.
                                            // redo if file has changed
#ifdef USE_ALL
        if (ofile older all && library older all)
            srcList += (list)file;
        else 
#endif
        if (source older ofile || source older library)
            srcList -= (list)file;
    }
    return srcList;
}

void c_compile(int prefix, string destDir, string srcDir, list cfiles)
{
    int idx;
    string compiler;
    string file;

    showCd(srcDir);

    if (srcDir != "")
        srcDir += "/";

    compiler = g_compiler + " -c -o " + destDir + "/" + (string)prefix;

    for (idx = sizeof(cfiles); idx--; )
    {
        file = cfiles[idx];
        system(compiler + change_ext(file, OBJ_EXT) + " " + srcDir + file);
        g_compiled = 1;
    }
}

void std_cpp(int ignoreMain, string destDir, 
            int prefix, string srcDir, string library)
{
    list files;

    chdir("");
                                                    // make list of all files
    md(destDir);
    chdir(srcDir);
    files = makelist(g_sources);
#ifdef MAIN
    if (ignoreMain)
        files -= (list)MAIN;
#endif
    chdir("");

    files = inspect(destDir, prefix, srcDir, files, library);  

    if (sizeof(files))
        c_compile(prefix, destDir, srcDir, files);  // compile files
}

void static_library()
{
#ifdef LIBRARY

    chdir(TMP_DIR "/o");
    if (sizeof(makelist("*" OBJ_EXT)))
    {
        showCd(TMP_DIR "/o");

        system("ar cru ../lib" LIBRARY + ".a *" OBJ_EXT);
        system("ranlib ../lib" LIBRARY + ".a");
        system("rm *" OBJ_EXT);
    }
    chdir("");

#endif
}

void shared_library(string libso, string libsoshared)
{
    string libsomajor;

    if (sizeof(makelist("*/o/*.o")))
    {
        libsomajor  = libso + "." + element(0, strtok(g_version, "."));

        system(g_compiler + " -shared -Wl,--as-needed,-z,defs,-soname," + 
                libsomajor + 
                " -o " + TMP_DIR + "/" + libsoshared + " */o/*.o "
                SHAREDREQ);
    
        chdir(TMP_DIR);
    
        system("ln -sf " + libsoshared + " " + libsomajor);
        system("ln -sf " + libsomajor  + " " + libso);
    
        system("rm o/*.o");

        chdir("");
    }
}

void build_libraries()
{
    int idx;
    string class;

    string libso;
    string libsoshared;
    string staticLib;

    setClasses();

    md(TMP_DIR);

    if (PARSER_DIR != "")
        parser();

    if (SCANNER_DIR != "")
        scanner();

#ifdef LIBRARY
    staticLib = g_cwd + TMP_DIR "/lib" LIBRARY ".a";
#endif

    g_sources = SOURCES;
    g_compiled = 0;

    if 
    (
        exists("version.cc")
        &&
        (
            "VERSION" younger "version.cc" 
            || 
            "YEARS" younger "version.cc"
            || 
            "AUTHOR" younger "version.cc"
        )
    )
        system("touch version.cc");

                                            // compile all files in subdirs
    for (idx = g_nClasses; idx--; )
        std_cpp(0, TMP_DIR "/o", idx + 1, g_classes[idx], staticLib);
        
                                            // compile all files in g_cwd
    std_cpp(1, TMP_DIR "/o", 0, ".", staticLib);  

    static_library();                       // make the library

    #ifdef SHARED
    #ifdef LIBRARY
        libso = "lib" LIBRARY ".so";
        libsoshared = libso + "." + g_version;
                                            // add option for shared lib
        setGcompiler();
        g_compiler += " -fPIC "; 

        for (idx = g_nClasses; idx--; )
            std_cpp(0, TMP_DIR "/o", idx + 1, g_classes[idx], 
                                            libsoshared);
        
                                            // compile all files in g_cwd
        std_cpp(1, TMP_DIR "/o", 0, ".", libsoshared);  

        shared_library(libso, libsoshared);// make a shared library
    #endif
    #endif

    chdir("");
}

void setLibs()
{
    int n;
    int index;
    list cut;
        
    cut = strtok(ADD_LIBRARIES, " ");        // cut op libraries
    n = sizeof(cut);
    for (index = 0; index < n; index++)
        g_libs += " -l" + cut[index];

    cut = strtok(ADD_LIBRARY_PATHS, " ");     // cut up the paths
    n = sizeof(cut);
    for (index = 0; index < n; index++)
        g_libpath += " -L" + cut[index];
}

void link(string maino, string strip)
{
    string compiler;

    if (g_command != "program")
        return;

    chdir(TMP_DIR);

    compiler = g_compiler + " -o bin/binary " + maino;

#ifdef LIBRARY
    compiler += " -l" LIBRARY " -L.";
#else
    if (sizeof(makelist("o/*" OBJ_EXT)))
        compiler += " o/*" OBJ_EXT;
#endif

    setLibs();

    if (strlen(g_libs))
        compiler += g_libs + g_libpath;

#ifdef LDFLAGS
    compiler += " " + setOpt(LDFLAGS, "LDFLAGS");
#else
    #ifdef LINKER_OPTIONS
        compiler += " " + LINKER_OPTIONS;
    #endif
#endif

#ifndef REFRESH
    if 
    (
        maino younger "bin/binary"
    #ifdef LIBRARY
        || 
        "lib" LIBRARY ".a" younger "bin/binary"
    #endif
        ||
        g_compiled
    )
#endif
    {
        showCd(TMP_DIR);
        printf("LINKING:\n");
        system(compiler + " " + strip);
    }

    chdir("");
}

void strip_shared()
{
#ifdef LIBRARY
    string libsoshared;

    libsoshared = "lib" LIBRARY ".so." + g_version;

    chdir(TMP_DIR);

    if (exists(libsoshared))
        system("strip --strip-unneeded " + libsoshared);
    else
        printf("Can't find " TMP_DIR "/" + libsoshared + "\n");
#endif
}

#ifdef USE_ALL
void rmUSE_ALLfiles()
{
    int idx;

    echo(OFF);

    for (idx = sizeof(g_inspected); idx--; )
        system("rm -f " + g_inspected[idx] + "/" USE_ALL);
    if (sizeof(g_inspected))
        system("rm -f " USE_ALL);

    echo(USE_ECHO);
}
#endif

void program(string strip)
{
#ifdef MAIN
    string maino = change_ext(MAIN, OBJ_EXT);

    md(TMP_DIR "/bin");

    #ifdef USE_ALL
        g_base = exists(USE_ALL);
    #endif

    build_libraries();

    if (g_base || MAIN younger TMP_DIR "/" + maino)
    {
        printf("\n"
                "chdir .\n"
                "RECOMPILE: main.cc\n");
        system(g_compiler + " -c -o " TMP_DIR "/" + maino + " " MAIN);
    }

    link(maino, strip);

    #ifdef USE_ALL
        rmUSE_ALLfiles();
    #endif

#endif
    exit(0);
}

void clean()
{
    chdir("");
#ifdef USE_ALL
    system("rm -rf " USE_ALL " */" USE_ALL " " TMP_DIR);
#else
    system("rm -rf " TMP_DIR);
#endif

    exit(0);
}

void install(string what, string path)
{
    string dir;

    if (what == "program")
    {
        dir = get_path(path);
        if (dir != "")    
            md(dir);
            
        system("cp " TMP_DIR "/bin/binary " + path);
    }
#ifdef LIBRARY
    else if (what == "static")
    {
        md(path);
        system("cp " TMP_DIR "/lib/lib" LIBRARY ".a " + path);
    }
#ifdef SHARED
    else if (what == "shared")
    {
        md(path);
        system("cp -d " TMP_DIR "/lib/lib" LIBRARY ".so.* " + path);
    }
#endif
#endif

    exit(0);
}

void clearScreen(int cls)
{
    if (cls)
        system("tput clear");
}

void main(int argc, list argv, list envp)
{
    int cls;
    string prog;
    string option;

    echo(USE_ECHO);

    setGcompiler();

    g_command = argv[1];

    if (g_command == "-c")
    {
        cls = 1;
        g_command = argv[2];
    }
    else
        cls = 0;

#ifdef CLS
    cls = 1;
#endif

#ifdef DEFCOM
    if (g_command == "")
    {
        argv = (list)"" + strtok(DEFCOM, " \t");
        g_command = argv[1];
    }
#endif

#ifdef KEEPGRAMSPEC
    g_keepGramspec = 1;
#endif
    
#ifdef PARSFILES
    if (exists(PARSER_DIR "/gramspec") && !g_keepGramspec)
    {
        printf("Remove superfluous directory `" PARSER_DIR 
                                            "/gramspec' [yN] ? ");
        if (getch() == "y")
            system("rm -rf " PARSER_DIR "/gramspec");
        else
            fprintf("icmconf", "\n"
                        "\n"
                        "    // Do not remove\n"
                        "#define KEEPGRAMSPEC\n");
    }
#endif                

    if (g_command == "clean")
        clean();

    if (g_command == "install")
        install(argv[2], argv[3]);

    if (g_command == "program")
    {
        clearScreen(cls);
        if (argv[2] == "strip")
            option = "-s";
        else
            option = "";

        program(option);
    }

    if (g_command == "library")
    {
        clearScreen(cls);
        build_libraries();      

#ifdef USE_ALL
    rmUSE_ALLfiles();
#endif
        exit(0);
    }

    if (g_command == "strip-shared")
    {
        strip_shared();      
        exit(0);
    }

    prog = change_ext(change_path(argv[0], ""), "");

    printf("Usage: ", prog, " [-c] cmd\n"
        "   -c              - Does 'tput clear' (clear screen) just before\n"
        "                     starting 'program' or 'library'\n"
        "   cmd: command to execute:\n"
        "        clean           - clean up remnants of previous activities\n"

    #ifdef LIBRARY
        "        library         - build the static library " TMP_DIR "/lib"
                                                            LIBRARY  ".a\n"
        "                          optionally (SHARED in icmconf) build a\n"
        "                           shared library as well\n"
    #endif

    #ifdef SHARED
        "        strip-shared    - strip a shared library\n"
    #endif

        "   When building a program:\n"
        "        program         - build " TMP_DIR "/binary\n"
        "        program strip   - build stripped " TMP_DIR "/binary\n"
        "        install program path - to install the binary at `path'\n"
        "                         (provide the full path-name)\n"
        "   When building a library:\n"
        "        install static base - install the static library under "
                                                                "`base'\n"
        "        install shared base - install the shared library under "
                                                                "`base'\n"
    );

    exit(1);
}
