#!/bin/bash

_self_bin_name="$0"
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 ${_self_bin_name}`
    [[ "$_my_path" = "" ]] && where_is_him "$_self_bin_name" || where_is_him "$_my_path"
}

source "$(where_am_i)/antidote.config.sh" || ! echo "Failed to source config file" || exit 2

function echo2 () {
    echo "$@" 1>&2
}

function curl_wrapped () {
    # Add some options for every curl request, basing on config file. 
    curl_options=( -s --http1.1 --max-time 30 ) # Old version of curl failed to fallback to http1.1 on http2 error. 
    [[ "$cis_bearer" != "" ]] && curl_options+=( -H "Authorization: $cis_bearer" )
    [[ "$cis_cookie" != "" ]] && curl_options+=( --cookie "$cis_cookie" )
    [[ "$cis_bearer$cis_cookie" = "" ]] && echo2 "Warning: You must set either cis_cookie or cis_bearer to authenticate your CIS API call, but you have set neither. curl requests is very likely to fail!"

    # Getting http status is a trick...
    exec 3>&1
    for retry_count in {0..5}; do
        HTTP_STATUS=$(curl -w "%{http_code}" -o >(cat >&3) "${curl_options[@]}" "$@") && break
        ! echo2 "Error $? while executing 'curl ${curl_options[@]} $@', refer to https://man7.org/linux/man-pages/man1/curl.1.html (EXIT CODES) for more info. Retry $retry_count in 5..."
    done || exit $?

    # There is some expected 40x error in some API. Caller may set HTTP_IGNORED_ERROR_STATUS_LIST=405,413 to ignore some of them. 
    [[ "$HTTP_IGNORED_ERROR_STATUS_LIST" == *"$HTTP_STATUS"* ]] && return 0 
    [[ "$HTTP_STATUS" == 40* ]] || [[ "$HTTP_STATUS" == 50* ]] && echo2 "HTTP Error $HTTP_STATUS while sending HTTP request 'curl ${curl_options[@]} $@'. Double-confirm that this API is working with your cookie or token! " && return 2
    return 0
}

##############################################################################

function upload_to_smb_share () {
    # returns smb_url from stdout
    release_ver="$1"
    local_wf_dir="$2"

    # if local_wf_dir is already a samba url, we do nothing and return. 
    [[ "${local_wf_dir:0:6}" = smb:// ]] && echo "${local_wf_dir:4}" && return 0
    # TODO: Deprecate the old confusing format in the future. 
    [[ "${local_wf_dir:0:2}" = \\\\ ]] || [[ "${local_wf_dir:0:2}" = // ]] && echo "$local_wf_dir" && return 0

    # The workflow builder should copy all xaml file to there. 
    find "$local_wf_dir" -name '*.pdb' -exec rm -f '{}' ';'
    cmd="deltree /antidote-cis/$smb_namespace/$release_ver;mkdir /antidote-cis;mkdir /antidote-cis/$smb_namespace;mkdir /antidote-cis/$smb_namespace/$release_ver;mask \"\";recurse ON;prompt OFF;cd /antidote-cis/$smb_namespace/$release_ver;lcd $local_wf_dir;mput *"
    
    hash smbclient || ! echo "smbclient is not installed in this environment. Pushing from local directory is unavailable. Please push from existing smbshare with 'antidote-cis push //MY_SHARE/my/shared/folder'. " || exit 3
    smbclient "$smb_provider" --user "$smb_username%$smb_password" -c "$cmd" 1>&2 || return 2
    smb_url="$smb_provider/antidote-cis/$smb_namespace/$release_ver"
    echo "$smb_url"
}

# CIS upload pkg
function cis_upload_pkg () {
    workflow_name="$1"
    release_ver="$2"
    pkg_path="$3"

    if [[ "${pkg_path:0:6}" = "cis://" ]]; then
        echo2 "Submitting request to upload package $cis_namespace.$workflow_name:$release_ver, using CIS internal package (trigger argless upload)..."
        curl_wrapped --data '[{"Cloud":"Public","PackageType":"'"$cis_namespace.$workflow_name"'","Version":"'"$release_ver"'","PackageId":"00000000-0000-0000-0000-000000000000","ActionType":501,"Notes":"antidote-auto-upload","ActionArgs":{"PackageSourcePath":null,"ScriptParameters":""}}]' "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace.$workflow_name/Packages/Request" -H 'content-type: application/json'
    else
        smb_url=`upload_to_smb_share "$release_ver" "$pkg_path"` || return $?
        echo2 "Submitting request to upload package $cis_namespace.$workflow_name:$release_ver, using smb:$smb_url..."
        curl_wrapped --data '[{"Cloud":"Public","PackageType":"'"$cis_namespace.$workflow_name"'","Version":"'"$release_ver"'","PackageId":"00000000-0000-0000-0000-000000000000","ActionType":501,"Notes":"antidote-auto-upload","ActionArgs":{"PackageSourcePath":"'"$smb_url"'","ScriptParameters":""}}]' "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace.$workflow_name/Packages/Request" -H 'content-type: application/json'
    fi
    return $?
}

function try_grep_pkgid () {
    # returns pkgid from stdout
    workflow_name="$1"
    release_ver="$2"
    resp=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace.$workflow_name/Packages?null"` || return $?
    [[ "$resp" = "" ]] && echo2 "Invalid api response. Is cookie expired or not correctly set?" && return 2
    ! echo "$resp" | json2table Id,Version -p | grep -F "|$release_ver|" > /dev/null && echo2 "Error: release_ver $release_ver doesn't exist at all" && return 2

    # output pkgid
    pkgid=`echo "$resp" | json2table Id,Version -p | grep -v 00000000-0000-0000-0000-000000000000 | grep -F "|$release_ver|" | sed 's/|.*$//g'`
    [[ "$pkgid" = "" ]] && return 2
    echo "$pkgid"
}


