#!/bin/bash
#
# Copyright (C) Niklaus F.Schen.
#

#Global Variables
# test system type
sysname=`uname -s`

# paths
realpath=""
nullpath=""
confpath=""
tmpfilepath=""
pidpath=""
logpath=""
melang_script_path=""
melang_script_path=""
melang_dylib_path=""

# wasm flag
wasm=0

# debug
debug=0

# optimization level
olevel='-O3'

# selected source files
select_files='all'

# disabled macros
disabled_macros=""

# llvm
llvm_flag=""

# event
event_flag="-DMLN_SELECT"

# sendfile
sendfile_flag=""

# writev
writev_flag=""

# unix98
unix98_flag=""

# mmap
mmap_flag=""

# function tracing
func_flag=""

# C99
c99_flag=""

# compile_flags_for_mingw
mingw_cflags=""

# constructor
constructor_flag=""

# msys2
msys2_flag=""

#Functions
set_melang_default_paths() {
    if ! case $sysname in MINGW*) false;; esac; then
        melang_script_path=$HOME/lib/melang
        melang_dylib_path=$HOME/lib/melang_dynamic
        mingw_cflags="-Wno-error=use-after-free -Wno-use-after-free"
    else
        melang_script_path=/usr/local/lib/melang
        melang_dylib_path=/usr/local/lib/melang_dynamic
    fi
}

set_c_compilers() {
    if ! case $sysname in
         MINGW*) false;;
         MSYS*)  false;;
      esac; then
        case $MSYSTEM in
            MINGW32|MSYS|CLANG64|CLANGARM64)
                ;;
            *)
            echo "only support MINGW32, MSYS, CLANG64 and CLANGARM64"
            exit 1
            ;;
        esac
        install_path=$HOME/libmelon
        cc="gcc"
        tcc="gcc"
        msys2_flag="-DMSYS2"
    else
        install_path=`echo "/usr/local/melon"`
        cc="cc"
        tcc="cc"
    fi
    if [ -n "$CC" ]; then
        cc="$CC"
        tcc=${TCC:-$CC}
    fi
    echo -e "#include <stdio.h>\nint main(void) {printf(\"1\");return 0;}" > .xcode.c
    $tcc -o .xcode .xcode.c 2>/dev/null 1>&2
    if [ $? -ne 0 ]; then
        tcc="$tcc -isysroot `xcrun --show-sdk-path`"
        cc="$cc -isysroot `xcrun --show-sdk-path`"
    fi
    rm -fr .xcode .xcode.c
}

send_install_request() {
    nohup curl http://register.melang.org/?$select_files -H "Referer: Melon Installation" 2> /dev/null 1>&2 &
}

before_get_params() {
    set_melang_default_paths
    set_c_compilers
}

get_params() {
    for param in $@
    do
        if [ $param == "--help" ]; then
            echo -e "\nMelon platform."
            echo "Copyright (C) Niklaus F.Schen."
            echo "Options:"
            echo -e "\t--prefix=INSTALL_PATH"
            echo -e "\t--melang-prefix=MELANG_SCRIPT_PATH"
            echo -e "\t--melang-dylib-prefix=MELANG_DYLIB_PATH"
            echo -e "\t--cc=C compiler"
            echo -e "\t--enable-wasm"
            echo -e "\t--debug"
            echo -e "\t--olevel=O|O1|O2|O3"
            echo -e "\t--select=[all|module1,module2,...]"
            echo -e "\t--disable-macro=[macro1,macro2,...]"
            echo -e "\t--func"
            echo -e "\t--c99"
            exit 0
        fi
        param_prefix=`echo $param|cut -d '=' -f 1`
        param_suffix=`echo $param|cut -d '=' -f 2`
        if [ $param_prefix == "--prefix" ]; then
            install_path=$param_suffix
        elif [ $param_prefix == "--melang-prefix" ]; then
            melang_script_path=$param_suffix
        elif [ $param_prefix == "--melang-dylib-prefix" ]; then
            melang_dylib_path=$param_suffix
        elif [ $param_prefix == "--cc" ]; then
            cc=$param_suffix
        elif [ $param_prefix == "--enable-wasm" ]; then
            wasm=1
        elif [ $param_prefix == "--debug" ]; then
            debug=1
        elif [ $param_prefix == "--func" ]; then
            func_flag='-DMLN_FUNC_FLAG'
        elif [ $param_prefix == "--c99" ]; then
            c99_flag='-std=c99 -DMLN_C99 -D_POSIX_C_SOURCE=200112L'
        elif [ $param_prefix == "--select" ]; then
            select_files=$param_suffix
        elif [ $param_prefix == "--disable-macro" ]; then
            disabled_macros=$param_suffix
        elif [ $param_prefix == "--olevel" ]; then
            if [ -z $param_suffix ]; then
                olevel=''
            else
                olevel='-'$param_suffix
            fi
        fi
    done
}

