#!/bin/sh

PROTO_MAGIC="HAL_PROTO"
STATUS_OK="OK"
MAJOR_VERS="0"
MINOR_VERS="1"

CUR_STATE="MAGIC"
CUR_COMMAND=""
PLEN=""
NARG="0"

#set -x

# Open /tmp/write for OUTPUT as fd=4
#exec 4>/tmp/read

# $1 is the length of payload $2 is the payload
send_packet()
{
	local HEADER=""

	HEADER="$(printf "%s" $PROTO_MAGIC)\n"
	HEADER=$HEADER"$(printf "%X" $1)\n"
	HEADER=$HEADER"\n"
	
	# Between the header and the payload we have extra '\n'
	# so "\n\n" is a signature of the header end
	#cat <<- EOF_COMMAND >&4 
	cat <<- EOF_COMMAND > /tmp/read
	$(echo "$HEADER""$2")
	EOF_COMMAND
}

# Functions below are responses for managed commands, we only answer here.

connect_response()
{
	local INPUTS_SZ="20"
	local OUTPUTS_SZ="20"
	local PAYLOAD=""
	local STR_LEN=""
	# Form the message payload: a command type, a status, major and minor
	# protocol versions, inputs and outputs image sizes
	PAYLOAD="CN\nOK\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $MAJOR_VERS)\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $MINOR_VERS)\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $INPUTS_SZ)\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $OUTPUTS_SZ)\n"

	STR_LEN=$(expr length $PAYLOAD)
	# A small hack when we calculate the length of the string we count
	# '\n' as 2 symbols now substract 1 one for each '\n'
	STR_LEN=$(expr $STR_LEN - 6)

	send_packet $STR_LEN $PAYLOAD

	echo "--> CN: send a response"

}
# $1 is a command type CYCLE_BEGIN, CYCLE_RUN or CYCLE_END
cycle_state_response()
{
	local PAYLOAD=""
	local STR_LEN=""

	PAYLOAD="$1\nOK\n"
	STR_LEN=$(expr length $PAYLOAD)
	STR_LEN=$(expr $STR_LEN - 2)

	send_packet $STR_LEN $PAYLOAD

	echo "--> $1: send a response"
}

# Functions below manage the other side program. We send commands and
# wait for reponses.

write_outputs()
{	
	local PAYLOAD=""
	local STR_LEN=""
	# In our test we want writing only a memory statistic (used memory) it
	# occupies 4 bytes.
	local LEN="4"
	local OFFS="4"
	# Fetch and format sending data.
	local MEM_STAT=$(free | tail -n +2 | head -n 1 | awk '{ print $3 }')
	# Form the message payload
	PAYLOAD="WO\nOK\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $LEN)\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $OFFS)\n"
	PAYLOAD=$PAYLOAD"$(printf "%08X" $MEM_STAT)\n"

	STR_LEN=$(expr length $PAYLOAD)
	STR_LEN=$(expr $STR_LEN - 5)

	send_packet $STR_LEN $PAYLOAD

	echo "--> WO: send a command"
	echo "--> WO: write $LEN bytes at $OFFS offset, value $(printf "%X" $MEM_STAT)"
}

write_inputs()
{	
	local PAYLOAD=""
	local STR_LEN=""
	# In our test we want writing only a memory statistic (used memory) it
	# occupies 4 bytes.
	local LEN="4"
	local OFFS="4"
	# Fetch and format sending data.
	local MEM_STAT=$(free | tail -n +2 | head -n 1 | awk '{ print $3 }')
	# Form the message payload
	PAYLOAD="WI\nOK\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $LEN)\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $OFFS)\n"
	PAYLOAD=$PAYLOAD"$(printf "%08X" $MEM_STAT)\n"

	STR_LEN=$(expr length $PAYLOAD)
	STR_LEN=$(expr $STR_LEN - 5)
	
	send_packet $STR_LEN $PAYLOAD

	echo "--> WI: send a command"
	echo "--> WI: write $LEN bytes at $OFFS offset, value $(printf "%X" $MEM_STAT)"
}

# $1 is command type RI or RO
read_image()
{
	local PAYLOAD=""
	local STR_LEN=""
	# We want get back what we have written
	local LEN="4"
	local OFFS="4"

	PAYLOAD="$1\nOK\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $LEN)\n"
	PAYLOAD=$PAYLOAD"$(printf "%X" $OFFS)\n"

	STR_LEN=$(expr length $PAYLOAD)
	STR_LEN=$(expr $STR_LEN - 4)

	send_packet $STR_LEN $PAYLOAD	

	echo "--> $1: send a command"
	echo "--> $1: read $LEN bytes at $OFFS offset"
}

# $1 is command type: WE or RE
operation_end()
{
	local PAYLOAD=""
	local STR_LEN=""

	PAYLOAD="$1\nOK\n"
	STR_LEN=$(expr length $PAYLOAD)
	STR_LEN=$(expr $STR_LEN - 2)

	send_packet $STR_LEN $PAYLOAD

	echo "--> $1: send a command"
}

# Set important variables to the innocent states that we can accept a new
# packet and correctly parse it
reset_variables()
{
	CUR_STATE="MAGIC"
	CUR_COMMAND=""
	NARG="0"
}

# In the loop we do:
# * accept a new connection;
# * confirm it;
#
# * get a cycle beginning message;
# * confirm it;
# * write data only to the inputs image;
# * end writting operations;
# * wait the write operation confirmation;
# * wait the end writting message confirmation;
#
# * get a cycle run message;
# * confirm it;
#
# * get a cycle end message;
# * confirm it;
# * read data only from the inputs image;
# * end reading operations;
# * wait the read operation confirmation + data;
# * wait the end of reading message confirmation;
#
# Here the other side exits
# 

