#!/bin/bash
projectPattern="*.uvprojx";

if [ "$#" -gt 1 ]
then
    1>&2 echo "Error: Invalid number of arguments: $#";
fi;

if [ "$#" -ne 1 ] || [ "$1" == "-h" ] || [ "$1" == "--help" ]
then
    echo "Usage: ${BASH_SOURCE[0]} <path-to-project-or-source-file>";
    exit;
fi;



function handle_status() {
    if [ $1 -ne 0 ]; then
        echo "Error: $2 (status code: $1)";
        exit $1;
    fi
}

function regex_escape() {
    echo "$1" | sed "s/[^\^]/[&]/g; s/\^/\0/g";
}

function normalize_path() {
    echo "$1" | sed "s/\\\\/\\//g";
}

function getTargetXPath() {
    index="$1";
    echo -n "Project/Targets/Target";

    if [ ! -z "$index" ]
    then
        echo -n "[$index]";
    fi;

    echo "";
}

function getFileXPath() {
    index="$1";
    echo "$(echo -n "$(getTargetXPath "$index")")/Groups/Group/Files/File/FilePath";
}

function getOptionPath() {
    index="$1";
    echo "$(echo -n "$(getTargetXPath "$index")")/TargetOption/TargetCommonOption";
}

function find_files() {
    local -n files=$1;
    arguments=("${@:2}");
    readarray -d '' files < <(find "${arguments[@]}" -print0);
    return;
}

function find_project() {
    file="$1";
    currentDir="$(dirname "$file")";
    find_files projectFiles "$currentDir" -name "$projectPattern";

    while true;
    do
        for projectFile in "${projectFiles[@]}"
        do
            if containsFile "$file" "$projectFile"
            then
                echo "$projectFile";
                return;
            fi;
        done;

        if [ "$currentDir" != "/" ];
        then
            currentDir="$(dirname "$currentDir")";
            find_files projectFiles "$currentDir" -maxdepth 1 -name "$projectPattern";
        else
            break;
        fi;
    done;
}

function containsFile() {
    local i;
    file="$1";
    projectFile="$2";
    targetIndex="$3";
    xpath="$(getFileXPath "$targetIndex")";

    relativePath="$(realpath --relative-to "$(dirname "$projectFile")" "$(dirname "$file")")";
    filePattern="$(regex_escape "$(basename "$file")")";

    while [ "$relativePath" != "." ];
    do
        filePattern="$(regex_escape "$(basename "$relativePath")")"'[/\\\\]'"$filePattern";
        relativePath="$(dirname "$relativePath")";
    done;

    fileCount="$(xmlstarlet select --template --copy-of "count($xpath)" "$projectFile")";

    for i in $(seq 1 $fileCount)
    do
        currentFileName="$(xmlstarlet select --template --match "($xpath)[$i]" --value-of . "$projectFile")";
        currentFileName="$(normalize_path "$currentFileName")";
        currentFileName="$(realpath --relative-to=. -m "$currentFileName")";

        if echo "$currentFileName" | grep "^$filePattern$" > /dev/null
        then
            true;
            return;
        fi;
    done;

    false;
}