# The only difference between release_pkg and set_default is: ActionType. One is 505, another is 503. 
function cis_release_pkg () {
    workflow_name="$1"
    release_ver="$2"
    echo2 "Release package $cis_namespace.$workflow_name:$release_ver..."
    pkg_id=`try_grep_pkgid "$workflow_name" "$release_ver"` || ! echo2 "Failed to get pkgid. Does release version exist?" || return $?
    curl_wrapped --data '[{"Cloud":"Public","PackageType":"'"$cis_namespace.$workflow_name"'","Version":"'"$release_ver"'","PackageId":"'"$pkg_id"'","ActionType":505,"Notes":"antidote-auto-release","ActionArgs":{"PackageSourcePath":null,"ScriptParameters":""}}]' "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace.$workflow_name/Packages/Request" -H 'content-type: application/json'
    return $?
}
function cis_set_default_pkgver () {
    workflow_name="$1"
    release_ver="$2"
    echo2 "Setting default version for $cis_namespace.$workflow_name:$release_ver..."
    pkg_id=`try_grep_pkgid "$workflow_name" "$release_ver"` || ! echo2 "Failed to get pkgid. Does release version exist?" || return $?
    curl_wrapped --data '[{"Cloud":"Public","PackageType":"'"$cis_namespace.$workflow_name"'","Version":"'"$release_ver"'","PackageId":"'"$pkg_id"'","ActionType":503,"Notes":"antidote-auto-release","ActionArgs":{"PackageSourcePath":null,"ScriptParameters":""}}]' "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace.$workflow_name/Packages/Request" -H 'content-type: application/json'
    return $?
}