while read STR
do

	case "$CUR_STATE" in
	MAGIC)
		if [ "$STR" != "$PROTO_MAGIC" ] ; then
			CUR_STATE="ERROR"
			ERRSTRING="magic not found"
		else
			CUR_STATE="PACKET_LEN"
		fi
		;;
	PACKET_LEN)
		# In this implementation we don't care about a payload length,
		# just save it and come to the next step
		PLEN="0x$STR"
		echo "--> PAYLOAD LENGTH: $PLEN"
		CUR_STATE="COMMAND"
		;;
	COMMAND)
		case "$CUR_COMMAND" in
		CN)
			# Count arguments and handle according its number
			# There are 3 arguments for CONNECT:
			# 1) command status;
			# 2) major protocol version;
			# 3) minor protocol version.

			NARG=$(expr $NARG + 1)

			case "$NARG" in
			1)
				# Check the status just in case
				if [ "$STR" != "$STATUS_OK" ] ; then
					CUR_STATE="ERROR_MSG"
				else
					echo "<-- $CUR_COMMAND: status is OK"
				fi
			;;
			2)
				if [ "$STR" -ne $MAJOR_VERS ] ; then
					CUR_STATE="ERROR"
					ERRSTRING="major version is wrong"
				else
					echo "<-- $CUR_COMMAND: major version is OK"
				fi
			;;
			3)
				if [ $STR -ne $MINOR_VERS ] ; then
					CUR_STATE="ERROR"
					ERRSTRING="minor version is wrong"
				else
					echo "<-- $CUR_COMMAND: minor version is OK"
					# Here we think that pipe is empty and
					# prepare a packet for writing back
					connect_response
					# Set our state back to MAGIC to be
					# ready for a new packet
					reset_variables
				fi
			;;
			esac
		;;
		CB)
			# There is only one argument:
			# 1) command status.

			NARG=$(expr $NARG + 1)

			case "$NARG" in
			1)
				if [ "$STR" != "$STATUS_OK" ] ; then
					CUR_STATE="ERROR_MSG"
				else
					# Now we need to answer
					echo "<-- $CUR_COMMAND: status is OK"
					cycle_state_response $CUR_COMMAND
					# If come here we can write inputs and
					# outputs images
					write_inputs
					operation_end "WE"
					reset_variables
				fi 
			;;
			esac
		;;
		CR)
			# There is only one argument:
			# 1) command status.
			
			NARG=$(expr $NARG + 1)

			case "$NARG" in
			1)
				if [ "$STR" != "$STATUS_OK" ] ; then
					CUR_STATE="ERROR_MSG"
				else
					echo "<-- $CUR_COMMAND: status is OK"
					# Now we need to answer
					cycle_state_response $CUR_COMMAND
					reset_variables
				fi 
			;;
			esac
		;;
		CE)
			# There is only one argument:
			# 1) command status.

			NARG=$(expr $NARG + 1)

			case "$NARG" in
			1)
				if [ "$STR" != "$STATUS_OK" ] ; then
					CUR_STATE="ERROR_MSG"
				else
					echo "<-- $CUR_COMMAND: status is OK"
					cycle_state_response $CUR_COMMAND
					# If come here we can read inputs and
					# outputs images
					read_image "RI"
					operation_end "RE"
					reset_variables
				fi 
			;;
			esac
		;;

	# Below there are only responses for our managing action.
		WO|WI)
			# There are 3 arguments for this command:
			# 1) command status;
			# 2) length of message that was written to the outputs
			# or inputs image;
			# 3) offset in the outputs or inputs image.
			NARG=$(expr $NARG + 1)

			case "$NARG" in
			1)
				if [ "$STR" != "$STATUS_OK" ] ; then
					CUR_STATE="ERROR_MSG"
				else
					echo "<-- $CUR_COMMAND: status is OK"
					reset_variables
				fi	
			;;
			esac
		;;
		RO|RI)
			# There are 4 arguments for this command:
			# 1) command status;
			# 2) length of message that was written to the outputs
			# image;
			# 3) offset in the outputs image;
			# 4) data bytes from ouputs image. 
			
			NARG=$(expr $NARG + 1)
			
			case "$NARG" in
			1)
				if [ "$STR" != "$STATUS_OK" ] ; then
					CUR_STATE="ERROR_MSG"
				else
					echo "<-- $CUR_COMMAND: status is OK"
				fi
			;;
			2)
				# Get the length
				echo "<-- $CUR_COMMAND: read message length is $STR"
			;;
			3)
				# Get offset
				echo "<-- $CUR_COMMAND: read message offset is $STR"
			;;
			4)
				# Get data
				echo "<-- $CUR_COMMAND: read message data is $STR"
				reset_variables
			;;
			esac
		;;
		WE|RE)
			# There is only one argument:
			# 1) command status.
    
			NARG=$(expr $NARG + 1)

			case "$NARG" in
			1)  
				if [ "$STR" != "$STATUS_OK" ] ; then
					CUR_STATE="ERROR_MSG"
				else
					echo "<-- $CUR_COMMAND: status is OK"
					reset_variables
				fi  
			;;  
			esac
		;;
		*)
			CUR_COMMAND=$STR
			echo "<-- $CUR_COMMAND:"
		;;
		esac	
		;;
	ERROR_MSG)
		CUR_STATE="ERROR"
		ERRSTRING=$STR
		;;
	ERROR)
		echo "error: $CUR_COMMAND $ERRSTRING"
		exit 1
		;;
	esac

done < /tmp/write

#exec 4<&-

exit 0