function chooseTarget() {
    local i;
    projectFile="$1";
    file="$2";
    targetCandidates=();
    targetCount="$(xmlstarlet select --template --copy-of "count($(getTargetXPath))" "$projectFile")";

    if [ $targetCount -gt 0 ]
    then
        for i in $(seq 1 $targetCount)
        do
            if containsFile "$file" "$projectFile" "$i"
            then
                targetCandidates+=("$i");
            fi;
        done;

        if [ "${#targetCandidates[@]}" = 0 ]
        then
            targetCandidates=($(seq 1 "$targetCount"));
        fi;

        while [ ! "${#targetCandidates[@]}" = 1 ]
        do

            echo "Which Target Would You Like to Build?";

            for j in $(seq 1 "${#targetCandidates[@]}")
            do
                index="${targetCandidates["$(expr $j - 1)"]}";
                targetName="$(xmlstarlet select --template --match "$(getTargetXPath "$index")/TargetName" --value-of . "$projectFile")";
                echo "$(printf "%${#targetCount}s" "$index"): \"$targetName\"";
            done;
            read -p "Enter your choice: " choice;

            if [ $choice -gt 0 ] && [ $choice -le "${#targetCandidates[@]}" ]
            then
                targetCandidates=("${targetCandidates[$(expr $choice - 1)]}");
            fi;
        done;

        return "${targetCandidates[0]}";
    else
        handle_status 1 "The current project '$projectFile' doesn't contain any targets!";
    fi;
}

fileName="$(realpath "$1")";
test -f "$fileName";
handle_status "$?" "The specified file \"$1\" does not exist!";

if [[ "$fileName" =~ \.uvprojx$ ]]
then
    projectFile="$fileName";
else
    projectFile="$(find_project "$fileName")";
fi

if [ ! -z "$projectFile" ];
then
    projectRoot="$(dirname "$projectFile")";
    cd "$projectRoot";
    tempFile="$(mktemp)";
    cp -f "$projectFile" "$tempFile";

    chooseTarget "$projectFile" "$fileName";
    targetIndex="$?";

    targetPath="$(getTargetXPath "$targetIndex")";
    optionPath="$(getOptionPath "$targetIndex")";

    fileCount="$(xmlstarlet select --template --copy-of "count($(getFileXPath))" "$projectFile")";
    targetName="$(xmlstarlet select --template --match "$targetPath/TargetName" --value-of . "$projectFile")";
    buildPath="$(xmlstarlet select --template --match "$optionPath/OutputDirectory" --value-of . "$projectFile")";
    outputName="$(xmlstarlet select --template --match "$optionPath/OutputName" --value-of . "$projectFile")";
    buildPath="$(normalize_path "$buildPath")";
    outputName="$(normalize_path "$outputName")";

    echo "Building Target \"$targetName\"";
    echo "";

    for i in $(seq 1 $fileCount)
    do
        xpath="($(getFileXPath))[$i]"
        currentFileName="$(xmlstarlet select --template --match "$xpath" --value-of . "$projectFile")";
        currentFileName="$(normalize_path "$currentFileName")";
        xmlstarlet edit --inplace --update "$xpath" --value "$currentFileName" "$projectFile";
    done;

    logFile="$(mktemp)";

    WINEDEBUG=-all,fixme+event xvfb-run bash -c "dwm & wine \"C:\Keil_v5\UV4\UV4.exe\" -j0 -b \"$projectFile\" -t \"$targetName\" -l \"$logFile\"";
    mv -f "$tempFile" "$projectFile";

    errorPatterns=()
    errorPatterns+=("\(\)\(.*\)\(([[:digit:]]\+): \(warning\|note\|error\): \([A-Z0-9]\+: \)\?.*\)$")
    errorPatterns+=("\(\"\)\(.*\)\(\", line [[:digit:]]\+\( (column [[:digit:]]\+)\)\?: \(Warning\|Error\): [A-Z0-9]\+: .*\)$");

    for errorPattern in "${errorPatterns[@]}"
    do
        sed -i \
            -e "/$errorPattern/{" \
            -e "h;" \
            -e "s/$errorPattern/\1\n\3/" \
            -e "x; s/$errorPattern/realpath \"\2\"/" \
            -e "e" \
            -e "x; G; h;" \
            -e "s/\([^\n]*\)\n//" \
            -e "s/\([^\n]*\)\n\(.*\)/\2\1/" \
            -e "x; s/\([^\n]*\)\n\(.*\)$/\1/" \
            -e "G; s/\n//" \
            -e "}" "$logFile";
    done;

    cat "$logFile";

    if grep -q " 0 Error(s)" "$logFile";
    then
        outputName="$(realpath "$projectRoot/$buildPath/$outputName")";
        outputFile="$outputName.axf";
        echo "The File Has Been Built to \"$outputFile\".";
        pyocd list | {
            grep '^No available' &&
            {
                echo "Skpping flash."
            } ||
            {
                pyocd flash --target stm32f439xi "$outputFile";
                handle_status $? "Flashing failed";
            };
        };
    else
        handle_status 1 "The project could not be built!";
    fi;
else
    handle_status 1 "No project file for \"${fileName}\" could be found."
fi;