#include "icmconf"

#ifndef SHAREDREQ
#define SHAREDREQ   ""
#endif

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

#ifndef ICM_DEP
#define ICM_DEP     "-V go"
#endif

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)?
list    g_classLines;   // list of all lines in CLASSES
list    g_classLine;    // line of the CLASSES file

list    g_buildprog = strtok("program strip", " ");

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 = getenv(envvar);    

    return optvar[0] == "1" ? optvar[1] : install_im;
}

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 // COMPILER
    #endif // CC
#endif // CXX
}

list patternList(string pattern)
{
    list ret;

    list in = strtok(pattern, " \t");

    for (int idx = listlen(in); idx--; )
        ret += makelist(in[idx]);

    return ret;
}

#ifdef PARSER_DIR
    void parser()
    {
        chdir(PARSER_DIR);
    
        list gramfiles = makelist(PARSSPEC);
    
    #ifdef PARSFILES
        gramfiles += makelist(PARSFILES);
    #endif
    
        for (int idx = listlen(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("..");
    }
#endif

#ifdef SCANNER_DIR
    void scanner()
    {
        chdir(SCANNER_DIR);
    
        int rerun = PARSER_DIR != "" && 
                    "../"PARSER_DIR"/"PARSOUT  younger SCANOUT;
    
        if (!rerun)
        {
            list scanfiles = makelist(PARSSPEC) + makelist(SCANSPEC);
        
            #ifdef SCANFILES
                scanfiles += makelist(SCANFILES);
            #endif
        
            for (int idx = listlen(scanfiles); idx--; )
            {
                if (scanfiles[idx]  younger SCANOUT)
                {
                    showCd(SCANNER_DIR);
                    rerun = 1;
                    break;
                }
            }
        }
    
        if (rerun)
            system(SCANGEN " " SCANFLAGS " " SCANSPEC);
    
        chdir("..");
    }
#endif

int isEmpty(string line)        // needs stripped line
{
    return strlen(line) == 0 || line[0] == "#" || strfind(line, "//") == 0;
}
    
int lineRead()
{
    g_classLine = fgets("CLASSES", g_classLine);
    return listlen(g_classLine) && g_classLine[2] == "OK";
}

list nextCLASSESline() // return entries of CLASSES as a list
{
    string ret;

            // get the next line from CLASSES:
    while (lineRead())
    {
        string line = trim(g_classLine[0]);     // remove blanks

        if (isEmpty(line))                      // nothing there
        {
            if (strlen(ret) != 0)               // ret already contains txt
                break;                          // so we're done
        }
        else                                    // the next line isn't empty
        {
            int last = strlen(line) - 1;
            int backslash = line[last] == "\\";

            if (backslash)                      // its last char is backsl.
                line = trimright(resize(line, last));    // remove it and
                                                // trim rhs blanks

            ret += line + " ";                  // append line

            if (! backslash)                    // done unless backslash
                break;
        }
    }

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

    return strtok(ret, " \t");
}

void setClasses()
{

#ifdef SCANNER_DIR
                                        // 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
#endif

#ifdef PARSER_DIR
    if (PARSER_DIR != "")
        g_classes += (list)PARSER_DIR;
#endif

    list class;
    while (listlen(class = nextCLASSESline()))
        g_classes = listunion(g_classes, class[0]);

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

    g_nClasses = listlen(g_classes);
}

list inspect(string destDir, 
             int prefix, string srcDir, list srcList, string library)
{
    string oprefix = destDir + "/" + (string)prefix;
    srcDir += "/";

#ifdef USE_ALL
    string all = srcDir + USE_ALL;
#endif

    for (int idx = listlen(srcList); idx--; )
    {
        string file  = srcList[idx];   
        string source = srcDir + file;
        string 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)
            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)
{
    showCd(srcDir);

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

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

    for (int idx = listlen(cfiles); idx--; )
    {
        string 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)
{
    chdir("");
                                                    // make list of all files
    md(destDir);
    chdir(srcDir);
    list files = makelist(g_sources);

#ifdef MAIN
    if (ignoreMain)
        files -= (list)MAIN;
#endif

    chdir("");

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

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

void static_library()
{
#ifdef LIBRARY
    chdir(TMP_DIR "/o");
    if (listlen(makelist("*" OBJ_EXT)))
    {
        showCd(TMP_DIR "/o");

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

void shared_library(string libso, string libsoshared)
{
    if (listlen(makelist("*/o/*.o")))
    {
        string 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 precompile(string class)
{
#ifdef PRECOMP
    string classIH = class + IH;
    if (!exists(classIH))
    {
#ifndef NO_PRECOMP_WARNING
        printf("[Warning] CAN'T PRECOMPILE ", class, "/", classIH, 
                " (unavailable)\n");
#endif
        return;
    }
    if (!exists(classIH + ".gch"))
        system(g_compiler + " " PRECOMP " " + classIH);
#endif
}

void precompileHeaders()
{
    list classes = makelist(O_SUBDIR, "*");

    for (int idx = listlen(g_classes); idx--; )
    {
        string class = g_classes[idx];

        chdir(class);
        precompile(class);        
        chdir(g_cwd);
    }
}

void build_libraries()
{
    setClasses();

#ifdef PARSER_DIR
    if (PARSER_DIR != "")
        parser();
#endif

#ifdef SCANNER_DIR
    if (SCANNER_DIR != "")
        scanner();
#endif

    if (strlen(ICM_DEP))
        system("icmake -d " ICM_DEP);

#ifdef PRECOMP
    precompileHeaders();

    string mainBase;
#ifdef MAIN
    mainBase = get_base(MAIN);
#endif

    if (DEFCOM == "program" && mainBase != "")
        precompile(mainBase);
#endif

    md(TMP_DIR);

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


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

                                            // compile all files in subdirs
    for (int 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
        string libso = "lib" LIBRARY ".so";
        string libsoshared = libso + "." + g_version;
                                            // add option for shared lib
        g_compiler += " -fPIC "; 

        for (int 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  // LIBRARY
    #endif  // SHARED

    chdir("");
}

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

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

void link(string maino, string strip)
{
    if (g_command != "program")
        return;

    chdir(TMP_DIR);

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

#ifdef LIBRARY
    compiler += " -l" LIBRARY " -L.";
#else
    if (listlen(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 = "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()
{
    echo(OFF);
    exec("find ./ -name " + USE_ALL + " -exec rm '{}' \\;");
    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

#ifdef PRECOMP
    system("find ./ -type f -name  \"*" IH ".gch\" -exec rm -f \\{\\} \\;");
#endif

    exit(0);
}

void install(string what, string path)
{
    if (what == "program")
    {
        string 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" LIBRARY ".a " + path);
    }
#ifdef SHARED
    else if (what == "shared")
    {
        md(path);
        system("cp -d " TMP_DIR "/lib" LIBRARY ".so.* " + path);
    }
#endif      // SHARED
#endif      // LIBRARY

    exit(0);
}

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

void main(int argc, list argv, list envp)
{
    echo(USE_ECHO);

    setGcompiler();

    g_command = argv[1];

    int cls;
    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

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

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

    string option;
    if (listfind(g_buildprog, g_command) != -1)
    {
        clearScreen(cls);
        if (g_command == "strip")
        {
            g_command = "program";
            option = "-s";
        }
        program(option);        // exits
    }

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

#ifdef USE_ALL
        rmUSE_ALLfiles();
#endif
        return;
    }

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

    exec("icmbuild -h");
}
