#! /bin/bash

cd `dirname $0`

if [ $# -eq 2 ];
then
  if [ `expr $2 : /` -eq 1 ];
  then
    output_dir=$2
  else
    output_dir=$1/$2
  fi
else
    output_dir=../objs
fi

list_file=data/action_list
types_file=data/action_types
reps_file=data/action_replacements
prefix_file=data/prefixes 
header_file=data/header_files
optional_modules_file=data/modules_optional
headers_file=data/headers
module_dependencies_file=data/module_dependencies
conf_macros_file=data/conf_macros
conf_locs_file=data/conf_locs
conf_args_file=data/conf_args

autogen_notice=text/autogen

actions_dir=actions
srcs_dir=src
include_prefix=
file_prefix=ndk_
auto_file_name=config
auto_includes_name=includes

conf_merge_filename=conf_merge.h
conf_cmd_basic_filename=conf_cmd_basic.h
conf_cmd_extra_filename=conf_cmd_extra.h


spacer=¬

sed_delete_empty_lines='t_NEL;d;:_NEL'


function trim_lines {
    sed -e '/./,$!d' -e :a -e '/^\n*$/{$d;N;ba' -e '}'
}


function strtoupper {
    [ $# -eq 1 ] || return 1
    local _str _cu _cl _x
    _cu=(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
    _cl=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
    _str=$1
    for ((_x=0;_x<${#_cl[*]};_x++)); do
        _str=${_str//${_cl[$_x]}/${_cu[$_x]}}
    done
    echo $_str
}


function sed_pad_right {
    len=$1
    spacer=$2
    prefix=_PR$3

    # returns a SED script that pads out (spaces) to the right to alignment $len
    # this script should be used inside a call to sed
    # NOTE : a spacer character $spacer should have already been written into the parsed string

    echo "t${prefix}a;:${prefix}a;s/^[^$spacer]{$len}/&/;\
t${prefix}b;s/^[^$spacer]*/& /;t${prefix}a;:${prefix}b;s/$spacer/ /"
}


function sed_pad_left {

    len=$1
    spacer=$2
    prefix=_PL$3

   # echo "t${prefix}a;:${prefix}a;s/^[^$spacer]{$len}/& /;t${prefix}a"     # NOT CORRECT?
}


function add_notice {

    echo > $1
    cat $autogen_notice | trim_lines >> $1
    echo >> $1
    echo >> $1
}

function add_non_generated_content {

    file=src/$1.h
    [ ! -f $file ] && return

    echo "/* Non-generated macros */" >> $2
    echo >> $2

    cat $file | trim_lines >> $2

    echo >> $2
    echo >> $2
}


function add_action_macros {

    list_file=$actions_dir/$1

    [ ! -f $list_file ] && return

    out=$2


    # alignment settings

    align1=20
    align2=35
    align3=62
    base_shrink=12
    define="#define     "



    # base macros

    echo "/* base action macro macros */" >> $out
    echo >> $out

    cat $list_file | trim_lines | sed -r \
    -e "s/^[ ]*([a-zA-Z0-9_]+)([ ]*)\(([a-zA-Z0-9_,]+)\)([ ]*)(.*)/$define%2%_\1_ac(\3,ac)\2\4  {\5;}/" \
    -e "s/[ ]{$base_shrink}\{/\{/" \
    -e 's/%A%/ac/g' \
    >> $out

    echo >> $out
    echo >> $out



    # generated macros

    echo "/* generated action macros */" >> $out
    echo >> $out

    cat -s $list_file | while read list_line; do

        [ "x`echo $list_line`" = 'x' ] && continue

        cat $types_file | while read type_line; do

            [ "x`echo $type_line`" = 'x' ] && continue  

            #ext=`echo $type_line | grep -E '^[a-zA-Z0-9_]+' | cut -d " " -f1`
            ext=`echo $type_line | sed -r 's/^([a-zA-Z0-9_]+).*/\1/'`
            params=`echo $type_line | sed -r 's/^[a-zA-Z0-9_]+[ ]*\((.*)\).*/\1/;ta;d;:a'`
            act=`echo $type_line | sed -r 's/^[a-zA-Z0-9_]+[ ]*(\(.*\))?(.*)/\2/'`
            [ "x$params" != "x" ] && params=",$params"

            echo $list_line | sed -r \
            -e "s/^([a-zA-Z0-9_]+)[ ]*\(([a-zA-Z0-9_,]+)\).*/%2%_\1_$ext(\2$params)$spacer%2%_\1_ac$spacer(\2,$act)/" \
            -e 's/[ ]*,[ ]*/,/g' \
            -e "`sed_pad_right $align2 $spacer 1`" \
            -e "`sed_pad_right $align3 $spacer 2`" \
            -e "s/.*/$define&/" \
            >> $out
        done
        echo >> $out
    done
}


function replace_prefixes {

    temp=.temp

    file=`cat $prefix_file`

    prefix1=
    prefix2=

    for prefix in $file ; do
        [ "x$prefix2" != "x" ] && echo "Too many prefixes in prefix file $prefix_file" && exit 1
        [ "x$prefix1" != "x" ] && prefix2=$prefix && continue
        prefix1=$prefix
    done

    sed -r "s/%1%/$prefix1/g;s/%2%/$prefix2/g" < $1 > $temp

    mv -f $temp $1
}


function replace_other_strings {

    temp=.temp

    cat $reps_file | while read line; do

        rep1=
        rep2=

        for rep in $line ; do
            [ "x$rep2" != "x" ] && echo "Too many replacments in replacements file $reps_file" && exit 1
            [ "x$rep1" != "x" ] && rep2=$rep && continue
            rep1=$rep
        done

        sed -r "s/%$rep1%/$rep2/g" < $1 > $temp
        mv -f $temp $1
    done
}


function generate_header_file {

    name=$1
    out=$output_dir/$file_prefix$name.h

    add_notice $out
    add_non_generated_content $name $out
    add_action_macros $name $out
    replace_prefixes $out
    replace_other_strings $out
}


function add_auto_include {
    echo "#include  <${file_prefix}$2>" >> $1
}


function add_include {
    echo "#include  <${include_prefix}${file_prefix}$2>" >> $1
}


function generate_non_optional_h_includes {

    # TODO : split into auto-generated and non-auto-generated ones

    echo "/* non-optional includes */" >> $1
    echo >> $1

    for mod in `cat $headers_file | sort`; do

        add_auto_include  $1  $mod.h
    done

    echo >> $1
    echo >> $1
}


function generate_include_all_includes {

    echo "/* include all optional modules */" >> $1
    echo >> $1
    echo "#ifdef NDK_ALL" >> $1
    echo >> $1

    modules=`cat $optional_modules_file | sed 's/*//g' | sort`

    for mod in $modules; do
        def="NDK_`strtoupper $mod`"
        echo "#ifndef $def" >> $1
        echo "#define $def 1" >> $1
        echo "#endif" >> $1
    done

    echo >> $1
    echo "#endif" >> $1
    echo >> $1
    echo >> $1
}


function generate_dependent_includes {

    echo "/* module dependencies */" >> $1
    echo >> $1

    cat $module_dependencies_file | while read line; do

        first=1

        for mod in $line; do

            def="NDK_`strtoupper $mod`"

            if [ $first = 1 ] ; then

                echo "#ifdef  $def" >> $1
                first=0
            else
                echo "#ifndef $def" >> $1
                echo "#define $def 1" >> $1
                echo "#endif" >> $1
            fi
        done

        [ $first = 0 ] && echo "#endif" >> $1
    done

    echo >> $1
    echo >> $1
}



function generate_optional_h_includes {

    echo "/* optional includes */" >> $1
    echo >> $1

    for mod in $modules; do
        def="NDK_`strtoupper $mod`"
        echo "#if ($def)" >> $1
        add_include  $1  $mod.h
        echo "#endif" >> $1
    done

    echo >> $1
    echo >> $1
}





function generate_conf_merge_macros_file {

    file=$conf_merge_filename
    out_file=${file_prefix}$file
    out=$output_dir/$out_file

    add_notice $out

    echo "/* conf-merge-value macros */" >> $out
    echo >> $out

    cat $srcs_dir/$file | trim_lines >> $out
    echo >> $out
    echo >> $out

    echo "/* conf-merge-prop macros */" >> $out
    echo >> $out

    echo "#define     ndk_conf_merge_prop(prop,default)\\" >> $out
    echo "            ndk_conf_merge_value\\" >> $out
    echo "                (conf->prop, prev->prop, default)" >> $out
    echo >> $out

    # loads macros, removes empty elements, sorts and translates to merge-prop macros

    cat $conf_macros_file | sed -r 's/^[A-Z0-9_]+[ ]*[A-Z0-9_]+[ ]*([a-z0-9_]+).*$/\1/;ta;d;:a' \
        | sort | sed -r \
        's/(.*)/#define     ndk_conf_merge_\1_prop(prop,default,...)\\\
            ndk_conf_merge_\1_value\\\
                (conf->prop, prev->prop, default,##__VA_ARGS__)\
    /' \
    >> $out

    add_auto_include $1 $file
}




function generate_conf_cmd_basic_file {

    temp=.rep
    file=$conf_cmd_basic_filename
    out_file=${file_prefix}$file
    out=$output_dir/$out_file

    align1=35

    # initial text

    add_notice $out


    # add ndk bitmasks

    echo "/* conf cmd core values/bitmasks */" >> $out
    echo >> $out


    cat $conf_args_file | sort | trim_lines | sed -r \
    -e "s/^([A-Z0-9_]+)/${define}NDK_\1${spacer}NGX_\1/" \
    -e $sed_delete_empty_lines \
    -e  "`sed_pad_right $align1 $spacer`" \
    >> $out

    echo >> $out
    echo >> $out


    # add additional bitmasks stored in file

    echo "/* conf cmd bitmasks */" >> $out
    echo >> $out

    cat $srcs_dir/$conf_cmd_basic_filename | trim_lines >> $out

    echo >> $out
    echo >> $out

    echo "/* conf cmd basic macros */" >> $out
    echo >> $out


    # build replacement string

    echo -n "s/^([A-Z0-9_]+)$/" > $temp

    cat $conf_locs_file | sed \
    -r -e 's/^([A-Z0-9_]+) *([A-Z0-9_]+).*/#define     NDK_\1_CONF_\\1\(name,func,off1,off2,post)\\\\\\\
                                    {ngx_string (name),\\\\\\\
                                    NGX_CONF_\\1|NDK_\1_CONF,\\\\\\\
                                    func, off1, off2, post},\\\
\\/' -e $sed_delete_empty_lines \
    >> $temp

    echo -n "/;$sed_delete_empty_lines" >> $temp


    # apply the replacement string to the 

    cat $conf_args_file | sort | trim_lines | sed -rf $temp >> $out
    rm -f $temp

    add_auto_include $1 $file
}



function generate_conf_cmd_extra_file {

    temp=.rep
    file=$conf_cmd_extra_filename
    out=$output_dir/${file_prefix}$file

    align1=35

    # initial text

    add_notice $out

    echo "/* conf command macros */" >> $out
    echo >> $out


    # build replacement string

    echo -n 's/^([A-Z0-9_]+)[ ]*([A-Z0-9_]+)[ ]*([a-z0-9_]+).*$/' > $temp

    cat $conf_locs_file | sed \
    -r -e 's/^([A-Z0-9_]+)[ ]*([A-Z0-9_]+)[ ]*([a-zA-Z0-9_]+)/#define     NDK_\1_CONF_\\1(name,p,post\)\\\\\\\
            NDK_\1_CONF_\\2\\\\\\\
                (name,\\\\\\\
                ndk_conf_set_\\3_slot,\\\\\\\
                NGX_\2_CONF_OFFSET,\\\\\\\
                offsetof (ndk_module_\3_conf_t, p),\\\\\\\
                post)\\\
\\/' -e $sed_delete_empty_lines \
    >> $temp

    echo -n "/;$sed_delete_empty_lines" >> $temp
    #echo -n ";`sed_pad_right 60 $spacer`" >> $temp


    # apply the replacement string to the 

    cat $conf_macros_file | sort | trim_lines | sed -rf $temp -e "`sed_pad_right 60 $spacer`" >> $out
    rm -f $temp

    add_auto_include $1 $file
}



function generate_auto_generated_h_includes {

    echo "/* auto-generated headers */" >> $1
    echo >> $1

    for name in `cat $header_file | sort` ; do

        generate_header_file $name
        add_auto_include $1 $name.h
    done

    generate_conf_merge_macros_file $1
    generate_conf_cmd_basic_file $1
    generate_conf_cmd_extra_file $1

    echo >> $1
    echo >> $1
}



function generate_optional_c_includes {

    echo "/* optional includes */" >> $1
    echo >> $1

    modules=`cat $optional_modules_file | sed 's/*//g' | sort`

    for mod in $modules; do
        def="NDK_`strtoupper "$mod"`"
        echo "#if ($def)" >> $1
        add_include $1 $mod.c
        echo "#endif" >> $1
    done

    echo >> $1
    echo >> $1
}


function generate_commands {

    echo "/* module commands */" >> $1
    echo >> $1
    echo "static ngx_command_t  ndk_http_commands[] = {" >> $1

    cat $optional_modules_file | sort | while read line; do

        cmds=`echo "$line" | grep -E '\*'`

        [ "x$cmds" = "x" ] && continue

        mod=`echo "$line" | grep -E '^[a-zA-Z0-9_]+' | cut -d " " -f1`
        up=`strtoupper $mod`
        def="NDK_$up"
        defcmd="NDK_${up}_CMDS"

        echo "#if ($def)"                   >> $1
        echo "#define $defcmd 1"            >> $1
        add_include  $1  $mod.h
        echo "#undef  $defcmd"              >> $1
        echo "#endif"                       >> $1

    done

    echo -e "    ngx_null_command\n};"      >> $1
}


function generate_all_h_files {

    out=$output_dir/${file_prefix}$auto_file_name.h

    add_notice $out

    generate_include_all_includes $out
    generate_dependent_includes $out

    out=$output_dir/${file_prefix}$auto_includes_name.h

    generate_optional_h_includes $out
    generate_non_optional_h_includes $out
    generate_auto_generated_h_includes $out $list
}


function generate_all_c_files {

    out=$output_dir/${file_prefix}$auto_file_name.c

    add_notice $out
    generate_optional_c_includes $out
    generate_commands $out
}



function generate_all_files {

    mkdir -p $output_dir
    rm -f $output_dir/*

    generate_all_h_files
    generate_all_c_files
}

generate_all_files
