#!/usr/bin/env bash

# Send stats, receives commands
[[ -t 1 ]] && source colors

set -o pipefail

INTERVAL=10
LOG="/var/log/hive-agent.log"
LAST_STAT="/run/hive/last_stat.json"
LAST_CMD_ID="/run/hive/last_cmd_id"
CONF_SEQ="/run/hive/confseq"
MINER_STATUS="/run/hive/miner_status."

API_TIMEOUT=15 # timeout to wait for miner API response, can take very long time on 13+ gpus
API_SWITCH_BACK=3600 # sec
API_ERRORS=5 # before switching to fallback hosts
API_HOSTS_DEFAULT_FILE=/hive/etc/api_hosts # baked-in fallback pool, merged with server $API_HOST_URLs
API_PROBE_CONNECT_TIMEOUT=3 # sec, availability probe connect timeout
API_PROBE_MAX_TIME=5 # sec, availability probe total timeout

API_DEFAULT_URL=()
API_URLS_ARR=()
API_URLS_LAST=
api_error=0
api_last_switch=0

force_stop=0
rebooting=0
LAST_OK=0
LAST_COMMAND=0 # time when the last command from webface was received
CUR_PUSH_INTERVAL=$PUSH_INTERVAL # current push interval
RESPONSE_TIME=300 # timeout in seconds to reduce push interval
MAX_PUSH_INTERVAL=50


if [[ $1 != "run" ]]; then
	#$PPID - might be parent screen pid
	screen_count=`screen -ls | grep "\.agent" | grep -v $PPID | wc -l`

	#there will be still 1 process for subshell
	#[[ `ps aux | grep "./agent" | grep -vE "grep|screen|SCREEN|$$" | wc -l` > 1 ]] &&
	[[ $screen_count -gt 0 ]] &&
		echo -e "${RED}Agent screen is already running${NOCOLOR}\nRun ${CYAN}agent-screen${NOCOLOR} to resume screen" &&
		exit 1

	echo -500 > /proc/self/oom_score_adj

	while true; do
	$0 run
	sleep 1
	echo "Restarting..."
	done
fi

echo -300 > /proc/self/oom_score_adj

#ROH Fan controller
ROH=`lsusb 2>/dev/null | grep -c 16c0:05dc`
ROT=RESPONSE_TIME
LOF=LAST_OK_FILE
LOK=LAST_OK

#check curl version and SSl session file
curl --help | grep -q ssl-session-file && [[ ! -z $SSL_SESSION_FILE ]] &&
	ssl_session="--ssl-session-file $SSL_SESSION_FILE" ||
	ssl_session=""


function process_gpu_detect_json() {
	#{"temp":["61","42","41"],"fan":["92","0","0"],"load":["92","0","0"],"busids":["01:00.0","03:00.0","05:00.0"]}
	#Nvidia indexes in aa [1,2,3] to use in jsons
	#nvidia_indexes=echo "$gpu_stats" | jq '.brand | to_entries[] | select(.value == "nvidia") | .key' | jq -sc '.'
	#nvidia_indexes=`gpu-detect listjson | jq '. | to_entries[] | select(.value.brand == "nvidia") | .key' | jq -sc '.'`
	#gpu_detect_json=`gpu-detect listjson`

	if [[ $1 == "redetect" || ! -e $GPU_DETECT_JSON ]]; then #this is needed only when upgrading version. later we can remove this
		gpu-detect listjson > $GPU_DETECT_JSON
	fi
	gpu_detect_json=$(< $GPU_DETECT_JSON )
	[[ -z "$gpu_detect_json" ]] &&
		echo "gpu_detect_json was empty after reading" > /run/hive/agent-no-gpu-detect
	amd_indexes_array=`echo "$gpu_detect_json" | jq -c '[ . | to_entries[] | select(.value.brand == "amd") | .key ]'`
	nvidia_indexes_array=`echo "$gpu_detect_json" | jq -c '[ . | to_entries[] | select(.value.brand == "nvidia") | .key ]'`
	cpu_indexes_array=`echo "$gpu_detect_json" | jq -c '[ . | to_entries[] | select(.value.brand == "cpu") | .key ]'`
}

# sanitize functions are used to prepare stats values for backend
source sanitize

gpu_stats=
gpu_detect_json=

process_gpu_detect_json

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