function _select_param_and_create_json_impl () {
    # pick parameter from cis_default_workflow_parameter, and format them into result format. 
    # input: stdin, list of requested parameter name. Output: stdout. 
    while read -r requiredParam; do
        [[ "$requiredParam" = "" ]] && continue
        prefix="$requiredParam="
        for kv in "${cis_default_workflow_parameter[@]}"; do
            [[ "$kv" == "$prefix"* ]] && requiredParam_val="${kv:${#prefix}}" && requiredParam_val="${requiredParam_val/\\/\\\\}" # The backslash in json must be escaped. 
        done
        [[ "$requiredParam_val" = "" ]] && echo2 "Note: workflow parameter $requiredParam has an empty value."
        echo -n "\"$requiredParam\":\"$requiredParam_val\","
    done
}
function assemble_workflow_parameters () {
    # Use variable $cis_default_workflow_parameter, query workflow parameter list, and fill them. 
    # The tail of array variable $cis_default_workflow_parameter has higher preference (because params from command line are appended after tail)
    # 
    # returns incomplete json text in stdout, like this: 
    #   "param1":"val1","param2":"val2",
    workflow_name="$1"
    params_query_response=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/JobTypeDefinition/$cis_namespace"  | json2table WorkflowDefinitions/InputParameters,Name -p | grep "|$workflow_name|" | cut -d '|' -f 1` || ! echo2 "Failed to get Workflow definition for workflow $workflow_name" || return $?
    [[ "$params_query_response" = "[]" ]] && params="" || params=`echo "$params_query_response" | json2table Name -p | sed 's/VAL: //g' | grep -oE '[A-Za-z0-9_-]+'` || ! echo2 "Failed to get Workflow definition for workflow $workflow_name" || return $?

    echo "$params" | _select_param_and_create_json_impl
}
function assemble_runtime_settings () {
    # Use variable $cis_default_workflow_parameter, query runtime settings list, and fill them. 
    # TODO: Many runtime settings have default value, I want to load them into cis_default_workflow_parameter automatically in the FUTURE! 
    # The tail of array variable $cis_default_workflow_parameter has higher preference (because params from command line are appended after tail)
    # 
    # returns incomplete json text in stdout, like this: 
    #   "param1":"val1","param2":"val2",

    # runtime_settings are only related to namespace, so we don't need workflow_name. (We're adding an '@' to all param name here)
    params=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/JobTypeDefinition/$cis_namespace" | json2table GlobalSettings/Name -p | sed 's/VAL: /@/g' | tr -d '|'` || ! echo2 "Failed to get runtime settings for namespace $cis_namespace" || return $?

    echo "$params" | _select_param_and_create_json_impl | tr -d @
}

function cis_run_job () {
    # returns job_id in stdout. 
    workflow_name="$1"
    release_ver="$2"
    echo2 "Submitting request to create $workflow_name:$release_ver..."
    pkg_id=`try_grep_pkgid "$workflow_name" "$release_ver"` || ! echo2 "Failed to get pkgid" || return $?
    workflow_params=`assemble_workflow_parameters "$workflow_name"` || ! echo2 "Failed to assemble wf_parameter" || return $?
    runtime_settings=`assemble_runtime_settings` || ! echo2 "Failed to assemble runtime_settings" || return $?
    echo2 "Using workflow parameters: $workflow_params, runtime settings: $runtime_settings"
    job_id=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace/GenericJob?workflowDefinitionName=$workflow_name" -H 'content-type: application/json' --data '{"JobType":"'"$cis_namespace"'","Workflow":"'"$workflow_name"'","ServiceHostCapability":"AzureJB","ServiceHostLocation":"Any","RuntimePackageId":"4dd32e8b-6bc7-478e-a1ab-48fcdc1bf3d3","PackageId":"'"$pkg_id"'","DisplayName":"Antidote-autosubmitted-WF","WorkflowParameters":{'"$workflow_params"'},"RuntimeSettings":{'"$runtime_settings"'},"WorkflowSettings":{"RuntimeSettings":{'"$runtime_settings"'}}}'` || ! echo2 "failed to get job_id" || return $?
    [[ "$job_id" =~ ^[0-9][0-9]*_[0-9a-f-]*$ ]] || ! echo2 "Failed to create job. Invalid cookie or invalid workflow_name?" || return 3
    
    echo2 "Waiting for CIS to create job... (job_id=$job_id)"
    niddle_should_break='"DisplayStatus":"NotStarted"'
    niddle_should_crash='"CustomState":"Internal Error"'
    while true; do
        resp=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace/GenericJob/$job_id/GetJobHierarchy?null"`
        echo "$resp" | grep "$niddle_should_break" > /dev/null && break
        echo "$resp" | grep "$niddle_should_crash" > /dev/null && echo2 "CIS reported 'Internal Error' while creating job. https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$job_id" && return 3
        echo2 -n .
        sleep 5
    done
    
    echo2 "Starting job..."
    # There's some bug report saying that, this job-starting request failed. Let's validate and retry. 
    while true; do
        # http 204 means success. 
        curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace/GenericJob/$job_id/Start/" -X 'PUT' -H 'content-type: application/json' -H 'accept: application/json, text/javascript, */*; q=0.01' --data-raw '{"Notes":"antidote-autostart"}' -v 3>&2 2>&1 1>&3 | grep -F '204 No Content' > /dev/null && break
        sleep 2
    done
    echo2 "Job started. log_url = https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$job_id"

    echo "$job_id" # This is the output
    return $?
}