update_llvm_flag() {
    if [ $wasm -eq 1 ]; then
        echo -e "Webassembly\t\t[enable]"

        $cc --help|grep LLVM > /dev/null 2>&1
        if [ $? -ne 0 ]; then
            llvm_flag="--llvm-lto 1"
        fi
    fi
}

relocate_install_path() {
    if [[ "$install_path" == ~* ]]; then
        install_path=$HOME/${install_path:1}
    fi
    if [[ "$melang_script_path" == ~* ]]; then
        melang_script_path=$HOME/${melang_script_path:1}
    fi
    if [[ "$melang_dylib_path" == ~* ]]; then
        melang_dylib_path=$HOME/${melang_dylib_path:1}
    fi
}

update_debug_flag() {
    if [ $debug -ne 0 ]; then
        if [ $wasm -eq 1 ]; then
            debug='-g -D__DEBUG__'
        else
            debug='-ggdb -D__DEBUG__'
        fi
    else
        debug=''
    fi
}

get_disabled_macros() {
    macros=()
    old_ifs=$IFS
    IFS=','
    for m in $disabled_macros
    do
        macros+=$m"_flag "
    done
    IFS=$old_ifs
    disabled_macros=$macros
}

detect_operating_system_event_support() {
    output="event\t\t\t[SELECT]"
    if [[ ! "${disabled_macros[@]}" =~ "event_flag" ]]; then
        echo "#include<stdio.h>
        #include<sys/types.h>
        #include<sys/event.h>
        #include<sys/time.h>
        int main(void){kqueue();return 0;}" > ev_test.c
        $cc -o ev_test ev_test.c 2>/dev/null
        if [ "$?" == "0" ]; then
            event_flag="-DMLN_KQUEUE"
            output="event\t\t\t[KQUEUE]"
        fi

        echo "#include<stdio.h>
        #include<sys/epoll.h>
        int main(void){epoll_create(10);return 0;}" > ev_test.c
        $cc -o ev_test ev_test.c 2>/dev/null
        if [ "$?" == "0" ]; then
            event_flag="-DMLN_EPOLL"
            output="event\t\t\t[EPOLL]"
        fi
    fi
    rm -f ev_test ev_test.c
    echo -e $output
}

detect_operating_system_sendfile_support() {
    output="sendfile\t\t[NOT support]"
    if [[ ! "${disabled_macros[@]}" =~ "sendfile_flag" ]]; then
        echo "#include <sys/sendfile.h>
        int main(void){sendfile(1,0,0,1);return 0;}" > sendfile_test.c
        $cc -o sendfile_test sendfile_test.c 2>/dev/null
        if [ "$?" == "0" ]; then
            sendfile_flag="-DMLN_SENDFILE"
            output="sendfile\t\t[support]"
        fi
        rm -f sendfile_test sendfile_test.c
    fi
    echo -e $output
}

detect_operating_system_writev_support() {
    output="writev\t\t\t[NOT support]"
    if [[ ! "${disabled_macros[@]}" =~ "writev_flag" ]]; then
        echo -e "#include <stdio.h>\n#include <sys/uio.h>" > writev_test.c
        echo "int main(void){writev(0,NULL,0);return 0;}" >> writev_test.c
        $cc -o writev_test writev_test.c 2>/dev/null
        if [ "$?" == "0" ]; then
            writev_flag="-DMLN_WRITEV"
            output="writev\t\t\t[support]"
        fi
        rm -f writev_test writev_test.c
    fi
    echo -e $output
}