function miner_stats() {
	local miner=$1
	local mindex=$2 #empty or 2, 3, 4, ...

	khs=0
	stats="null"
	[[ ! -f $RUNNING_FLAG ]] && return 1

	local MINER_DIR=/hive/miners/$miner
	if [[ -f $MINER_DIR/h-stats.sh ]]; then
		# run miner stats in subshell with timeout
		read -t $(( API_TIMEOUT + 5 )) -d "" pid khs stats < <(
			echo "$BASHPID" # report subshell PID
			cd $MINER_DIR
			{ source h-manifest.conf; source h-config.sh; source h-stats.sh; } 1>&2 # output to stderr
			printf "%q\n" "$khs" # avoid spaces, \n and etc
			echo "$stats"
		)
		if [[ $? -ge 128 ]]; then
			# kill subshell
			[[ ! -z "$pid" && "$pid" -ne $$ ]] && kill -9 "$pid" 2>/dev/null
			echo "${RED}$miner stats timeout$NOCOLOR"
		fi
	else
		echo -e "${YELLOW}$MINER_DIR/h-stats.sh does not exist${NOCOLOR}"
		#declare -g "MINER${mindex}=unknown"
	fi

	[[ -z "$khs" ]] && khs=0
	[[ -z "$stats" ]] && stats="null"
}

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

source agent.do_command.sh

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

function reset_miner_vars() {
	#reset all miner vars
	MINER=
	for i in {2..32}; do
		unset MINER$i
	done
}


# Waits for new stats to appear in file
function wait-for-gpu-stats() {
	#additional timeout to increase push interval
	#local max_tries=0
	#[[ $CUR_PUSH_INTERVAL -gt $MAX_PUSH_INTERVAL ]] && CUR_PUSH_INTERVAL=$MAX_PUSH_INTERVAL
	#local tosleep=$(( $CUR_PUSH_INTERVAL - $INTERVAL ))
	#if [[ $tosleep -ge 0 ]]; then
	#	for (( i=0; i<2*tosleep; i++ )); do
	#		echo -n '.'
	#		sleep 0.5
	#	done
	#fi

	local i=
	local max_tries=$(( INTERVAL*2*2 ))
	for (( i=1; i<=$max_tries; i++)); do
		local mtime=`stat -c %Y $GPU_STATS_JSON 2>/dev/null`
		[[ -z $mtime ]] && mtime=0

		local stats_age=$(( `date +%s` - mtime ))

		#echo "stats_age=$stats_age"
		echo -n '.'
		if [[ $stats_age -le 1 ]]; then #gotcha!
			gpu_stats=$(< $GPU_STATS_JSON)
			echo -en " ${GREEN}+"
			return
		elif [[ $i == $max_tries && $stats_age -ge 70 ]]; then #it seems gpu-stats hanged for more than 1.5 minutes
			gpu_stats='{}'
			echo -en " ${RED}hanged gpu-stats? ${CYAN}Restarting"
			if [[ $(pgrep -cf "agent.gpu-stats run") -ne 0 ]]; then
				pkill -f "agent.gpu-stats run"
			else
				pkill -f "agent.gpu-stats"
				sleep 0.2
				screen -S agent -X screen 2 agent.gpu-stats
				sleep 0.2
				screen -S agent -X title "gpu-stats" #set nice title
			fi
			return
		elif [[ $i == $max_tries ]]; then #&& $stats_age -ge $(($INTERVAL*2))  #too long to wait for it, giving up. taking old value
			[[ -e $GPU_STATS_JSON ]] && gpu_stats=$(< $GPU_STATS_JSON)
			echo -en " ${YELLOW}gave up waiting"
			return
		fi

		sleep 0.5
	done
}


