#!/usr/bin/env bash

set -ef

check_tools () {
	echo -n "Checking for prerequisites..."
	if not hash jq &>/dev/null ; then
		echo "Please, install jq."
		exit 1
	fi

	if not hash rsync &>/dev/null ; then
		echo "Please, install rsync."
		exit 1
	fi
	echo " done."
}

mbed_new () {
	echo -n "Creating MbedOS Application..."
	#always work in /tmp
	cd /tmp/

	if [ ! -d mbed-os-program ]; then
		mbed new mbed-os-program
	fi
	cd mbed-os-program
	echo " done."
}

mbed_revision () {
	echo -n "Checking out preferred 'mbed-os' version..."
	if [ "$MBED_UPDATE" -eq 1 ]; then
		echo -n " Updating to latest..."
		set +e
		mbed update
		set -e
	fi

	if [ -n "$REMOTE_BRANCH" ]; then
		echo -n " Checking out remote branch $REMOTE_BRANCH..."
		# checkout the mbed-os version you prefer...
		cd mbed-os
		git checkout "$REMOTE_BRANCH"
		cd ..
	fi

	if [ -n "$LOCAL_REPO" ]; then
		echo -n " Linking local repo $LOCAL_REPO..."
		# ... or link your local repo
		if [ -d mbed-os ]; then
			if [ ! -L mbed-os ]; then
				rm -rf mbed-os
				ln -s "$LOCAL_REPO" mbed-os
			fi
		fi
	fi
	echo " done."

}

create_mbed_program () {
	echo -n "Setting up Mbed Application..."
	rm -rf .mbedignore

	mbed target "$BOARDNAME"
	mbed toolchain GCC_ARM

	cat > main.cpp << MAIN_C
#include "mbed.h"
int main() {}
MAIN_C

	if [ ! -f "$ARDUINOVARIANT"/conf/mbed_app.json ]; then
		echo "================================================"
		echo "Please, consider creating a 'conf/mbed_app.json'"
		echo "to avoid mbed-cli always recompile from scratch."
		echo "================================================"
		cat > mbed_app.json << MBED_APP
{
	"macros": [
		"MBED_HEAP_STATS_ENABLED=1",
		"MBED_STACK_STATS_ENABLED=1",
		"MBED_MEM_TRACING_ENABLED=1"
		],
	"target_overrides": {
		"*": {
			"platform.stdio-buffered-serial": true,
			"platform.stdio-baud-rate": 115200,
			"platform.default-serial-baud-rate": 115200,
			 "rtos.main-thread-stack-size": 32768
		}
	}
}
MBED_APP
	fi

	if [ -d "$ARDUINOVARIANT"/conf ]; then
		find "$ARDUINOVARIANT"/conf/ -type f -exec cp -p '{}' . ';'
	fi

	echo " done."
}


apply_patches () {
	if [ "$APPLY_PATCHES" -eq 1 ]; then
		echo -n "Applying patches..."
		if [ -d "$MBED_CORE_LOCATION"/patches ]; then
			cd mbed-os
			find "$MBED_CORE_LOCATION"/patches/ -type f -print0 | sort -z | xargs -t -0 -n1 git apply
			cd -
		fi
		echo " done."
		if [ "$RESTORE_GDB_INFO" -eq 1 ]; then
			echo "Restoring gdb info (this increases libmbed binary size, not suitable for release)"
			cd mbed-os
			git checkout tools/profiles/develop.json
			cd -
		fi
	fi
}

mbed_compile () {
	echo -n "Compiling Mbed Application..."
	if [ "$MBED_CLEAN" -eq 1 ]; then
		echo -n "Cleaning..."
		rm -rf BUILD
	fi

	PROFILE_FLAG=""
	if [ x"$PROFILE" != x ]; then
		if [ -f "$ARDUINOVARIANT/conf/profile/$PROFILE.json" ]; then
			PROFILE_FLAG=--profile="$ARDUINOVARIANT"/conf/profile/$PROFILE.json
		else
			PROFILE_FLAG=--profile="${PROFILE}"
		fi
		export PROFILE=-${PROFILE^^}
	fi

	mbed compile $PROFILE_FLAG --source . -v \
	| tee >(cat | grep 'Compile \[' >&2) | grep "Macros:" > "$BOARDNAME".macros.txt
	echo " done."
}