function cis_get_job_status () {
    # Argument should be job_id of *root* workflow. (not child workflow!)
    # Returns job status in stdout, maybe InProgress, NotStarted, Failed, Blocked, Finished, or some other unknown status. 
    # Returns job summary in stderr, in plain text or markdown format. 
    jobid="$1"
    getjobhierarchy_apires=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace/GenericJob/$jobid/GetJobHierarchy"` && 
    subworkflows_text=`echo "$getjobhierarchy_apires" | json2table DisplayName,Id,DisplayStatus,JobType -p` || ! echo2 "API GetJobHierarchy failed. Server says '$getjobhierarchy_apires'" || return $?
    callstack_formatted=`echo "$getjobhierarchy_apires" | json2table DisplayName,Id,DisplayStatus | grep -E '( InProgress )|( PlanError )'` # Ignore failure because it's only useful in InProgress and PlanError state. 
    _mdcode='```'

    if planerror_callstack=`echo "$subworkflows_text" | grep -F '|PlanError|'`; then
        # RemoteJob may have PlanError in subworkflow. The top-level workflow is still InProgress but bottom workflow got PlanError. 
        [[ -z "$callstack_formatted" ]] && echo2 "API GetJobHierarchy result misinterpreted. No workflow in stat InProgress or PlanError. This is an unexpected non-fatal error and I did not catch it. " && return 2
        [[ -z $ANTIDOTE_JOBSTAT_MARKDOWN ]] &&
            echo2 -e ">>> CIS reported PlanError:\nCallstack: \n$callstack_formatted\nCIS Log: https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$jobid" ||
            echo2 -e "## CIS reported PlanError\n\n[View CIS Log](https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$jobid)\n\n### Callstack\n\n$_mdcode\n$callstack_formatted\n$_mdcode\n"
        echo "Failed" && return 0
    fi
    if inprogress_callstack=`echo "$subworkflows_text" | grep -F '|InProgress|'`; then
        # There's some workflow in-progress. It maybe inprogress or blocked. 
        # Note that, there may be multiple workflows in "InProgress" state. In this scenario, 
        #   the last "InProgress" workflow is the bottom sub-workflow which we're interested in. 
        #   We just check the last sub-workflow, to see if there's any "Blocked" activity. 

        [[ -z "$callstack_formatted" ]] && echo2 "API GetJobHierarchy result misinterpreted. No workflow in stat InProgress or PlanError. This is an unexpected non-fatal error and I did not catch it. " && return 2

        # Remote workflow may have different namespace, so we need to get bottom_subworkflow_namespace. 
        bottom_subworkflow_id=`echo "$inprogress_callstack" | tail -n 1 | cut -d '|' -f 3` && 
        bottom_subworkflow_namespace=`echo "$inprogress_callstack" | tail -n 1 | cut -d '|' -f 4` && 
        bottom_wf_pagedtasks_apires=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$bottom_subworkflow_namespace/GenericJob/$bottom_subworkflow_id/WithPagedTasksAndIncidents"` && 
        bottom_wf_activities=`echo "$bottom_wf_pagedtasks_apires" | json2table Data/Tasks/DisplayName,StateName` || ! echo2 "API WithPagedTasksAndIncidents failed" || return $?
        if echo "$bottom_wf_activities" | tr -d ' ' | grep -F '|Blocked|' > /dev/null; then
            # I found some activity blocked! Show incident and exit. 
            icm_link=`echo "$bottom_wf_pagedtasks_apires" | json2table Data/Tasks/Incidents/ExternalLink -p | sed 's/VAL: //g' | tr -d '|'` || icm_link=""
            [[ -z $ANTIDOTE_JOBSTAT_MARKDOWN ]] &&
                echo2 -e ">>> Incident detected:\nCallstack: \n$callstack_formatted\nActivity: \n$bottom_wf_activities\nCIS Log: https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$jobid\nICM Link: $icm_link" ||
                echo2 -e "## Incident detected\n\n[View CIS Log](https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$jobid)\n\n### Callstack\n\n$_mdcode\n$callstack_formatted\n$_mdcode\n\n### Activity\n\n$_mdcode\n$bottom_wf_activities\n$_mdcode\n[View ICM Incident]($icm_link)"
            echo "Blocked" && return 0
        fi

        # if there's no activity "InProgress", it's not an error. that's a race condition. 
        echo "InProgress" && return 0
    fi

    # Since the first line is the root workflow. If it's not InProgress, then we can trust the DisplayStatus. Just print it out. 
    echo "$subworkflows_text" | grep -vF 'DisplayName|DisplayStatus|Id' | head -n 1 | cut -d '|' -f 2

    [[ -z $ANTIDOTE_JOBSTAT_MARKDOWN ]] &&
        echo2 -e "CIS Log: https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$jobid" ||
        echo2 -e "[View CIS Log](https://beta-cps.trafficmanager.net/Public/$cis_namespace/JobDetails/$jobid)"
    return $?
}

