#!/bin/bash

_vw_bin_name="$0"

_vw_version_major="1"
_vw_version_minor="4"
_vw_version_ext=""
_vw_version="${_vw_version_major}.${_vw_version_minor}${_vw_version_ext}"

[[ $_vw_version_major == 0 ]] && echo "Vivado wrapper is unfinished, and unable to work." && exit 11
[[ $_vw_bin_name == '' ]] && _vw_bin_name=vivado-wrapper

trap "exit 9" TERM
export _vw_mypid=$$
shopt -s nullglob

function show_help () {
    echo "Vivado wrapper ${_vw_version}
Usage:
    ${_vw_bin_name} <SubCommand> [Args ...]
        Run SubCommand.
    ${_vw_bin_name} --help
        Show this help message.
    
    You must set environment variable 'vivado_exec' properly.

SubCommands:
    init
        Init a new, empty project directory, with Vivadofile template and build/constrain directory.

    build (Vivadofile required)
        Build current project, using ./Vivadofile as configuration file.
        --top <top_module_name>
            Override top module appointed in Vivadofile.
        --constraint <path/to/constraint.xdc>
            Override the constraint file appointed in Vivadofile.
        
    burn (Vivadofile required)
        Burn compiled top_module bit file into hardware board.
        --top <top_module_name>
            Override top module appointed in Vivadofile.
        --device <device_name>
            Device name to burn into. Auto-detect if not appointed.

    gui (Vivadofile required)
        Launch vivado GUI, which has opened this project.
        Warning: All modification to sources/constraints will be saved to origin project. All 
          modification to other project-level configurations, adding or removing sources, will 
          be discarded.

    burn-file <file_name>
        Burn a bit file into hardware board.
        --device <device_name>
            Device name to burn into. Auto-detect if not appointed.

Examples:
    ${_vw_bin_name} init
    ${_vw_bin_name} build
    ${_vw_bin_name} burn
    ${_vw_bin_name} build --top some_other_module
    ${_vw_bin_name} burn-file build/another_module.bit
    "
}

function echo2 () {
    echo $@ > /dev/fd/2
}
function echo_err () {
    echo2 "[VIVADOW ERROR] " $@
}
function echo_warn () {
    echo2 "[VIVADOW WARNING] " $@
}
function echo_info () {
    echo2 "[VIVADOW INFO] " $@
}
function echo_debug () {
    [[ $_vw_version_ext == 'dev' ]] && echo2 "[VIVADOW DEBUG] " $@
}