generate_defines () {
	echo -n "Generating defines..."
	cut -f2 -d":" < "$BOARDNAME".macros.txt | tr ' ' '\n'  | sed 's#\"#\\"#g' | sort > "$ARDUINOVARIANT"/defines.txt
	echo "-DMBED_NO_GLOBAL_USING_DIRECTIVE=1" >> "$ARDUINOVARIANT"/defines.txt
	MAJOR=$(echo $VERSION| cut -d'.' -f 1)
	MINOR=$(echo $VERSION| cut -d'.' -f 2)
	PATCH=$(echo $VERSION| cut -d'.' -f 3)
	echo "-DCORE_MAJOR=$MAJOR" >> "$ARDUINOVARIANT"/defines.txt
	echo "-DCORE_MINOR=$MINOR" >> "$ARDUINOVARIANT"/defines.txt
	echo "-DCORE_PATCH=$PATCH" >> "$ARDUINOVARIANT"/defines.txt
	if [ -f "$ARDUINOVARIANT"/variant.cpp ]; then
		echo '-DUSE_ARDUINO_PINOUT' >> "$ARDUINOVARIANT"/defines.txt
	fi
	echo " done."
}

generate_includes () {
	echo -n "Generating includes..."

	find ./BUILD/"$BOARDNAME"/GCC_ARM${PROFILE}/ -type f -name '.include*' -print0 | xargs -0 cat \
	| tr ' ' '\n' | tr -d '"' | sed -e 's#-I./mbed-os#-iwithprefixbefore/mbed#g' \
	| sed '/^-I./d' | sed '/lwipstack/d' | cat \
	> "$ARDUINOVARIANT"/includes.txt

	echo -n " copying to destination... "

	cd mbed-os
	cut -d'/' -f3- < "$ARDUINOVARIANT"/includes.txt | grep 'targets' \
	| xargs -I{} find {} -maxdepth 2 -name '*.h' \
	| xargs -I{} cp --parent {} "$ARDUINOCOREMBED"/
	cd -

	echo " done."
}

generate_flags () {
	echo -n "Generating flags..."
	for fl in c cxx ld; do
		jq -r '.flags | .[] | select(. != "-MMD")' ./BUILD/"$BOARDNAME"/GCC_ARM${PROFILE}/.profile-${fl} \
		> "$ARDUINOVARIANT"/${fl}flags.txt
		if [[ $ARDUINOVARIANT == *PORTENTA* || $ARDUINOVARIANT == *GIGA* || $ARDUINOVARIANT == *NICLA_VISION* || $ARDUINOVARIANT == *OPTA* ]]; then
			echo "Patching '-fno-exceptions' flag for $ARDUINOVARIANT/${fl}flags.txt"
			sed -i '/-fno-exceptions/d' "$ARDUINOVARIANT"/${fl}flags.txt
			set +e
			HAS_OPENAMP_SECTION=`grep openamp_section "$ARDUINOVARIANT"/linker_script.ld`
			set -e
			if [ x"$HAS_OPENAMP_SECTION" == x ]; then
				echo "Adding OpenAMP section to $ARDUINOVARIANT/linker_script.ld"
				OPENAMP_SECTION=".openamp_section (NOLOAD) : {\n \
				. = ABSOLUTE(0x38000000);\n \
				*(.resource_table)\n \
				} >RAM_D3  AT > FLASH\n \
				.pdm_section (NOLOAD) : {\n \
				. = ABSOLUTE(0x3800FC00);\n \
				*(.pdm_buffer)\n \
				} > RAM_D3\n"

				if [[ $ARDUINOVARIANT == *PORTENTA*M7* || $ARDUINOVARIANT == *GIGA* || $ARDUINOVARIANT == *OPTA* ]]; then
					OPENAMP_SECTION="${OPENAMP_SECTION} \
					_dtcm_lma = __etext + SIZEOF(.data);\n \
					.dtcm : AT(_dtcm_lma) {\n \
					_sdtcm = .;\n \
					*(.dtcm*)\n \
					_edtcm = .;\n \
					} > DTCMRAM"
				fi

				sed -i "s?.heap (COPY):?${OPENAMP_SECTION}\n    .heap (COPY):?g" $ARDUINOVARIANT/linker_script.ld
				OPENAMP_REGIONS="__OPENAMP_region_start__  = 0x38000400;\n__OPENAMP_region_end__ = 0x38000400 + LENGTH(RAM_D3) - 1K;"
				sed -i "s?ENTRY(Reset_Handler)?${OPENAMP_REGIONS}\nENTRY(Reset_Handler)?g" $ARDUINOVARIANT/linker_script.ld
			fi
			echo "Patching linker scripts"
			sed -i 's/0x8100000/CM4_BINARY_START/g' "$ARDUINOVARIANT"/linker_script.ld
			sed -i 's/LENGTH = 0x200000/LENGTH = CM4_BINARY_END - CM4_BINARY_START/g' "$ARDUINOVARIANT"/linker_script.ld
			sed -i 's/LENGTH = 0x1c0000/LENGTH = CM4_BINARY_START - 0x8040000/g' "$ARDUINOVARIANT"/linker_script.ld
		fi
		if [[ $ARDUINOVARIANT == *NANO_RP2040* ]]; then
			set +e
			HAS_2NDSTAGE_SECTION=`grep second_stage_ota "$ARDUINOVARIANT"/linker_script.ld`
			set -e
			if [ x"$HAS_2NDSTAGE_SECTION" == x ]; then
				echo "Adding second stage bootloader section to Nano RP2040 Connect"
				SECOND_STAGE_SECTION=".second_stage_ota : {\n \
        KEEP (*(.second_stage_ota))\n \
    } > FLASH"
				sed -i "s?.flash_begin?${SECOND_STAGE_SECTION}\n    .flash_begin?g" $ARDUINOVARIANT/linker_script.ld
			fi
		fi
	done
	echo " done."
}