# CIS wait upload pkg success
function cis_wait_for_upload () {
    workflow_name="$1"
    release_ver="$2"
    echo2 "Waiting for CIS to create package..."
    while true; do
        echo2 -n .
        sleep 5

        # Fail if uploading job failed or blocked, success if pkgid is ready. 
        try_grep_pkgid "$workflow_name" "$release_ver" && return 0
        [[ $novalidate != 1 ]] && upload_jobid=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace.$workflow_name/Packages?null" | json2table Actions/ActionLink,Version -p | grep -F "|$release_ver|" | grep -oE '[0-9]+_[0-9a-f-]{36}'` || continue # Ignore the failure because jobid need time to generate. 
        if [[ "$upload_jobid" != "" ]]; then
            upload_jobstat=`cis_namespace=System cis_get_job_status "$upload_jobid"` || ! echo2 "Uploading request created successfully, but failed to check the status of uploading. The error checking is now disabled, and it might wait forever. " || novalidate=1
            [[ "$upload_jobstat" = "Blocked" ]] || [[ "$upload_jobstat" = "Failed" ]] && echo2 "Uploading job $upload_jobstat. Stop waiting..." && return 2
        fi
    done
}




function subcmd_push () {
    # No output. TODO: DO NOT release in push. Delete it and modify documents. 
    local_wf_dir="$1"
    workflow_name="$2"
    [[ "$workflow_name" = "" ]] && workflow_name="$cis_default_workflow_name"
    release_ver="$3"
    [[ "$release_ver" = "" ]] && release_ver="ricin.$RANDOM$RANDOM"
    echo "$release_ver" > "/tmp/.antidote-cis.$workflow_name-ver"

    cis_upload_pkg "$workflow_name" "$release_ver" "$local_wf_dir" &&
    cis_wait_for_upload "$workflow_name" "$release_ver" &&
    cis_release_pkg "$workflow_name" "$release_ver"
    return $?
}
function subcmd_setdef () {
    # No output. 
    workflow_name="$1"
    [[ "$workflow_name" = "" ]] && workflow_name="$cis_default_workflow_name"
    release_ver="$2"
    [[ "$release_ver" != "" ]] || release_ver=`cat "/tmp/.antidote-cis.$workflow_name-ver"` || ! echo2 "Error: Unable to determine workflow version" || exit 1
    echo "$release_ver" > "/tmp/.antidote-cis.$workflow_name-ver"

    cis_set_default_pkgver "$workflow_name" "$release_ver"
    return $?
}
function subcmd_release () {
    # No output. 
    workflow_name="$1"
    [[ "$workflow_name" = "" ]] && workflow_name="$cis_default_workflow_name"
    release_ver="$2"
    [[ "$release_ver" != "" ]] || release_ver=`cat "/tmp/.antidote-cis.$workflow_name-ver"` || ! echo2 "Error: Unable to determine workflow version" || exit 1
    echo "$release_ver" > "/tmp/.antidote-cis.$workflow_name-ver"

    cis_release_pkg "$workflow_name" "$release_ver"
    return $?
}
function subcmd_cloudrun () {
    # Output stdout: job_id on success
    workflow_name="$1"
    [[ "$workflow_name" = "" ]] && workflow_name="$cis_default_workflow_name"
    release_ver="$2"
    [[ "$release_ver" != "" ]] || release_ver=`cat "/tmp/.antidote-cis.$workflow_name-ver"` || ! echo2 "Error: Unable to determine workflow version" || exit 1
    echo "$release_ver" > "/tmp/.antidote-cis.$workflow_name-ver"

    cis_run_job "$workflow_name" "$release_ver"
    return $?
}
function subcmd_jobstatus () {
    # Output stdout: job status summary in plain text or markdown. 
    jobid="$1"
    [[ "$jobid" = "" ]] && echo2 "Error: subcommand 'jobstatus' requires argument 'jobid'" && exit 1

    cis_get_job_status "$jobid"
    return $?
}
function subcmd_listver () {
    # Output stdout: a list of version and state, seperated by |
    workflow_name="$1"
    [[ "$workflow_name" = "" ]] && workflow_name="$cis_default_workflow_name"

    resp=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/$cis_namespace.$workflow_name/Packages"` || return $?
    [[ "$resp" = "" ]] && echo2 "Invalid api response. Is cookie expired or not correctly set?" && return 2
    echo "$resp" | json2table Version,State -p
    return $?
}
function subcmd_xmlupload () {
    # Output stdout: Text message returned from CIS API. 
    jobtype_filename="$1"

    # 406 means "Identical Job type definition found. Upload completed with no-op."
    HTTP_IGNORED_ERROR_STATUS_LIST=406 curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/JobTypeDefinition/upload" -F "note=" -F "uploadfiles[]=@$jobtype_filename"
    return $?
}
function subcmd_xmldownload () {
    # Output stdout: Downloaded JobType XML text. 
    apires=`curl_wrapped "https://beta-cps.trafficmanager.net/cis.client.svc/Public/JobTypeDefinition/rawdata/$cis_namespace?null"` || ! echo "API failed to download jobtype XML: $? $apires" || return $?

    # Remove the first & last character from result, and then unescape
    printf "%b" "${apires:1:${#apires}-2}" | sed 's/\\"/"/g'
    return 1
}