function where_is_him () {
    SOURCE="$1"
    while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
        DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
        SOURCE="$(readlink "$SOURCE")"
        [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
    done
    DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
    echo -n "$DIR"
 }

function where_am_i () {
    _my_path=`type -p ${_vw_bin_name}`
    where_is_him "$_my_path"
}

function naive_opt () {
    _opt_prev=""
    for opt in $@; do
        [[ $_opt_prev == '--top' ]] && top_module="$opt"
        [[ $_opt_prev == '--device' ]] && device_name="$opt"
        [[ $_opt_prev == '--constraint' ]] && constr_path="$opt"
        _opt_prev="$opt"
    done
}

function import_vivadofile_impl () {
    [[ -e ./Vivadofile ]] && source ./Vivadofile && return 0
    [[ -e ./vivadofile ]] && source ./vivadofile && return 0
    [[ -e ./VivadoFile ]] && source ./VivadoFile && return 0
    [[ -e ./VIVADOFILE ]] && source ./VIVADOFILE && return 0
    return 1
}

function import_vivadofile () {
    import_vivadofile_impl
    [[ $? == 1 ]] && echo_err 'Vivadofile, vivadofile, VivadoFile, VIVADOFILE not found.' && return 1
    [[ ! -e ${vivado_exec} ]] && echo_err "vivado_exec '${vivado_exec}' not found." && return 1
    [[ ! -x ${vivado_exec} ]] && echo_err "vivado_exec '${vivado_exec}' not executable." && return 1
    [[ "${thread_num}" == '' ]] && thread_num=1
    [[ "${sources[*]}" == '' ]] && echo_err "sources not provided." && return 1
    [[ "${bit_dir}" == '' ]] && echo_err "bit_dir not provided." && return 1
    [[ "${top_modules[*]}" == '' ]] && echo_err "top_modules not provided." && return 1
    [[ "${top_module}" == '' ]] && echo_err "top_module not provided." && return 1
    return 0
}

function get_constraint_of_module () {
    # stdout of this function is used as return val.
    # Given module name, echo full constr path. Need vivadofile.
    _mod_name="$1"
    for _ele in "${top_modules[@]}" ; do
        _key=${_ele%%:*}
        _value=${_ele#*:}
        if [[ ${_key} == ${_mod_name} ]]; then
            [[ -f "${_value}" ]] && echo -n "${_value}" && return 0
            echo_err "constraint file '${_value}' not found."
        fi
    done
    echo_err "Can not find constraint for module '$_mod_name'"
    # `exit 9` won't work here. So I'm killing myself.
    kill -s TERM $_vw_mypid
    # exit
}

function vivado_check_and_init_template () {
    [[ -z "$board" ]] && echo_err "You must set variable 'board'. Try \`vivadow init\` again." && return 4
    [[ -d "$my_path/template/$board" ]] && return 0

    echo_warn "Creating a new template for board $board. You may need permission to write to installation directory $my_path/template/"
    "$my_path/gen_tcl.sh" init-project temp_project "$my_path/template/$board" "$board" > $temp_dir/sh.tcl
    "$vivado_exec" -mode batch -source "$temp_dir/sh.tcl" -nojournal -nolog || return $?
    rm "$my_path/template/$board/temp_project.srcs/constrs_1/new/constraint.xdc"
    rm -rf "$my_path/template/$board/temp_project.cache/"*
}

function generate_real_project () {
    # Init the real project
    vivado_check_and_init_template || return 4
    cp -r "$my_path/template/"* "$temp_dir/"
    ln -s "$temp_dir/$board" "$temp_dir/project"

    # Convert vwc constraints to xdc here
    [[ "$constr_path" == '' ]] && constr_path="$(pwd)/$(get_constraint_of_module $top_module)"
    [[ ${constr_path: -4} == ".vwc" ]] && "$my_path/vwc2xdc.sh" "$constr_path" > "$temp_dir/generated.xdc" && constr_path="$temp_dir/generated.xdc"

    # Move sources and constraints
    echo_debug ${#sources[@]} ${sources[@]}
    [[ ${#sources[@]} -eq 0 ]] && echo_warn "No source file detected. However, I'll continue and have a try."
    _real_proj_src="$temp_dir/project/temp_project.srcs"
    for src in `echo ${sources[@]}`; do
        mkdir -p "$_real_proj_src/sources_1/new/$(dirname "$src")"
        ln -s "$(pwd)/$src" "$_real_proj_src/sources_1/new/$src"
    done
    ln -s "$constr_path" "$_real_proj_src/constrs_1/new/constraint.xdc"
    echo_info "real_project generated at $temp_dir"
}

function clean_real_project () {
    rm -rf $temp_dir ./.Xil
    echo_info "real_project cleaned"
}

function cp_with_backup () {
    a="$1"
    b="$2"
    [[ -f "$b" ]] && mv "$b" "$b.backup"
    cp "$a" "$b"
}

function do_init () {
    mkdir -p constraint build src
    cp_with_backup "$my_path"/template/Vivadofile ./Vivadofile
    echo_info "I'll provide a constraint file for xc7a100tcsg324-1, which is used by HUST students. Remove it if it's not your case."
    cp_with_backup "$my_path"/template/xc7a100tcsg324-1.xdc ./constraint/xc7a100tcsg324-1.xdc
    cp_with_backup "$my_path"/template/xc7a100tcsg324-1.vwc ./constraint/xc7a100tcsg324-1.vwc
    echo_info "Vivadow project inited."
}

function do_build () {
    generate_real_project
    [[ $? -ne 0 ]] && echo_err "Generate real project failed." && clean_real_project && return 4

    "$my_path/gen_tcl.sh" build "$temp_dir/project/temp_project.xpr" synth_1 impl_1 write_bitstream "$top_module" $thread_num > $temp_dir/sh.tcl
    "$vivado_exec" -mode batch -source "$temp_dir/sh.tcl" -nojournal -nolog
    _ret=$?
    [[ $_ret -ne 0 ]] && echo_err "Build failed. Please check previous error report." && clean_real_project && return $_ret
    _bit_file="$temp_dir/project/temp_project.runs/impl_1/$top_module.bit"
    [[ -e "$_bit_file" ]] && cp "$_bit_file" "$bit_dir/$top_module.bit"
    [[ $? -ne 0 ]] && echo_err "vivado-wrapper: Error: Build failed. Please check previous error report." && clean_real_project && return 5

    clean_real_project
}

function burn_file () {
    file_to_burn="$1"
    "$my_path/gen_tcl.sh" burn-file "$file_to_burn" "$device_name" > $temp_dir/sh.tcl
    "$vivado_exec" -mode batch -source "$temp_dir/sh.tcl" -nojournal -nolog
    return $?
}

function do_burn () {
    burn_file "$bit_dir/$top_module.bit"
}

function do_gui () {
    generate_real_project

    "$my_path/gen_tcl.sh" gui "$temp_dir/project/temp_project.xpr" > $temp_dir/sh.tcl
    "$vivado_exec" -mode batch -source "$temp_dir/sh.tcl" -nojournal -nolog

    clean_real_project
}

my_path=`where_am_i`
temp_dir=`mktemp -d`
# If noob user add space character in $1, just truncate it.
vw_cmd=$1
shift

[[ $vw_cmd == '' ]] && show_help && exit 1
[[ $vw_cmd == '--help' ]] && show_help && exit 0
if [[ $vw_cmd == 'build' ]] || [[ $vw_cmd == 'burn' ]] || [[ $vw_cmd == 'gui' ]]; then
    import_vivadofile
    [[ $? != 0 ]] && echo_err "Vivadofile error reported. Exiting..." && exit 2
fi

naive_opt $@

case $vw_cmd in
    'init' )
        do_init
        ;;
    'build' )
        do_build
        ;;
    'burn' )
        do_burn
        ;;
    'gui' )
        do_gui &
        ;;
    'burn-file' )
        burn_file $1
        ;;
    * )
        echo_err "Unknown command '${vw_cmd}', try '${_vw_bin_name} --help'"
        ;;
esac

exit $?
