#!/bin/bash

## settings
#
# files
rpt="exploit_regions.rpt"
DEF="design.def"
LEF="NangateOpenCellLibrary.lef"
ca="cells.assets"
svg="exploit_regions.svg"
png="exploit_regions.png"
gp="exploit_regions.gp"
# strings
## related to rpt
line_pattern="Sites coordinates: "
## related to gp
gp_lw="linewidth 0.1"
gp_fs_1="fillstyle solid 1"
gp_fs_02="fillstyle solid 0.2"
gp_fs_05="fillstyle solid 0.5"

# progress bar function
# https://unix.stackexchange.com/a/415450
prog() {
local w=1 p=$1;  shift
# create a string of spaces, then change them to dots
printf -v dots "%*s" "$(( $p * $w ))" ""; dots=${dots// /.};
# print those dots on a fixed-width space plus the percentage etc. 
printf "\r\e[K|%-*s| %3d %% %s" "$w" "$dots" "$p" "$*"; 
}

echo "Initializing ..."

# init array of macro sizes from LEF
#
declare -A cell_width=()
macro_size="UNDEF"
macro_name="UNDEF"

# go line by line, split lines into array
#
while read -a line; do

	# substring matching for pattern in line; ignore other lines
	if [[ "${line[0]}" == "MACRO" ]]; then

		macro_name=${line[1]}
		# reset macro_size; helps to memorize only 1st SIZE that follows, not others; works together w/ lines below
		macro_size="UNDEF"

	# double-check; for first few lines w/o any macro parsed yet, ignore SIZE lines
	elif [[ "$macro_name" != "UNDEF" && "${line[0]}" == "SIZE" ]]; then 

		macro_size=${line[1]}
	fi

	if [[ "$macro_size" != "UNDEF" ]]; then
#echo "$macro_name: $macro_size"

		cell_width[$macro_name]=$macro_size

		# reset macro again; helps to repeat assignments as well as catching another SIZE statement before
		# next macro begins
		macro_name="UNDEF"
		macro_size="UNDEF"
	fi

done < $LEF

##### start main

# clean gp file
rm -rf $gp

# extract constants from DEF
#
die_x=$(grep -w 'DIEAREA' $DEF | awk '{print $7}')
die_y=$(grep -w 'DIEAREA' $DEF | awk '{print $8}')
core_ll_x=$(grep -E "(DESIGN).+LL_X" $DEF | awk '{print $4}')
core_ll_y=$(grep -E "(DESIGN).+LL_Y" $DEF | awk '{print $4}')
core_ur_x=$(grep -E "(DESIGN).+UR_X" $DEF | awk '{print $4}')
core_ur_y=$(grep -E "(DESIGN).+UR_Y" $DEF | awk '{print $4}')
units=$(grep -w 'UNITS DISTANCE' $DEF | awk '{print $4}')
#echo $die_x
#echo $die_y
#echo $core_ll_x
#echo $core_ll_y
#echo $core_ur_x
#echo $core_ur_y
#echo $units
#
# rows, as defined by DEF outline in X direction and by LEF for Y direction (height)
core_row_ll_x=$core_ll_x
core_row_ur_x=$core_ur_x
# first row starts at ll of core as well
core_row_ll_y=$core_ll_y
# count all rows
core_row_count=$(grep -c -w 'ROW' $DEF)
# NOTE here be dragons (little ones though): expects size of rows to come first in LEF; but, would still work in case
# there are only single-row cells, which is true for Nangate
core_row_height=$(grep -w 'SIZE' $LEF | head -n 1 | awk '{print $4}')

# gp header
#
gp_obj_idx=1
#
echo "set terminal svg" >> $gp
echo "set output '$svg'" >> $gp
echo "set xrange[0:$die_x]" >> $gp
echo "set yrange[0:$die_y]" >> $gp
#
# gp uses inverse ratio
## awk for printing leading zero, as required by gp but omitted by bc
ratio=$(bc -l <<< "scale=4; ($die_y / $die_x)" | awk '{printf "%f", $0}')
echo "set size ratio $ratio" >> $gp
#
# core rect
#
rect="set object $gp_obj_idx rect from "
gp_obj_idx=$((gp_obj_idx + 1))
ll_x=$(bc -l <<< "($core_ll_x * $units)")
rect=$rect$ll_x
rect=$rect", "
ll_y=$(bc -l <<< "($core_ll_y * $units)")
rect=$rect$ll_y
rect=$rect" to "
ur_x=$(bc -l <<< "($core_ur_x * $units)")
rect=$rect$ur_x
rect=$rect", "
ur_y=$(bc -l <<< "($core_ur_y * $units)")
rect=$rect$ur_y
echo $rect >> $gp

# core row rects
#
## consider row to span whole x range; just compute y coordinates for each row 
ll_x=$(bc -l <<< "($core_row_ll_x * $units)")
ur_x=$(bc -l <<< "($core_row_ur_x * $units)")
for ((i=0; i<$core_row_count; i++)); do

	rect="set object $gp_obj_idx rect from "
	gp_obj_idx=$((gp_obj_idx + 1))

	rect=$rect$ll_x
	rect=$rect", "

	ll_y=$(bc -l <<< "($core_row_ll_y * $units)")
	rect=$rect$ll_y
	rect=$rect" to "

	rect=$rect$ur_x
	rect=$rect", "

	core_row_ur_y=$(bc -l <<< "($core_row_ll_y + $core_row_height)")
	core_row_ll_y=$core_row_ur_y
	ur_y=$(bc -l <<< "($core_row_ur_y * $units)")
	rect=$rect$ur_y

	rect=$rect" fillcolor rgb \"white\" $gp_fs_1 $gp_lw"

	echo $rect >> $gp
done

echo "Generating plot data for components ..."

# go over lines from DEF file; generate each placed component as rect for gp
#

# NOTE '-' in front
#- key_sbox\/U20 OAI221_X1 + PLACED ( 61180 16520 ) FN
#- CTS_ccl_BUF_CRYPTOCLK_G0_L1_6 CLKBUF_X3 + FIXED ( 52440 66920 ) N + WEIGHT 1
#- FE_OFC9_n581 BUF_X16 + SOURCE TIMING + PLACED ( 50920 75320 ) FS 
regex_line_DEF="^- (\S+) (\S+).+[+] (PLACED|FIXED).[(] ([0-9]+) ([0-9]+) [)].+"

# first, need to store all relevant lines, to be able to iterate over them and know about the index at the same time (for parallel processing)
# NOTE here be dragons: components should have "-" in front, whereas placed/fixed pins would have "+" in front; also expects components to be defined in one line
# NOTE readarray works with this, whereas read -a didn't; readarray seems preferred in general:
# https://stackoverflow.com/questions/11393817/read-lines-from-a-file-into-a-bash-array/11395181
readarray -t lines < <(grep -E "$regex_line_DEF" $DEF)
#declare -p lines

# also read cell assets into array, to compare against components to be plotted
#
## NOTE backslashes in assets names are properly escaped by readarray
readarray -t ca_ < $ca
#declare -p ca_
#
# declare another, associative array; fast to access later on
declare -A ca__
for line in "${ca_[@]}"; do
	# simple array; actually only indices matter actually, dummy value of 1 to indicate is asset; other non-assets
	# cells are identified as such by absence of related index in array
	ca__[$line]=1
done

components_total=$(grep -E '(COMPONENTS )[0-9]+' $DEF | awk '{print $2}')

if [[ ${#lines[@]} != $components_total ]]; then
	echo "WARNING: Number of parsed components (${#lines[@]}) differs from number specified in DEF ($components_total) ..."
fi

for ((i=0; i<${#lines[@]}; i++)); do

	# progress bar
	prog $(( 100 * (i+1)/components_total ))

	## parallel runs; see notes below
	(

	line=${lines[$i]}

	if [[ "$line" =~ $regex_line_DEF ]]; then

#		declare -p BASH_REMATCH

		cell_name=${BASH_REMATCH[1]}
		cell_macro=${BASH_REMATCH[2]}

		cell_size=${cell_width[$cell_macro]}
#echo "PARSED CELL: $cell_name - $cell_macro - $cell_size"

		# build up rect command
		#
		rect="set object $((gp_obj_idx + i)) rect from "

		ll_x=${BASH_REMATCH[4]}
		rect=$rect$ll_x
		rect=$rect", "

		ll_y=${BASH_REMATCH[5]}
		rect=$rect$ll_y
		rect=$rect" to "

		ur_x=${BASH_REMATCH[4]}
		ur_x=$(bc -l <<< "($ur_x + ($cell_size * $units))")
		rect=$rect$ur_x
		rect=$rect", "

		# NOTE here be dragons (little ones though): ur_y is assumed to be defined by row height; works for
		# Nangate as there are no multi-row cells
		ur_y=${BASH_REMATCH[5]}
		ur_y=$(bc -l <<< "($ur_y + ($core_row_height * $units))")
		rect=$rect$ur_y

		## highlight asset components
		#
		# NOTE ``How to Find a Missing Index in an Associative Array''
		# https://www.fosslinux.com/45772/associative-array-bash.htm
		if [ ${ca__[$cell_name]+_} ]; then
#			echo $cell_name
			rect=$rect" fillcolor rgb \"blue\" $gp_fs_05 $gp_lw"
		else
			rect=$rect" fillcolor rgb \"grey\" $gp_fs_02 $gp_lw"
		fi

		echo $rect >> $gp
	fi

	) &
done
wait

# final newline to finish progress bar
echo ""

# increment gp_obj_incr by all components covered now
## NOTE the alternative, incrementing gp_obj_incr in each parallel run, does not work as each run
## starts with local copy of gp_obj_incr
# NOTE incr might be larger than needed, namely when some lines dont match regexp; however, such mismatch is
# unexpected and even if so, gnuplot should not be bothered by some object ids missing in between
gp_obj_incr=$(echo ${#lines[@]})
gp_obj_idx=$((gp_obj_idx + gp_obj_incr))

echo "Generating plot data of sites for all regions ..."

# go over lines from rpt file; generate each site as rect for gp
#
region_idx=1
region_dbg=1
regions_total=$(grep -c "$line_pattern" $rpt)

# NOTE explicitly reading full lines w/o splitting, as this is done below based on "} {" delimiters for sites
while read line; do

#	# NOTE for dbg only
#	## use only this to limit the number of regions
#	if [[ $region_idx -gt $region_dbg ]]; then
#		break
#	fi

	# substring matching for pattern in line; ignore other lines
	if [[ "$line" == *"$line_pattern"* ]]; then

#		# NOTE for dbg only
#		## use also this to consider only the particular region_dbg
#		if [[ $region_idx -lt $region_dbg ]]; then
#
#			#echo $region_idx
#			region_idx=$((region_idx + 1))
#			continue
#		fi

		# progress bar
		prog $((100*region_idx/regions_total))

		#NOTE comment out the idx increment for dbg; check about sites being unique or not, so use common index for all
		# --> sites are not unique; means that sites being considered as exploitable for multiple regions are reported
		# multiple times
		region_idx=$((region_idx + 1))

		# cut pattern from beginning of line
		line=$(echo "$line" | cut -d ' ' -f 3-)
#echo "Line: $line"

		# split coordinates lines into individual sites, based on brackets between them
		#
		delimiter="} {"
		# also pad line such that full delimiter is found at beginning and end
		line="} "$line" {"

		sites=()
		while [[ $line ]]; do
			sites+=( "${line%%"$delimiter"*}" )
			line=${line#*"$delimiter"}
		done
#declare -p sites

		# sites[0] is empty, probably due to splitting procedure
		# thus, start from i=1 and also decrement idx to have ids continously used
		gp_obj_idx=$((gp_obj_idx - 1))
		for ((i=1; i<${#sites[@]}; i++)); do
#echo "Site: ${sites[$i]}"
		
			## start frame of code to be run in parallel
			## https://unix.stackexchange.com/a/103921
			(

			# build up rect command
			#
			# NOTE read -a is required for simple splitting by space
			read -a coords <<< ${sites[$i]}

			rect="set object $((gp_obj_idx + i)) rect from "

			ll_x=$(bc -l <<< "(${coords[0]} * $units)")
			rect=$rect$ll_x
			rect=$rect", "

			ll_y=$(bc -l <<< "(${coords[1]} * $units)")
			rect=$rect$ll_y
			rect=$rect" to "

			ur_x=$(bc -l <<< "(${coords[2]} * $units)")
			rect=$rect$ur_x
			rect=$rect", "

			ur_y=$(bc -l <<< "(${coords[3]} * $units)")
			rect=$rect$ur_y

			rect=$rect" fillcolor rgb \"red\" $gp_fs_02 $gp_lw"

			echo $rect >> $gp

			## end frame of code to be run in parallel
			) &
		done
		# wait until parallel runs are done
		wait

		# increment gp_obj_incr by all sites covered now
		## NOTE the alternative, incrementing gp_obj_incr in each parallel run, does not work as each run
		## starts with local copy of gp_obj_incr
		gp_obj_incr=$(echo ${#sites[@]})
		gp_obj_idx=$((gp_obj_idx + gp_obj_incr))
	fi

done < $rpt
# final newline to finish progress bar
echo ""

echo "plot NaN notitle" >> $gp

echo "Actual plotting of svg via gnuplot ..."
gnuplot $gp

echo "Additional plotting of png via inkscape ..."
inkscape $svg -w 1800 -h 1440 -e $png