# Initialization
antidote_version="1.1.11"
subcmd="$1"
shift

# Retrieve all workflow parameters (name=value) from arg array. Runtime settings starts with @
filteredArgs=()
for arg in "$@"; do
    [[ "$arg" == *'='* ]] && cis_default_workflow_parameter+=("$arg") || filteredArgs+=("$arg")
done

# Call the actual subcommand
case "$subcmd" in
    push)
        subcmd_push "${filteredArgs[@]}" ; exit $?
        ;;
    setdef)
        subcmd_setdef "${filteredArgs[@]}" ; exit $?
        ;;
    cloudrun)
        subcmd_cloudrun "${filteredArgs[@]}" ; exit $?
        ;;
    jobstatus)
        subcmd_jobstatus "${filteredArgs[@]}" ; exit $?
        ;;
    release)
        subcmd_release "${filteredArgs[@]}" ; exit $?
        ;;
    listver )
        subcmd_listver "${filteredArgs[@]}" ; exit $?
        ;;
    jobtype-upload )
        subcmd_xmlupload "${filteredArgs[@]}" ; exit $?
        ;;
    jobtype-download )
        subcmd_xmldownload "${filteredArgs[@]}" ; exit $?
        ;;
    *)
        echo2 "
Antidote-CIS version $antidote_version, maintained by Recolic Keghart <root@recolic.net> or Bensong Liu <bensl@microsoft.com> (the same person!)