function parse_host_url() {
	local host="$1"
	[[ ! "$host" =~ ^(https?://)?([0-9a-z\.-]+)(:[0-9]+)?$ ]] && return 1
	[[ -z "$2" ]] && return 0
	local -n arr="$2"
	arr[0]="${BASH_REMATCH[0]}" # full url
	arr[1]="${BASH_REMATCH[1]}" # proto
	arr[2]="${BASH_REMATCH[2]}" # host
	arr[3]="${BASH_REMATCH[3]}" # port
}


# Normalize a candidate URL: add the default protocol if a bare host was given.
function normalize_api_url() { # $1 = url; echoes normalized url or returns 1
	local U=()
	parse_host_url "$1" U || return 1
	if [[ -z "${U[1]}" ]]; then
		echo "${API_DEFAULT_URL[1]:-http://}${U[2]}${U[3]}"
	else
		echo "${U[0]}"
	fi
}


# Lightweight availability probe of an API endpoint.
# Success = endpoint reachable AND it really is the Hive API (a stats request
# with an invalid rig must return a JSON-RPC error). This rejects DPI block
# pages / captive portals that answer with HTTP 200 but no .error.code.
function probe_api_url() { # $1 = url (with proto)
	local url="$1"
	local resp
	resp=$(curl $insecure --connect-timeout ${API_PROBE_CONNECT_TIMEOUT:-3} --max-time ${API_PROBE_MAX_TIME:-5} --silent \
		-H "Content-Type: application/json" -H "User-Agent:" -H "Accept:" \
		-X POST -d '{"method":"stats","params":{"rig_id":"-1","passwd":"1"}}' \
		"${url}/worker/api" 2>/dev/null)
	[[ $? -eq 0 && -n $(echo "$resp" | jq -c 'if .error.code then . else empty end' 2>/dev/null) ]]
}


# Probe candidate API URLs sequentially (starting at $1 offset) and echo the
# first reachable one. Returns 1 if none responded.
function find_working_api() { # $1 = start offset (default 0)
	local start=${1:-0}
	local n=${#API_URLS_ARR[@]}
	[[ $n -eq 0 ]] && return 1
	local i idx url
	for (( i=0; i<n; i++ )); do
		idx=$(( (start + i) % n ))
		url=$(normalize_api_url "${API_URLS_ARR[$idx]}") || continue
		echo "${GRAY}  probing $url${NOCOLOR}" >&2
		if probe_api_url "$url"; then
			echo "$url"
			return 0
		fi
	done
	return 1
}


# Build the merged candidate list: default URL first, then server-provided
# $API_HOST_URLs, then the baked-in pool. Deduplicated by normalized URL so
# distinct proto/port variants of the same host are kept (they have different
# reachability under DPI blocking). Result goes into $API_URLS_ARR.
function build_api_urls() {
	local merged=() seen=" " u nu baked=()
	[[ -f "$API_HOSTS_DEFAULT_FILE" ]] &&
		readarray -t baked < <(grep -vE '^\s*(#|$)' "$API_HOSTS_DEFAULT_FILE" 2>/dev/null)
	for u in "$HIVE_HOST_URL" $API_HOST_URLs "${baked[@]}"; do
		[[ -z "$u" ]] && continue
		nu=$(normalize_api_url "$u") || continue
		[[ " $seen " =~ " $nu " ]] && continue
		seen+="$nu "
		merged+=("$nu")
	done
	API_URLS_ARR=( "${merged[@]}" )
}


# Main loop cycle
function loop {
	# remount to make some things working
	[[ ! -w /tmp ]] && echo "Remounting /tmp" && mount none /tmp -t tmpfs -o size=100m,mode=1777 2>/dev/null

	# check filesystem status
	if [[ ! -w / ]]; then
		echo "Filesystem is read-only, rebooting in 30 sec"
		sleep 30 # wait. maybe sreboot was called
		[[ $rebooting != 1 ]] && rebooting=1 && mount | message error "Filesystem is read-only, rebooting" payload
		# using reboot as sreboot most probably failed
		sync
		reboot -f
		sleep 5
		echo "Reboot failed!"
	fi

	local exitcode="$1"
	# increase current push interval if last command was received more then $RESPONSE_TIME ago
	no_command=$(( SECONDS - LAST_COMMAND ))
	[[ $no_command -gt $RESPONSE_TIME ]] && CUR_PUSH_INTERVAL=$PUSH_INTERVAL #&& echo "CUR_PUSH_INCREASED"
	# but set it back to 0 if there was an error with sending stats to server
	[[ $exitcode -ne 0 || -z "$response" ]] && CUR_PUSH_INTERVAL=0

	[[ -z $gpu_detect_json ]] && process_gpu_detect_json redetect

	if (( LAST_OK < SECONDS - RESPONSE_TIME*RESPONSE_TIME )); then
		miner stop
		if [[ $force_stop -eq 0 ]]; then
			force_stop=1
			echo "[`date`] No connection with API server. Stop the miner" >> $LOG
		fi
	fi

	[[ ! -f $RIG_CONF ]] && echo -e "No config $RIG_CONF" && return

	reset_miner_vars

	# each time read config again
	AGENT_GZIP=0
	PUSH_INTERVAL=10
	HIVEONAI_ENABLED=0
	source $RIG_CONF
	[[ $DISABLE_CERT_CHECK -ne 0 ]] && insecure="--insecure" || insecure=
	[[ -f $WALLET_CONF ]] && source $WALLET_CONF

	# API hosts failover
	[[ $exitcode -eq 0 ]] && api_error=0 || ((api_error++))
	if [[ "$API_HOST_URLs" != "$API_URLS_LAST" || "$HIVE_HOST_URL" != "${API_DEFAULT_URL[0]}" ]]; then
		# config (re)loaded: rebuild candidate list and reset failover state
		API_URLS_LAST="$API_HOST_URLs"
		if parse_host_url "$HIVE_HOST_URL" API_DEFAULT_URL; then
			[[ -e "$API_HOST_FILE" ]] && rm $API_HOST_FILE
			api_last_switch=0
			api_error=0
			build_api_urls
		else
			API_DEFAULT_URL=()
			API_URLS_ARR=()
			echo "${RED}Default API URL parsing error ($HIVE_HOST_URL)${NOCOLOR}"
		fi
		echo "${GRAY}Reloading API URLs (${#API_URLS_ARR[@]}), using ${HIVE_HOST_URL:-none}${NOCOLOR}"
		for url in "${API_URLS_ARR[@]}"; do
			echo "${GRAY} # $url${NOCOLOR}"
		done
	elif [[ $api_error -ge $API_ERRORS && ${#API_URLS_ARR[@]} -gt 1 ]]; then
		# current host keeps failing: probe candidates and switch to the first
		# reachable one (default included, so a recovered default is preferred)
		echo "${GRAY}API errors=$api_error, probing ${#API_URLS_ARR[@]} candidates for a reachable server${NOCOLOR}"
		local new_url=$(find_working_api 0)
		if [[ -n "$new_url" ]]; then
			HIVE_HOST_URL="$new_url"
			if [[ "$HIVE_HOST_URL" == "${API_DEFAULT_URL[0]}" ]]; then
				# default is reachable again, drop the override
				[[ -e "$API_HOST_FILE" ]] && rm $API_HOST_FILE
				echo "${GREEN}Using default API $HIVE_HOST_URL${NOCOLOR}"
			else
				echo "${GREEN}Switching API to $HIVE_HOST_URL${NOCOLOR}"
				echo "HIVE_HOST_URL=\"$HIVE_HOST_URL\"" > $API_HOST_FILE
			fi
			api_error=0
		else
			echo "${RED}No reachable API server found among ${#API_URLS_ARR[@]} candidates${NOCOLOR}"
		fi
		api_last_switch=$SECONDS
	elif [[ -e "$API_HOST_FILE" ]]; then
		if (( api_last_switch + API_SWITCH_BACK < SECONDS )); then
			# periodically retry the preferred default; switch back only if it answers
			if probe_api_url "${API_DEFAULT_URL[0]}"; then
				echo "${GRAY}Default ${API_DEFAULT_URL[0]} reachable again, switching back${NOCOLOR}"
				rm $API_HOST_FILE
				api_last_switch=0
				api_error=0
			else
				echo "${GRAY}Default still unreachable, staying on fallback${NOCOLOR}"
				api_last_switch=$SECONDS
				source $API_HOST_FILE
			fi
		else
			source $API_HOST_FILE
		fi
	fi

	HIVE_URL="$HIVE_HOST_URL/worker/api"

	echo -n "${YELLOW}"
	wait-for-gpu-stats
	echo "${NOCOLOR}"
	date

	[[ -z $gpu_stats ]] && gpu_stats='{}'
	local temp=$(echo "$gpu_stats" | jq -c 'if .temp then .temp else empty end')
	local jtemp=$(echo "$gpu_stats" | jq -c 'if .jtemp then .jtemp else empty end')
	local mtemp=$(echo "$gpu_stats" | jq -c 'if .mtemp then .mtemp else empty end')
	local fan=$(echo "$gpu_stats" | jq -c 'if .fan then .fan else empty end')
	local power=$(echo "$gpu_stats" | jq -c 'if .power then .power else empty end')

	local miners_json=()
	if [[ ! -z $MINER ]]; then
		local hashrate=""
		local mindex
		for mindex in {2..32} 1; do # first miner must be the last to set global $khs and $stats
			[[ $mindex -eq 1 ]] && mindex=
			local -n minerval="MINER$mindex"
			if [[ ! -z $minerval ]]; then
				miner_stats "$minerval" $mindex
				khs=$(sanitize_total_hashrate "$khs")
				declare -g "khs$mindex=$khs" # is it needed anywhere?
				stats=$(sanitize_miner_stats "$stats")
				#add miner status
				[[ -z $mindex ]] && nmindex=1 || nmindex=$mindex
				if [[ -f ${MINER_STATUS}${nmindex} ]]; then
					mstatus=`cat ${MINER_STATUS}${nmindex}`
					if [[ -z $stats || $stats == 'null' ]]; then
						stats=$mstatus
					else
						stats=$(jq -s '.[0] * .[1]' <<< "$mstatus $stats")
					fi
				fi
				miners_json[${mindex:-1}]=$(jq -n -c \
					--arg miner$mindex "$minerval" --argjson miner_stats$mindex "$stats" \
					'{"params": {$miner'$mindex', "total_khs'$mindex'":'$khs', $miner_stats'$mindex'}}' \
					2>/dev/null) || echo -e "${RED}$minerval json creating error${NOCOLOR}"
				[[ ! -z $mindex ]] &&
					hashrate="${hashrate}, ${CYAN}$minerval${NOCOLOR} `[[ $khs != 0 ]] && echo -e ${YELLOW} || echo -e ${RED}`$khs${NOCOLOR} kH/s"
			fi
		done
		echo "Hashrate ${CYAN}$MINER${NOCOLOR} `[[ $khs != 0 ]] && echo -e ${YELLOW} || echo -e ${RED}`$khs${NOCOLOR} kH/s${hashrate}"
	else
		echo -e "${YELLOW}No miner is set in config!${NOCOLOR}"
		khs=0
		stats="null"
	fi
	echo "$khs" > $HASHRATE_KHS

	# ROH Fan controller blink error state
	if [[ $ROH -ge 1 ]]; then
		# update text to OLED screen
		miner_algo=`echo "$stats" | jq -r '.algo'`
		$OCTOFAN update_text $miner_algo $khs

		error_state=$(echo "$response" | jq -r '.result.error_state')
		if [[ $error_state == "danger" ]]; then
			$OCTOFAN blink_error
		elif [[ $error_state == "warning" ]]; then
			$OCTOFAN blink_warning
		else
			$OCTOFAN blink_off
		fi
	fi

	request=`jq -c -n --arg rig_id "$RIG_ID" --arg passwd "$RIG_PASSWD" '{ "method": "stats", "params": { "v":2, $rig_id, $passwd }}' 2>/dev/null`
	if [[ $? -ne 0 ]]; then
		echo -e "${RED}Request json creating error${NOCOLOR}"
		return
	fi

	if [[ -f $LAST_CMD_ID ]]; then
		json=`echo "$request" '{"params": {"last_cmd_id": '$(<$LAST_CMD_ID)' }}' | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
			request="$json" ||
			echo -e "${RED}Last cmd id parsing error${NOCOLOR}"
	fi

	temp=$(sanitize_temp "$temp")
	jtemp=$(sanitize_temp "$jtemp")
	mtemp=$(sanitize_temp "$mtemp")
	fan=$(sanitize_fan "$fan")
	power=$(sanitize_power "$power")

	[[ ! -z $META ]] && meta="$META" || meta='null'

	mem=`free -m | grep 'Mem' | awk '{print $2", "$7}'` # total and available
	df=`df -h / | awk '{ print $4 }' | tail -n 1 | sed 's/%//'`
	cputemp=`cpu-temp | jq -cs .`
	cpuavg=`awk '{print $1", "$2", "$3}' /proc/loadavg`

	stats_json=$(
		jq -n -c \
		--argjson meta "$meta" \
		--argjson temp "$temp" \
		--argjson fan "$fan" \
		--argjson power "$power" \
		--arg df "$df" \
		--argjson mem "[$mem]" \
		--argjson cputemp "$cputemp" \
		--argjson cpuavg "[$cpuavg]" \
		'{ "params": { $meta, $temp, $fan, $power, $df, $mem, $cputemp, $cpuavg }}'
		2>/dev/null
	)

	if [[ $? -ne 0 ]]; then
		echo -e "${RED}Stats json creating error${NOCOLOR}"
	else
		json=`echo "$request" "$stats_json" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
			request="$json" ||
			echo -e "${RED}Stats json parsing error${NOCOLOR}"
	fi

	if [[ "$jtemp" =~ [0-9] ]]; then
		stats_json=$(jq -n --argjson jtemp "$jtemp" '{ "params": { $jtemp }}' 2>/dev/null) &&
			json=`echo "$request" "$stats_json" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
				request="$json"
	fi

	if [[ "$mtemp" =~ [0-9] ]]; then
		stats_json=$(jq -n --argjson mtemp "$mtemp" '{ "params": { $mtemp }}' 2>/dev/null) &&
			json=`echo "$request" "$stats_json" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
				request="$json"
	fi

	# Add miners stats to json
	for miner_json in "${miners_json[@]}"; do
		if [[ ! -z $miner_json ]]; then
			json=`echo "$request" "$miner_json" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
				request="$json" ||
				echo -e "${RED}Miner json parsing error${NOCOLOR}"
		fi
	done

	# send stats only in CUR_PUSH_INTERVAL
	[[ $CUR_PUSH_INTERVAL -gt $MAX_PUSH_INTERVAL ]] && CUR_PUSH_INTERVAL=$MAX_PUSH_INTERVAL
	local nextpush=$(( LAST_OK + CUR_PUSH_INTERVAL - INTERVAL/2 - SECONDS ))
	if (( nextpush > 0 && nextpush <= MAX_PUSH_INTERVAL )); then
		echo "$request" > $LAST_STAT # update on every cycle
		echo "${GRAY}$nextpush sec until next push, waiting$NOCOLOR"
		return 0
	fi

	# HiveonAI stats added only if it's enabled in config
        if [[ $HIVEONAI_ENABLED -gt 0 && -f /hive/sbin/haictl ]]; then
                haistats_json=$(jq -n -c --argjson hiveonai "$(haictl stats)" '{ "params": { $hiveonai } }' 2>/dev/null )
                [[ -n $haistats_json ]] && json=$(echo "$request" "$haistats_json" | jq -s -c '.[0] * .[1]' 2>/dev/null ) && request="$json" || echo -e "${RED}HiveonAI JSON parsing error${NOCOLOR}"
        fi

	# Add ROH Fan controller stats to json
	if [[ $ROH -ge 1 ]]; then
		casefan=`$OCTOFAN get_fan_json`
		thermosensors=`$OCTOFAN get_temp_json`
		psu=`$OCTOFAN get_psu_json`
		bme=`$OCTOFAN get_bme_json`
		octofan_stats=$(
			jq -n -c \
			--argjson casefan "$casefan" \
			--argjson thermosensors "$thermosensors" \
			--argjson psu "$psu" \
			--argjson bme "$bme" \
			'{
				$casefan, $thermosensors, $psu, $bme
			}'
			2>/dev/null
		)

		octofan_stats=`sanitize_fan_controller_stats "$octofan_stats"`
		octofan_stats=$(
			jq -n -c \
			--argjson octofan_stats "$octofan_stats" \
			'{
				"params": { $octofan_stats }
			}'
			2>/dev/null
		)

		if [[ ! -z $octofan_stats ]]; then
			json=`echo "$request" "$octofan_stats" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
				request="$json" ||
				echo -e "${RED}Octofan json parsing error${NOCOLOR}"
		fi

		$OCTOFAN write_cli_log
	fi

	# Add Coolbox Autofan controller stats to json
	if [[ -f $COOLBOX_AVAILABLE_FILE ]]; then
		local coolbox=`$COOLBOX --get_json`
		if [[ ! -z $coolbox ]]; then
			coolbox=`sanitize_fan_controller_stats "$coolbox"`
			coolbox_stats=$(
				jq -n -c \
				--argjson coolbox_stats "$coolbox" \
				'{
					"params": {$coolbox_stats}
				}'
				2>/dev/null
			)

			if [[ ! -z $coolbox_stats ]]; then
				json=`echo "$request" "$coolbox_stats" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
					request="$json" ||
					echo -e "${RED}Coolbox json parsing error${NOCOLOR}"
			fi
		fi
	fi

	# Add Ykeda Autofan controller stats to json
	if [[ -f $YKEDA_AVAILABLE_FILE ]]; then
		local ykeda_autofan=`$YKEDA_AUTOFAN --get_json`
		if [[ -n $ykeda_autofan ]]; then
			ykeda_autofan=`sanitize_fan_controller_stats "$ykeda_autofan"`
			ykeda_autofan_stats=$(
				jq -n -c \
				--argjson ykeda_autofan_stats "$ykeda_autofan" \
				'{
					"params": {$ykeda_autofan_stats}
				}'
				2>/dev/null
			)

			if [[ -n $ykeda_autofan_stats ]]; then
				json=`echo "$request" "$ykeda_autofan_stats" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
					request="$json" ||
					echo -e "${RED}Ykeda Autofan json parsing error${NOCOLOR}"
			fi
		fi
	fi

	# Add Wind Tank Autofan controller stats to json
	if $WINDTANK_AUTOFAN --check_hw; then
		local windtank_autofan=`$WINDTANK_AUTOFAN --get_json`
		if [[ -n $windtank_autofan ]]; then
			windtank_autofan=`sanitize_fan_controller_stats "$windtank_autofan"`
			windtank_autofan_stats=$(
				jq -n -c \
				--argjson windtank_autofan_stats "$windtank_autofan" \
				'{
					"params": {$windtank_autofan_stats}
				}'
				2>/dev/null
			)

			if [[ -n $windtank_autofan_stats ]]; then
				json=`echo "$request" "$windtank_autofan_stats" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
					request="$json" ||
					echo -e "${RED}Wind Tank Autofan json parsing error${NOCOLOR}"
			fi
		fi
	fi

	# Add 8MK_NET Autofan controller stats to json
	if $MKNET_AUTOFAN --check_hw; then
		local mknet_autofan=`$MKNET_AUTOFAN --get_json`
		if [[ -n $mknet_autofan ]]; then
			mknet_autofan=`sanitize_fan_controller_stats "$mknet_autofan"`
			mknet_autofan_stats=$(
				jq -n -c \
				--argjson mknet_autofan_stats "$mknet_autofan" \
				'{
					"params": {$mknet_autofan_stats}
				}'
				2>/dev/null
			)

			if [[ -n $mknet_autofan_stats ]]; then
				json=`echo "$request" "$mknet_autofan_stats" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
					request="$json" ||
					echo -e "${RED}8MK_NET Autofan json parsing error${NOCOLOR}"
			fi
		fi
	fi

	# Add average hashrate
	if [[ $SEND_AVG_KHS -eq 1 ]]; then
		local file_age=0
		let file_age=`date +%s`-`stat --format='%Y' $AVG_KHS`
		if [[ $file_age -le 20 ]]; then
			local avg_khs_json=`cat $AVG_KHS`
			json=`echo "$request" "$avg_khs_json" | jq -s -c '.[0] * .[1]' 2>/dev/null` &&
			request="$json" ||
				echo -e "${RED}avg_khs json parsing error${NOCOLOR}"
		else
			echo -e "${RED}avg_khs data is too old, check avg_khs service${NOCOLOR}"
		fi
	fi

	echo "$request" > $LAST_STAT

	[[ $AGENT_GZIP == 1 ]] && echo -n  "Z "
	echo $request | jq -c --arg pass "${RIG_PASSWD//?/*}" '.params.passwd=$pass'

	if [[ -z $RIG_ID ]]; then
		echo -e "${YELLOW}No RIG_ID, skipping sending stats${NOCOLOR}"
		return 0
	fi

	if [[ -z "$HIVE_HOST_URL" ]]; then
		echo -e "${RED}No host, skipping sending stats${NOCOLOR}"
		return 99
	fi

	#log request
	#echo "[`date`] > `echo $request | jq -c '.'`" >> $LOG

	if [[ $AGENT_GZIP == 1 ]]; then
		echo "[`date`] > Z $request" >> $LOG

		response=$(echo "$request" | gzip -9 -c -f | curl $insecure -L --data-binary @- ${ssl_session} \
				-H "Content-Encoding: gzip" -H "Content-Type:" -H "User-Agent:" -H "Accept:" \
				--connect-timeout 7 --max-time 15 --silent -XPOST "${HIVE_URL}?id_rig=$RIG_ID&method=stats")
		exitcode=$?
	else
		echo "[`date`] > $request" >> $LOG

		if [[ $MSGPACK_ENABLE -ne 0 ]]; then
			tmpfile=$(mktemp)
			if [[ $? -ne 0 ]]; then
				MSGPACK_ENABLE=0 # fallback to old method
			else
				response=$(echo "$request" | json2msgpack -B 25 | curl $insecure -L --data-binary @- ${ssl_session} -o $tmpfile \
						--connect-timeout 7 --max-time 15 --silent -w "%{content_type}\n" -H "User-Agent:" -H "Accept:" \
						-XPOST "${HIVE_URL}?id_rig=$RIG_ID&method=stats" -H "Content-Type: application/x-msgpack")
				exitcode=$?
				[[ "$response" =~ /x-msgpack ]] && response=$(msgpack2json -B -i $tmpfile 2>/dev/null) || MSGPACK_ENABLE=0
				rm -f $tmpfile
			fi
		fi
		if [[ $MSGPACK_ENABLE -eq 0 ]]; then
			response=$(echo "$request" | curl $insecure -L --data @- ${ssl_session} --connect-timeout 7 --max-time 15 --silent \
					-H "Content-Type:" -H "User-Agent:" -H "Accept:" -XPOST "${HIVE_URL}?id_rig=$RIG_ID&method=stats")
			exitcode=$?
		fi
	fi

	if [[ $exitcode -ne 0 || -z "$response" ]]; then
		echo -e "${RED}Error sending stats to $HIVE_HOST_URL${NOCOLOR}" && human-curl-error $exitcode
		echo "[`date`] < ERROR SENDING STATS TO $HIVE_HOST_URL (curl code=$exitcode) \"$response\"" >> $LOG
		[[ -z "$response" ]] && exitcode=100
		return $exitcode
	fi

	#echo $response
	#echo "[`date`] < `echo $response | jq '.' -c`" >> $LOG
	echo "[`date`] < $response" >> $LOG #full response even invalid

	error=$(echo "$response" | jq '.error' --raw-output 2>/dev/null)
	if [[ $? -ne 0 ]]; then
		error=`echo "$response" | grep -oP "<title>\K[^<]+"`
		if [[ ! -z "$error" ]]; then
			echo "${RED}Bad response from $HIVE_HOST_URL: ${BRED}$error${NOCOLOR}"
		else
			echo "${RED}Invalid response from $HIVE_HOST_URL:${NOCOLOR}"
			echo "${GRAY}$response${NOCOLOR}"
		fi
		return 101
	fi
	if [[ ! -z "$error" && "$error" != "null" ]]; then
		echo -e "${RED}Error response from $HIVE_HOST_URL: ${BRED}$error${NOCOLOR}"
		return 102
	fi

	if [[ $force_stop -eq 1 ]]; then
		miner start
		force_stop=0
		echo "[`date`] Connection with API server is OK. Start miner" >> $LOG
	fi

	command=$(echo "$response" | jq '.result.command' --raw-output)

	if [[ "$command" != "OK" ]]; then
		echo "$response" | jq -c '.result'
		#the command has been received, reducing push interval to make the rig more responsive
		CUR_PUSH_INTERVAL=0
		LAST_COMMAND=$SECONDS
	else
		# OK, remove last cmd id
		rm $LAST_CMD_ID 2>/dev/null
	fi

	if [[ "$command" != "batch" ]]; then
		body=`echo "$response" | jq -c '.result'`
		#Optional command identifier
		cmd_id=`echo "$body" | jq -r '.id'`
		[[ "$cmd_id" == "null" ]] && cmd_id=
		# save last id
		[[ $cmd_id -gt 0 ]] && echo $cmd_id > $LAST_CMD_ID
		do_command
		confseq=`echo "$body" | jq -r '.confseq'`
	else
		confseq=
		count=`echo "$response" | jq '.result.commands|length'`
		echo "Got $count batch commands"
		for (( i=0; i < $count; i++ )); do
			body=`echo "$response" | jq -c ".result.commands[$i]"`
			#Optional command identifier
			cmd_id=`echo "$body" | jq -r '.id'`
			[[ "$cmd_id" == "null" ]] && cmd_id=
			# save last id for each command
			[[ $cmd_id -gt 0 ]] && echo "$cmd_id" > $LAST_CMD_ID
			command=
			do_command
			newseq=`echo "$body" | jq -r '.confseq'`
			[[ $newseq -gt $confseq ]] && confseq=$newseq
		done
	fi

	if [[ ! -z "$confseq" && "$confseq" != "null" && -w /run ]]; then
		if [[ "$command" != "OK" ]]; then
			echo "$confseq" > $CONF_SEQ 2>/dev/null
		elif [[ -e $CONF_SEQ ]]; then
			curseq="$(<$CONF_SEQ)"
			#echo "confseq = $confseq, current = $curseq"
			if [[ "$curseq" != "$confseq" ]]; then
				echo -e "${YELLOW}Config sequence mismatch, sending HELLO${NOCOLOR}"
				echo "[`date`] < CONFSEQ MISMATCH, SENDING HELLO" >> $LOG
				rm $CONF_SEQ
				hello
			fi
		fi
	fi

	return 0
}


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

echo -e "\n\n[`date`] Started" >> $LOG

exitcode=
while true; do
	# sleeping is controlled also by agent.gpu-stats now
	loop $exitcode
	exitcode=$?
	sleep 2 # to make sure we will not grab the same gpu-stats file
done