generate_libs () {
	echo -n "Generating libs..."
	tr ' ' '\n' < ./BUILD/"$BOARDNAME"/GCC_ARM${PROFILE}/.link_options.txt | grep "\.o" | grep -v "/main\.o" \
	| xargs arm-none-eabi-ar rcs ./BUILD/mbed-core-"$BOARDNAME".a

	cp ./BUILD/mbed-core-"$BOARDNAME".a "$ARDUINOVARIANT"/libs/libmbed.a
	cp ./BUILD/"$BOARDNAME"/GCC_ARM${PROFILE}/.link_script.ld "$ARDUINOVARIANT"/linker_script.ld
	cp ./BUILD/"$BOARDNAME"/GCC_ARM${PROFILE}/mbed_config.h "$ARDUINOVARIANT"/

	sed -i "s/custom_mbedtls_config.h/conf\/custom_mbedtls_config.h/" $ARDUINOVARIANT/mbed_config.h

	# TODO: discover needed libraries based on compile target
	#find -L . -name 'lib*.a' -exec cp '{}' "$ARDUINOVARIANT"/libs/ ';'
	echo " done."
}

copy_core_files () {
	echo -n "Copying generic MbedOS headers to core... "

	rsync -zar --exclude="targets/" --exclude="*TEST*/" --include="*/" --include="*.h"    --exclude="*" \
	mbed-os/ "$ARDUINOCOREMBED"/

	rsync -zar --exclude="targets/" --exclude="*TEST*/" --include="*/" --include="mstd_*" --exclude="*" \
	mbed-os/ "$ARDUINOCOREMBED"/

	echo " done."
}

patch_mbed_h () {
	echo -n "Patching 'mbed.h'..."
	if [ x`uname` == xLinux ]; then
		sed -i 's?#include "platform/mbed_version.h"?#include "platform/mbed_version.h"\n#include "mbed_config.h"?g' \
		"$ARDUINOCOREMBED"/mbed.h
	else
		ed "$ARDUINOCOREMBED"/mbed.h >/dev/null <<EOF
/#include "platform\/mbed_version.h"/
a
#include "mbed_config.h"
.
wq
EOF
	fi
	echo " done."
}

patch_spi_h () {
	echo -n "Patching SPI headers..."
	mv "$ARDUINOCOREMBED"/drivers/include/drivers/SPI.h "$ARDUINOCOREMBED"/drivers/include/drivers/SPIMaster.h
	for header in mbed.h connectivity/drivers/nfc/PN512/include/nfc/controllers/PN512SPITransportDriver.h storage/blockdevice/COMPONENT_SPIF/include/SPIF/SPIFBlockDevice.h storage/blockdevice/COMPONENT_SD/include/SD/SDBlockDevice.h; do
		sed -i.bak 's#drivers/SPI\.h#drivers/SPIMaster\.h#g' "$ARDUINOCOREMBED"/$header
		rm "$ARDUINOCOREMBED"/$header.bak
	done
	echo " done."
}

#############
# MAIN LOOP #
#############