[[ Please read and modify antidote.config.sh before using this tool! ]]

Usage:
       $0 push path/to/your/workflow/dir [BootstrapCAWorkflow] [v4.1.1]
       $0 push smb://YOUR_HOSTNAME/shared_folder/your/workflow/dir [BootstrapCAWorkflow] [v4.1.1]
       $0 push cis:// [BootstrapCAWorkflow] [v4.1.1]
       $0 release [BootstrapCAWorkflow] [v4.1.1]
       $0 setdef [BootstrapCAWorkflow] [v4.1.1]
       $0 cloudrun [BootstrapCAWorkflow] [v4.1.1] [ParameterName=ParameterValue ...]
       $0 listver [BootstrapCAWorkflow]
       $0 jobstatus <JOB_ID>
       $0 jobtype-upload path/to/your/jobtype.xml
       $0 jobtype-download

The 'push' subcommand uploads your workflow to CIS, and release it. 
The 'release' subcommand releases your package. 
The 'setdef' subcommand set a released package as the default version. 
The 'cloudrun' subcommand would run the workflow on CIS. It doesn't wait for job completion. 
The 'listver' subcommand shows all versions of your workflow. 
The 'jobstatus' subcommand would show the status of specified job. It's usually 'InProgress', 'Blocked' or 'Finished'. 
The 'jobtype-upload' subcommand uploads a jobtype definition XML to CIS. Some app might need extra permission to access this feature. 
The 'jobtype-download' subcommand downloads the jobtype definition XML of current cis_namespace to STDOUT. 

You can push from local directory, CORPNET samba share, or CIS itself. Refer to README.md for more info. 
If you omit the version number, 'push' would generate a random verion number, while other subcommands would use the version number in your last push.
    WARNING: OMITTING VERSION NUMBER IS NOT USING THE DEFAULT VERSION OF WORKFLOW!
If you omit the workflow name, antidote would use the cis_default_workflow_name in antidote.config.sh.
The workflow_parameters in command line would overwrite workflow_parameters in antidote.config.sh. 
    You may set a default value for workflow_parameters in antidote.config.sh, and they could be overwritten by command line parameters. 
    If your workflow parameter has 'bool' type, use string 'true' or 'false' as its value. 
    ParameterName with a leading '@' is 'RuntimeSettings', refer to README.md for more info. 
    ParameterName and ParameterValue must not contain punctuation mark (').

Examples: 
antidote-cis push ../bin/net472 BootstrapCAWorkflow
antidote-cis push smb://reddog/builds/branches/git_azure_deployment_builder_master/1.11/MyWorkflow MyWorkflow 1.11
antidote-cis push smb://RECOLICPC/mybuild
antidote-cis setdef BootstrapCAWorkflow
antidote-cis cloudrun BootstrapCAWorkflow
antidote-cis cloudrun BuildDomainControllerWorkflow jihyan.9.24.2 IsFirstDC=true UserNameKey=unkey PasswordKey=pwkey @SubscriptionId=420f71ab-35f2-4550-9de6-7c8fab764b4d @Region=westus2
antidote-cis jobstatus 2517645620722579999_df3a452e-d580-47c2-b96e-61f1671358c9
antidote-cis jobtype-upload ../jobtype/modified.xml
antidote-cis jobtype-download > ../jobtype/downloaded.xml

Available env: 
    ANTIDOTE_JOBSTAT_MARKDOWN: If this variable is set to non-empty, 'jobstatus' would output as markdown instead of plain text. 
"
        exit 1
esac


