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

#include "icmconf"

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

#ifndef LIBRARY
#define LIBRARY ""
#endif

#ifndef BIN_INSTALL
#define BIN_INSTALL "."
#endif

#ifndef SHAREDREQ
#define SHAREDREQ   ""
#endif

//                      NO CONFIGURABLE PARTS BELOW THIS LINE

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
string g_binary;        // binary program to build
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_depHaystack;   // list used with USE_ALL

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);
}

string cd(string next)
{
    if (USE_ECHO && next != ".")
        printf("chdir " + next + "\n");

    return chdir(next);
}

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()
{
#ifdef COPTS
    g_compiler = COMPILER " -c " + setOpt(COMPILER_OPTIONS, COPTS);
#else
    g_compiler = COMPILER " -c " COMPILER_OPTIONS;
#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;

    cd(PARSER_DIR);

    gramfiles = makelist(PARSSPEC);

#ifdef PARSFILES
    gramfiles += makelist(PARSFILES);
#else
    #ifdef GRAMBUILD
        cd("gramspec");
        system("./grambuild");
        chdir("..");
    #endif
#endif

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

    chdir("..");
}

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

    cd(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)
            {
                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 dependent(int idx)
{
    list dependencies;
    string target;
    int ret;
    int depIdx;

    target = g_classes[idx];            // recompile this dir.
    g_depHaystack += (list)target;      // recompile classes depending on 
                                        // these classes
    printf("RECOMPILE: ", target, "\n");
    system("touch " + target + "/*.cc");

                                        // inspect all classes
    for (depIdx = 0; depIdx != g_nClasses; ++depIdx)
    {
                                        // get all dependencies for depIdx
        dependencies = strtok(g_classLines[depIdx], " \t\n");
        
        if 
        (
            find(target, dependencies) != -1    // depIdx depends on target
            &&                                  // and depIdx not yet recorded
            find(dependencies[0], g_depHaystack) == -1
        )
            dependent(depIdx);                  // then do depIdx's deps
    }
}            
    
void checkALL()
{
    int idx;
    int base;
    string all;

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

        if (exists(all))
        {
            dependent(idx);
            base = 1;
        }
    }

    if (base)
    {
        system("rm -f */" USE_ALL);
        system("touch *.cc");
    }
}
    
#endif

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 = fgets("CLASSES", (int)class[1])))
    {
        dir = element(0, strtok(class[0], " \t\n"));

        if 
        (
            strlen(dir)                 // omit empty lines
            &&
            strfind(dir, "#") != 0      // omit lines starting in #
            &&
            strfind(dir, "/") != 0      // omit lines starting in //
        )
        {
            if 
            (
                dir != SCANNER_DIR          // SCANNER_DIR is already there
                &&
                dir != PARSER_DIR           // PARSER_DIR is already there
            )
                g_classes += (list)dir;     // add this dir.

            g_classLines += (list)class[0];
        }
    }
    // 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;

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

    compiler = g_compiler + " -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);
    cd(srcDir);
    files = makelist(g_sources);
    if (ignoreMain)
        files -= (list)MAIN;
    chdir("");

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

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

void static_library()
{
    cd(TMP_DIR "/o");

    if (strlen(LIBRARY) && sizeof(makelist("*" OBJ_EXT)))
    {
        system("ar cru ../lib" LIBRARY + ".a *" OBJ_EXT);
        system("ranlib ../lib" LIBRARY + ".a");
        system("rm *" OBJ_EXT);
    }
    chdir("");
}

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

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

        system(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);

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

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

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

    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
        libso = "lib" LIBRARY ".so";
        libsoshared = libso + "." + g_version;
                                            // add option for shared lib
        setGcompiler();
        g_compiler += " -fPIC -o "; 

        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

    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)
{
#ifdef BINARY
    string compiler;

    cd(TMP_DIR);

    compiler = COMPILER " -o bin/" BINARY " " + maino;

    if (exists("lib" LIBRARY ".a"))
        compiler += " -l" LIBRARY " -L.";
    else if (sizeof(makelist("o/*" OBJ_EXT)))
        compiler += " o/*" OBJ_EXT;

    setLibs();

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

    #ifndef REFRESH
        if 
        (
            maino younger "bin/" BINARY
            || 
            "lib" LIBRARY ".a" younger "bin/" BINARY
            ||
            g_compiled
        )
    #endif
        system(compiler + " " + strip + setOpt(LINKER_OPTIONS, "LDFLAGS"));

    chdir("");
#endif
}

void strip_shared()
{
    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");
}


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

    md(TMP_DIR "/bin");

    build_libraries();

    if (MAIN younger TMP_DIR "/" + maino)
        system(g_compiler + " -o " TMP_DIR "/" + maino + " " MAIN);

    link(maino, strip);

#ifdef USE_ALL
    echo(OFF);
    system("rm -f */" USE_ALL);
    echo(USE_ECHO);
#endif

    exit(0);
}

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

    exit(0);
}

void install(string base)
{
#ifdef BINARY
#ifdef BIN_INSTALL
    md(base + BIN_INSTALL);

    system("cp " TMP_DIR "/bin/" BINARY " " + 
            base + BIN_INSTALL "/" + BINARY);

#endif
#endif
#ifdef LIBRARY
#ifdef LIB_INSTALL
    md(base + LIB_INSTALL);
    system("cp -d " TMP_DIR "/lib/* " + base + LIB_INSTALL);
#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();

    option = argv[1];

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

#ifdef CLS
    cls = 1;
#endif

#ifdef DEFCOM
    if (option == "")
    {
        argv = (list)"" + strtok(DEFCOM, " \t");
        option = 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 (option == "clean")
        clean();

    if (option == "install")
        install(argv[2]);

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

        program(option);
    }

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

#ifdef USE_ALL
        echo(OFF);
        system("rm -f */" USE_ALL);
        echo(USE_ECHO);
#endif

        exit(0);
    }

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

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

    printf("Usage: [-c] ", prog, " mode\n"
        "   -c              - Does 'tput clear' (clear screen) just before\n"
        "                     starting 'program' or 'library'\n"
        "Where `mode' is one of:\n"
        "   clean           - clean up remnants of previous activities\n"
        "   library         - build the static library " TMP_DIR "/lib"
                                                            LIBRARY  ".a\n"
        "                     optionally (SHARED in icmconf) build a shared\n"
        "                     library as well\n"
        "   strip-shared    - strip a shared library\n"
#ifdef BINARY
        "   program         - build " TMP_DIR "/" BINARY "\n"
        "   program strip   - build stripped " TMP_DIR "/" BINARY "\n"
#endif
        "   install <base>  - to install the software in the locations\n"
        "                     defined in the icmconf file, optionally\n"
        "                     below <base>\n"
    );

    exit(1);
}