detect_operating_system_unix98_support() {
    output="__USE_UNIX98\t\t[not support]"
    if [[ ! "${disabled_macros[@]}" =~ "unix98_flag" ]]; then
        echo -e "#ifndef __USE_UNIX98\n#define __USE_UNIX98\n#endif\n#include <pthread.h>\n" > unix98_test.c
        echo "int main(void){pthread_setconcurrency(0);return 0;}" >> unix98_test.c
        $cc -o unix98_test unix98_test.c -lpthread 2>/dev/null
        if [ "$?" == "0" ]; then
            output="__USE_UNIX98\t\t[support]"
            unix98_flag="-DMLN_USE_UNIX98"
        fi
        rm -fr unix98_test unix98_test.c
    fi
    echo -e $output
}

detect_operating_system_mmap_support() {
    output="mmap\t\t\t[NOT support]"
    if [[ ! "${disabled_macros[@]}" =~ "mmap_flag" ]]; then
        echo -e "#include <stdio.h>\n#include <sys/mman.h>" >> mmap_test.c
        echo "int main(void){mmap(NULL, 0, PROT_READ, MAP_PRIVATE, 0, 0);return 0;}" >> mmap_test.c
        $cc -o mmap_test mmap_test.c 2>/dev/null
        if [ "$?" == "0" ]; then
            mmap_flag="-DMLN_MMAP"
            output="mmap\t\t\t[support]"
        fi
        rm -f mmap_test mmap_test.c
    fi
    echo -e $output
}

detect_operating_system_constructor_support() {
    output="constructor\t\t[NOT support]"
    if [[ ! "${disabled_macros[@]}" =~ "constructor_flag" ]]; then
        echo -e "#include <stdio.h>\n" >> constructor_test.c
        echo -e "void my_constructor() __attribute__((constructor));\nvoid my_constructor(void) {}\nint main() {return 0;}\n" >> constructor_test.c
        $cc -o constructor_test constructor_test.c 2>/dev/null
        if [ "$?" == 0 ]; then
            constructor_flag="-DMLN_CONSTRUCTOR"
            output="constructor\t\t[support]"
        fi
        rm -fr constructor_test constructor_test.c
    fi
    echo -e $output
}

detect_operating_system_support() {
    if [ $wasm -eq 0 ]; then
        get_disabled_macros
        detect_operating_system_event_support
        detect_operating_system_sendfile_support
        detect_operating_system_writev_support
        detect_operating_system_unix98_support
        detect_operating_system_mmap_support
        detect_operating_system_constructor_support
    fi
}

update_paths() {
    echo -e "Installation Path \t[$install_path]"
    echo -e "Melang script Path \t[$melang_script_path]"
    echo -e "Melang dylib Path \t[$melang_dylib_path]"
    echo -e "#include <stdio.h>\nint main(int argc, char *argv[]) {printf(\"%s\", argv[1]);return 0;}" > .path_generator.c
    $tcc -o path_generator .path_generator.c
    realpath=`./path_generator $install_path`
    nullpath=`./path_generator /dev/null`
    melang_script_path=`./path_generator $melang_script_path`
    melang_script_path=`./path_generator $melang_script_path`
    melang_dylib_path=`./path_generator $melang_dylib_path`
    rm -f path_generator .path_generator.c
}

# build makefile content
get_source_files() {
    files=()
    if [ $1 = 'all' ]; then
        for f in `find src -name "*.c"`
        do
            files+=$f" "
        done
    else
        while true
        do
            old_ifs=$IFS
            IFS=','
            current_files=$select_files
            for f in $select_files
            do
                IFS=$old_ifs
                fname='mln_'$f'.c'
                fname=`find src -name $fname`
                if [ -z $fname ]; then
                    echo "[$f] not found"
                    exit 1
                fi
                if [[ ! "${files[@]}" =~ $fname ]]; then
                    files+=$fname" "
                fi
                for header in `cpp -MM -MG -I include $fname 2> /dev/null`
                do
                    suffix=`echo $header | cut -d '.' -f 2`
                    if [ $suffix == 'h' ]; then
                        source_file=`echo $header | cut -d '/' -f 2`
                        module_name=`echo $source_file | cut -d '.' -f 1`
                        source_file='src/'$module_name'.c'
                        module_name=$(echo "$module_name" | sed 's/mln_//')
                        source_file=`find src -wholename "$source_file"`
                        if [ ! -z $source_file ]; then
                            if [[ ! "${files[@]}" =~ $source_file ]]; then
                                files+=$source_file" "
                                current_files+=","$module_name
                            fi
                        fi
                    fi
                done
            done
            if [ $select_files = $current_files ]; then
                break
            fi
            select_files=$current_files
            IFS=$old_ifs
        done
    fi
    select_files=$files
}

