#!/bin/bash

# Usage: flash-fw-usb sub1-01 /home/firefly/Firmware/android.img

set -uo pipefail

FLASH_RATE=$((3 * 1024 * 1024))  # estimated 3MB/s

CACHE_DIR=/var/netrecovery
NETRECOVERY_DIR=/usr/share/netrecovery

. /usr/share/bmc/lib.sh

node_error_with_exit_code_after_maskrom() {
    slave_backup_log "$1"
    node_switch_to_maskrom_by_dd "$1"
    node_error_with_exit_code "$@"
}

switch_to_netrecovery() {
    node_report_status $node $JOB_PREPARING 20 "switching to netrecovery"
    __switch_to_netrecovery() {
        node_switch_to_netrecovery $node || :
        sleep 1
        is_netrecovery_device $node
    }
    retry_cmd 3 10 __switch_to_netrecovery \
        || node_error_with_exit_code $node $ELOADER "switch to netrecovery fails"

    node_report_status $node $JOB_PREPARING 60 "uploading netrecovery tools"
    __upload_netrecovery_tools() {
        local result
        node_adb_push $node $NETRECOVERY_DIR/bin/netrecovery-slave /tmp/ || return 1
        result=$(node_adb_shell $node "test -f /usr/bin/rkupdate && echo okay" || :)
        [[ "$result" == "okay" ]]
    }
    retry_cmd 3 10 __upload_netrecovery_tools \
        || node_error_with_exit_code_after_maskrom $node $EPUSH "failed to push netrecovery tools"
}

slave() {
    local node="$1" result
    shift
    node_adb_shell $node /tmp/netrecovery-slave "$*"
}

slave_backup_log() {
    local node="$1"
    local dir=$state_dir/devlog-${id}
    local tarfile=${dir}.tar.gz

    slave $node report_system_info
    node_adb_shell $node "tar -cC /tmp/log . | gzip > /tmp/log.tar.gz"

    node_adb $node pull /tmp/log.tar.gz "$tarfile" &>/dev/null
    [[ -f "$tarfile" ]] \
        && mkdir -p $dir \
        && tar xf $tarfile -C $dir \
        && rm $tarfile
}

setup_ipaddr() {
    local intf intf_ip err_msg
    declare -n reachable_ip=$1

    node_report_status $node $JOB_PREPARING 80 "setting network"
    intf="$(node_network_intf $node)"
    __get_reachable_ip() {
        intf_ip="$(ifconfig $intf | grep "inet " | awk '{print $2}' || :)"
        if [[ -z "$intf_ip" ]]; then
            err_msg="No IP for main board's $intf"
            return 1
        fi
        node_log_info $node "Server IP: $intf_ip"
        reachable_ip=$(slave $node get_dhcp_reachable_ip $intf_ip)
        if [[ -z "$reachable_ip" || "$reachable_ip" == "error" ]]; then
            err_msg="DHCP failed to get IP"
            return 1
        fi
        return 0
    }
    retry_cmd 3 10 __get_reachable_ip \
        || node_error_with_exit_code_after_maskrom $node $ENETWORK "$err_msg"
}

request_flash() {
    # Start rkupdate service on node
    __request_flash() {
        rpid="$(slave $node long_flash_firmware $url)"
        case "$rpid" in
            busy)
                node_log_info $node "Kill existing flash firmware process in device"
                slave $node kill long_flash_firmware
                return 1
                ;;
            [0-9]*)
                return 0
                ;;
            *)
                node_log_info $node "Submit flashing firmware request failed: $rpid"
                return 1
        esac
    }
    retry_cmd 10 10 __request_flash \
        || node_error_with_exit_code_after_maskrom $node $EUNEXPECTED "Failed to request flashing"
}

# Track rkupdate status on node
track_flash_progress() {
    local status
    local progress last_progress=0
    local last_time=$(get_monosecs) now
    local errcnt=0 errmsg=""

    echo $rpid >$state_dir/rpid

    while true; do
        sleep 2
        if ! is_netrecovery_device $node; then
            status="error"
            (( errcnt++))
            errmsg="out of netrecovery mode"
        else
            status="$(slave $node status $rpid)"
            case "$status" in
            done)
                progress=100
                node_report_status $node $JOB_FLASHING $progress
                sleep 6
                break
                ;;

            [0-9]*)
                progress=$status
                now=$(get_monosecs)
                if ((progress != last_progress)); then
                    # this is progressing
                    node_report_status $node $JOB_FLASHING $progress
                    last_time=$now
                    last_progress=$progress
                    errcnt=0
                elif ((now - last_time >= 60)); then
                    status="fatal"
                    errmsg="Timeout (no progress update)"
                fi
                ;;

            error)
                (( errcnt++))
                errmsg="failed to get rkupdate state"
                ;;

            *)
                (( errcnt++))
                status="error"
                errmsg="unknown failed: $status"
                ;;
            esac
        fi

        if [[ "$status" = "error" ]] && (( errcnt > 20 )); then
            status="fatal"
            errmsg="Timeout (too many errors)"
        fi
        case "$status" in
            fatal)
                node_error_with_exit_code_after_maskrom $node $EFLASH "$errmsg"
                ;;
            error)
                node_log_info $node "ERROR: $errmsg #$errcnt"
                ;;
        esac
    done
}

net_flash() {
    local  rpid server_ip url

    node_report_status $node $JOB_PREPARING 0 "net flash starts"

    switch_to_netrecovery

    setup_ipaddr server_ip

    if [[ "$firmware" == "http://"* ]]; then
        url="$firmware"
    else
        url="http://$server_ip:2020$firmware"
    fi

    node_report_status $node $JOB_PREPARING 100 "waiting to flash"
    wait_meeting_flashing_limit

    node_report_status $node $JOB_FLASHING "0"
    request_flash
    track_flash_progress

    node_report_status $node $JOB_DONE

    node_log_info $node "Backup log"
    slave_backup_log $node

    sleep 3
    node_log_info $node "Reset node"
    #slave $node reboot   # spi flash don't like reboot
    node_reset $node

    node_log_info $node "All done"
}

if (( $# < 2 )); then
    error_with_exit_code $EUSAGE "Usage: flash-fw-net <node> <firmware>"
fi

node="$1"
firmware="$2"
id="${3:-$$}"

state_dir=${CACHE_DIR}/$node/state

check_node "$node"
check_firmware "$firmware" http
net_flash