while getopts "cuagr:b:p:" opt; do
	case $opt in
	c ) export MBED_CLEAN=1 ;;
	u ) export MBED_UPDATE=1 ;;
	a ) export APPLY_PATCHES=1 ;;
	g ) export RESTORE_GDB_INFO=1 ;;
	r ) export LOCAL_REPO="$OPTARG" ;;
	b ) export REMOTE_BRANCH="$OPTARG" ;;
	p )
		MBED_CORE_LOCATION="$(cd "$OPTARG" && pwd -P)"
		export MBED_CORE_LOCATION ;;
	* )
		echo "Unknown parameter."
		exit 1
		;;
	esac
done

# Done with processing all the starting flags with getopts...
shift $(( OPTIND - 1 ))

if [ $# -eq 0 ] ; then
	echo "Usage: $(basename $0) [-c] [-u] [-r /path/to/local/repo] [-b mbed/remote/branch] [-p /mbed/core/location] [VARIANT1:BOARD1 VARIANT2:BOARD1 VARIANT3:BOARD3 ... ]"
	echo
	echo " -c clean Mbed application BUILD directory"
	echo " -u update to latest mbed-os release"
	echo " -a apply patches"
	echo " -b specify remote mbed-os branch to checkout"
	echo " -r specify local mbed-os directory to link"
	echo " -p specify local mbed core directory (defaults to PWD)"
	echo " -g restores debug information"
	echo
	echo "Example:"
	echo " $(basename $0) -a ARDUINO_NANO33BLE:ARDUINO_NANO33BLE CHALLENGE_PMC_R2DX:ARDUINO_NANO33BLE"
	exit 0
fi


declare -A VARIANT_BOARDS

TUPLES=( "$@" )

for tuple in "${TUPLES[@]}"; do
    OLD_IFS="$IFS"
    IFS=":"
    set -- $tuple
    VARIANT_BOARDS[$1]="$2"
    IFS="$OLD_IFS"
done

export MBED_CORE_LOCATION=${MBED_CORE_LOCATION:-$PWD}
export MBED_CLEAN=${MBED_CLEAN:-0}
export MBED_UPDATE=${MBED_UPDATE:-0}
export APPLY_PATCHES=${APPLY_PATCHES:-0}
export RESTORE_GDB_INFO=${RESTORE_GDB_INFO:-0}
export LOCAL_REPO=${LOCAL_REPO:-""}
export REMOTE_BRANCH=${REMOTE_BRANCH:-""}

echo
echo MBED_CLEAN=$MBED_CLEAN
echo MBED_UPDATE=$MBED_UPDATE
echo APPLY_PATCHES=$APPLY_PATCHES
echo RESTORE_GDB_INFO=$RESTORE_GDB_INFO
echo LOCAL_REPO="$LOCAL_REPO"
echo REMOTE_BRANCH="$REMOTE_BRANCH"
echo MBED_CORE_LOCATION="$MBED_CORE_LOCATION"
echo

for variant in ${!VARIANT_BOARDS[*]}; do
    echo "VARIANT=$variant BOARD=${VARIANT_BOARDS[$variant]}"
done

check_tools
mbed_new
mbed_revision

for VARIANT in ${!VARIANT_BOARDS[*]}; do
	export BOARDNAME="${VARIANT_BOARDS[$variant]}"
	export ARDUINOVARIANT="$MBED_CORE_LOCATION"/variants/"$VARIANT"
	export ARDUINOCOREMBED="$MBED_CORE_LOCATION"/cores/arduino/mbed

	if [ -d "$ARDUINOVARIANT" ]; then
		mkdir -p "$ARDUINOVARIANT"/libs
	fi

	if [ -d "$ARDUINOVARIANT"/conf/profile ]; then
		export PROFILE="custom"
	fi

	create_mbed_program
	apply_patches
	mbed_compile
	generate_defines
	generate_includes
	generate_libs
	generate_flags
done

copy_core_files
patch_mbed_h
patch_spi_h

echo
echo "[$(basename $0)] MbedOS core generation ended succesfully."
echo

# TODO
# - Add include path for rpc library to envie
#

exit 0

##################
# Using Arduino as an mbed library
#
# echo -e "arduino/cores/arduino/main.cpp\n arduino/cores/arduino/mbed/\narduino/libraries/" > .mbedignore
# #add ARDUINO_AS_MBED_LIBRARY=1 to macros section in mbed_app.json
# echo "https://github.com/arduino/ArduinoCore-mbed#bf6e64771ebe20285b0364756dff856ebbc679dc" > arduino.lib