generate_Makefile() {
    echo "# " > Makefile
    echo "# Copyright (C) Niklaus F.Schen." >> Makefile
    echo "# " >> Makefile
    if [ $wasm -eq 1 ]; then
        echo -e "CC\t\t= emcc" >> Makefile
    else
        echo -e "CC\t\t= $cc" >> Makefile
    fi
    if [ $wasm -eq 1 ]; then
        echo -e "FLAGS\t\t= -Iinclude -c $debug $olevel $llvm_flag -s -mmutable-globals -mnontrapping-fptoint -msign-ext -Wemcc -DMLN_ROOT=\\\"$realpath\\\" -DMLN_NULL=\\\"$nullpath\\\" -DMLN_LANG_LIB=\\\"$melang_script_path\\\" -DMLN_LANG_DYLIB=\\\"$melang_dylib_path\\\" $CFLAGS" >> Makefile
    else
        echo -e "FLAGS\t\t= -Iinclude -c -Wall $debug -Werror $mingw_cflags $olevel -fPIC -DMLN_ROOT=\\\"$realpath\\\" -DMLN_NULL=\\\"$nullpath\\\" -DMLN_LANG_LIB=\\\"$melang_script_path\\\" -DMLN_LANG_DYLIB=\\\"$melang_dylib_path\\\" $event_flag $sendfile_flag $writev_flag $unix98_flag $mmap_flag $func_flag $c99_flag $constructor_flag $msys2_flag $CFLAGS" >> Makefile
    fi
    if ! case $sysname in MINGW*) false;; esac; then
        if [ $wasm -eq 0 ]; then
            echo -e "MELONSO\t\t= libmelon.dll" >> Makefile
        fi
        echo -e "MELONA\t\t= libmelon_static.lib" >> Makefile
    else
        if [ $wasm -eq 0 ]; then
            echo -e "MELONSO\t\t= libmelon.so" >> Makefile
        fi
        echo -e "MELONA\t\t= libmelon_static.a" >> Makefile
    fi

    echo -e "OBJS\t\t= \\" >> Makefile
    for fname in ${select_files[@]}
    do
        fname=`basename $fname`
        objname=`echo $fname | cut -d '.' -f 1`".o"
        echo -n "        objs/"$objname >> Makefile
        echo " \\" >> Makefile
    done
    echo "" >> Makefile

    echo -e ".PHONY :\tcompile install clean" >> Makefile

    if [ $wasm -eq 1 ]; then
        echo "compile: MKDIR \$(OBJS) \$(MELONA)" >> Makefile
    else
        echo "compile: MKDIR \$(OBJS) \$(MELONSO) \$(MELONA)" >> Makefile
    fi
    echo "clean:" >> Makefile
    echo -e "\trm -fr objs lib bin build Makefile" >> Makefile
    echo "MKDIR :" >> Makefile
    echo -e "\ttest -d objs || mkdir objs" >> Makefile
    echo -e "\ttest -d lib || mkdir lib" >> Makefile

    echo "\$(MELONA) : \$(OBJS)" >> Makefile
    if [ $wasm -eq 1 ]; then
        echo -e "\temar rcs lib/\$(MELONA) \$(OBJS)" >> Makefile
    else
        echo -e "\tar -r lib/\$(MELONA) \$(OBJS)" >> Makefile
    fi

    if [ $wasm -eq 0 ]; then
        echo "\$(MELONSO) : \$(OBJS)" >> Makefile
        if [ $sysname = 'Linux' ]; then
            echo -e "\t\$(CC) -o lib/\$(MELONSO) \$(OBJS) $debug -Wall -lpthread -Llib/ -ldl -shared -fPIC $LDFLAGS" >> Makefile
        elif ! case $sysname in MINGW*) false;; esac; then
            echo -e "\t\$(CC) -o lib/\$(MELONSO) \$(OBJS) $debug -Wall -lpthread -lWs2_32 -Llib/ -shared -fPIC $LDFLAGS" >> Makefile
        else
            echo -e "\t\$(CC) -o lib/\$(MELONSO) \$(OBJS) $debug -Wall -lpthread -Llib/ -lc -shared -fPIC $LDFLAGS" >> Makefile
        fi
    fi
    echo "install:" >> Makefile
    echo -e "\ttest -d $melang_script_path || mkdir -p $melang_script_path" >> Makefile
    echo -e "\ttest -d $install_path || mkdir -p $install_path" >> Makefile
    echo -e "\tcp -fr lib $install_path" >> Makefile
    echo -e "\tcp -fr include $install_path" >> Makefile
    echo -e "\ttest -d $install_path/conf || cp -fr conf $install_path" >> Makefile
    echo -e "\ttest -d $melang_script_path/trace || cp -fr trace $melang_script_path" >> Makefile

    echo "TEST_MKDIR: " >> Makefile
    echo -e "\ttest -d bin || mkdir -p bin" >> Makefile
    for fname in ${select_files[@]}
    do
        fname=$(echo $fname | sed 's/^src\/mln_//')
        binname=`echo $fname | cut -d '.' -f 1`
        fname="t/$fname"
        if [ -f $fname ]; then
            echo -n "bin/$binname : " >> Makefile
            for header in `cpp -MM -MG $fname 2> /dev/null`
            do
                suffix=`echo $header | cut -d '.' -f 2`
                if [ $suffix = 'c' ]; then
                    echo -n $header >> Makefile
                    echo -n " " >> Makefile
                    continue
                fi
                if [ $suffix != 'h' ]; then
                    continue
                fi
                test -e include/$header && echo -n "include/$header " >> Makefile
            done
            echo "" >> Makefile

            echo -e "\t\$(CC) -o \$@ $fname $debug -Iinclude -Llib -lmelon" >> Makefile
        fi
    done
    echo -e "BINS = \\" >> Makefile
    for fname in ${select_files[@]}
    do
        fname=$(echo $fname | sed 's/^src\/mln_//')
        binname="bin/"`echo $fname | cut -d '.' -f 1`
        fname="t/$fname"
        if [ -f $fname ]; then
            echo "        $binname \\" >> Makefile
        fi
    done
    echo "" >> Makefile
    echo "test: TEST_MKDIR \$(BINS)" >> Makefile
    echo "" >> Makefile

    echo "run:" >> Makefile
    echo -e "\t@echo 'Run Tests' && \\" >> Makefile
    for fname in ${select_files[@]}
    do
        fname=$(echo $fname | sed 's/^src\/mln_//')
        binname="bin/"`echo $fname | cut -d '.' -f 1`
        fname="t/$fname"
        if [ -f $fname ]; then
            echo -e "\techo \"====== Test $binname running... ======\" && $MLN_TEST_RUNNER ./$binname && \\" >> Makefile
        fi
    done
    echo -e "\techo 'Done'" >> Makefile

    for fname in ${select_files[@]}
    do
        objname=`basename $fname | cut -d '.' -f 1`".o"
        echo -n "objs/$objname :" >> Makefile
        for header in `cpp -MM -MG $fname 2> /dev/null`
        do
            suffix=`echo $header | cut -d '.' -f 2`
            if [ $suffix = 'c' ]; then
                echo -n $header >> Makefile
                echo -n " " >> Makefile
                continue
            fi
            if [ $suffix != 'h' ]; then
                continue
            fi
            test -e include/$header && echo -n "include/$header " >> Makefile
        done
        echo "" >> Makefile

        echo -e "\t\$(CC) \$(FLAGS) -o \$@ $fname" >> Makefile
    done
}

generate_conf_file() {
    sed -e "s#{{ROOT}}#${realpath}#g" conf/melon.conf.template > conf/melon.conf
}

after_get_params() {
    send_install_request
    update_llvm_flag
    relocate_install_path
    update_debug_flag
    detect_operating_system_support
    update_paths
    get_source_files $select_files
    generate_Makefile
    generate_conf_file
}

before_get_params
get_params $@
after_get_params
echo "Configure done!"

