#!/bin/bash
# Multiple shebangs are fine :-)

#UTIL
# error function checks?

export contate_cmd="${BASH_SOURCE}"
if [ -z "${BASH_SOURCE}" ]; then
	err "However you're running this script, ssh or somethign, we don't support that yet. Maybe main should be an actual contate function that then exports it and calls contate, that way we cab avoid knowing where we are."
	exit 1
fi

function contate {
	${contate_cmd} "${@}"
}

if [ -z "$CONTATE_DEBUG" ]; then
	export CONTATE_DEBUG=false
fi

if $CONTATE_DEBUG; then
	export CoC="c"
	export CoS="s"
	export CoE="e"
	export CoP="p"
	export CoI="i"
	export DEBUGINDENT+="."
else
	unset CoC CoS CoE CoP CoI
fi

dbg(){
	if $CONTATE_DEBUG; then
		>&2 echo "${DEBUGINDENT}DEBUG: ${@}"
	fi
}

# must will fail the last exit code isn't true
must(){
	dbg "must passed $1 for $2"
	if [ $1 -ne 0 ]; then
		err $2
		exit 1
	fi
}

# should will dbg if the last exit code isn't true
should(){
	if [ $1 -ne 0 ]; then
		dbg $2
	fi
}

dbg "defining utils"
err(){
	if ! $CONTATE_QUIET; then
		>&2 echo "ERR: ${@}"
	fi
}

usage(){
	if ! $CONTATE_QUIET; then
		>&2 echo "USAGE: " # TODO
	fi
}
dbg "util defined"

export -f dbg should must err contate
#!/bin/bash
	
export D_MASTER D_FILE D_HASH D_CACHE

# DICTIONARY
dbg "defining dictionary"

# d_reload_from_file - will just load the file into the map. It's called during d_start and probably after running another contate
function d_reload_from_file {
	dbg "d_reload_from_file() (${D_FILE})"
	dbg "file:$(cat ${D_FILE})"
	dbg "D_CACHE: $D_CACHE"
	dbg "D_HASH: $D_HASH"
	unset TEMP_HASH
	while IFS= read -r line; do
		if [ -z "$TEMP_HASH" ]; then
			dbg "TEMP_HASH EMPTY SO $line"
			TEMP_HASH="$line"
			if [ "$TEMP_HASH" = "$D_HASH" ]; then
				dbg "CACHE HIT"
				return
			fi
			unset D_CACHE
			export D_CACHE
			D_HASH="${TEMP_HASH}"
		else
			dbg "Line: $line;"
			D_CACHE+="${line} "
		fi
	done < "${D_FILE}"
	D_CACHE="${D_CACHE% }"
	dbg "D_CACHE: ${D_CACHE}"
}

# d_rewrite_to_file - will write the map to the file, should be called anytime we modify the cache
function d_rewrite_to_file {
	dbg "d_rewrite_to_file()"
	echo -n "" > "${D_FILE}"
	for key in $D_CACHE; do
		echo "$key" >> "${D_FILE}"
	done
	dbg "Wrote file:$(cat ${D_FILE})"
	D_HASH="$(md5sum "${D_FILE}")"
	sed -i "1i$D_HASH" ${D_FILE}
	dbg "Wrote file:$(cat ${D_FILE})"
	if [ -z "$(cat ${D_FILE})" ]; then
		echo "$D_HASH" > "${D_FILE}"
	fi
}

# d_start - should be called at the beginning of any contate script
function d_start {
	dbg "d_start()"
	if [[ -z "${D_MASTER}" ]]; then
		D_HASH=""
		D_MASTER=$$
		D_FILE="$(mktemp)"
		d_rewrite_to_file
	elif [[ ! -f ${D_FILE} ]]; then
		err "D_FILE should not be null if D_MASTER is non-zero"
		return 1
	fi
	dbg "D_MASTER: ${D_MASTER}"
	dbg "D_FILE: ${D_FILE}"
	d_reload_from_file
	dbg "D_CACHE: ${D_CACHE}"
}

# d_clean - should be set to trap EXIT/FINISH
function d_clean {
	dbg "d_clean()"
	if [[ "$$" = "${D_MASTER}" && -f "${D_FILE}" ]]; then
		rm "${D_FILE}"
	fi
	unset D_MASTER
	unset D_FILE
	unset D_CACHE
	unset D_HASH
}


# d_get KEY - gets key or ""
function d_get {
	dbg "d_get($1)"
	d_reload_from_file
	unset D_CACHE_MAP
	declare -A D_CACHE_MAP
	unset TEMPKEY
	dbg "D_CACHE: ${D_CACHE}"
	for thing in $D_CACHE; do
		if [ -z "${TEMPKEY}" ]; then
			TEMPKEY="$thing"
			dbg "Get, Key: $thing"
		else
			D_CACHE_MAP[$TEMPKEY]="$thing"
			dbg "Get, Val: $thing"
			unset TEMPKEY
		fi
	done
	# write map
	dbg "Keys:${!D_CACHE_MAP[*]}"
	dbg "Vals:${D_CACHE_MAP[*]}"
	dbg "Got: $(base64 -d <<< "${D_CACHE_MAP["$(base64 <<< "$1")"]}")"
	echo -n  "$(base64 -d <<< "${D_CACHE_MAP["$(base64 <<< "$1")"]}")"
}

# d_set KEY VALUE - sets KEY to VALUE
function d_set {
	dbg "d_set($1, $2)"
	d_reload_from_file
	# TODO: keys cannot contain spaces
	unset D_CACHE_MAP
	declare -A D_CACHE_MAP
	unset TEMPKEY
	dbg "D_CACHE: ${D_CACHE}"
	for thing in $D_CACHE; do
		if [ -z "${TEMPKEY}" ]; then
			TEMPKEY="$thing"
		else
			D_CACHE_MAP[$TEMPKEY]="$thing"
			unset TEMPKEY
		fi
	done
	dbg "Keys:${!D_CACHE_MAP[*]}"
	dbg "Vals:${D_CACHE_MAP[*]}"
	D_CACHE_MAP["$(base64 <<< "$1")"]="$(base64 <<< "$2")"
	dbg "Keys:${!D_CACHE_MAP[*]}"
	dbg "Vals:${D_CACHE_MAP[*]}"
	unset D_CACHE
	export D_CACHE 
	for key in "${!D_CACHE_MAP[@]}"; do
		D_CACHE+="${key} ${D_CACHE_MAP["$key"]} "
	done
	D_CACHE="${D_CACHE% }"
	dbg "D_CACHE: ${D_CACHE}"
	d_rewrite_to_file
}

# d_exists KEY - returns whether or not key exists
function d_exists {
	dbg "d_exists($1)"
	d_reload_from_file
	unset D_CACHE_MAP
	declare -A D_CACHE_MAP
	unset TEMPKEY
	dbg "D_CACHE: ${D_CACHE}"
	for thing in $D_CACHE; do
		if [ -z "${TEMPKEY}" ]; then
			TEMPKEY="$thing"
		else
			D_CACHE_MAP[$TEMPKEY]="$thing"
			unset TEMPKEY
		fi
	done
	dbg "Keys:${!D_CACHE_MAP[*]}"
	dbg "Vals:${D_CACHE_MAP[*]}"
	return $([ ${D_CACHE_MAP["$(base64 <<< "$1")"]+_} ])
}

# d_del KEY - deletes the keyu
function d_del {
	dbg "d_del($1)"
	d_reload_from_file
	# TODO: keys cannot contain spaces
	unset D_CACHE_MAP
	declare -A D_CACHE_MAP
	unset TEMPKEY
	dbg "D_CACHE: ${D_CACHE}"
	for thing in $D_CACHE; do
		if [ -z "${TEMPKEY}" ]; then
			TEMPKEY="$thing"
		else
			D_CACHE_MAP[$TEMPKEY]="$thing"
			unset TEMPKEY
		fi
	done
	dbg "Keys:${!D_CACHE_MAP[*]}"
	dbg "Vals:${D_CACHE_MAP[*]}"
	unset D_CACHE_MAP["$(base64 <<< "$1")"]
	unset D_CACHE
	export D_CACHE #TODO is this everywhere
	for key in "${!D_CACHE_MAP[@]}"; do
		D_CACHE+="${key} ${D_CACHE_MAP["$key"]} "
	done
	D_CACHE="${D_CACHE% }"
	d_rewrite_to_file
}


export -f d_start d_clean d_get d_set d_del d_reload_from_file d_rewrite_to_file
#!/bin/bash

function c_execute {
	dbg "Running $TMP_SCRIPT"
	SCRIPT_OUTPUT=""
	chmod +x "$TMP_SCRIPT"
	must $? "Failed to set +x on $TMP_SCRIPT"
	if [ "$IS_C" = true ]; then
		dbg "It is C"
		mv "$TMP_SCRIPT" "$TMP_SCRIPT".c
		must $? "Couldn't rename $TMP_SCRIPT for C"
		TMP_SCRIPT="$TMP_SCRIPT".c
	fi
	if declare -p CONTATE_CONFIG > /dev/null 2>&1 ; then
		dbg "Sourcing"
		. $TMP_SCRIPT
	else
		SCRIPT_OUTPUT="$($TMP_SCRIPT)"
	fi
	must $? "Script output failed"
	TOTAL_LINES=$(echo "$SCRIPT_OUTPUT"| wc -l)
	i=0
	while IFS='' read -r scriptoutput2; do
		dbg "Line Number:$i / $TOTAL_LINES"
		i=$(( i + 1 ))
		if [ $i -gt 1 ]; then
			if [ "${OUTPUT}" = "/dev/stdout" ]; then
			echo -n "${CoP}${INDENTATION}${scriptoutput2}"
			else
			echo -n "${CoP}${INDENTATION}${scriptoutput2}" >> "$OUTPUT"
			fi
		else
			if [ "${OUTPUT}" = "/dev/stdout" ]; then
				echo -n "${CoP}${scriptoutput2}"
			else
				echo -n "${CoP}${scriptoutput2}" >> "$OUTPUT"
			fi
		fi
		if [ $i -lt $TOTAL_LINES ]; then
			echo "" >> "$OUTPUT"
		else
			dbg "Was last line"
		fi
	done <<< "$SCRIPT_OUTPUT"
	unset IS_C
}


[ -z "${CONTATE_START_REGEX}" ] && CONTATE_START_REGEX='^(.*)#!(/.+)$'
[ -z "${CONTATE_END_REGEX}" ] && CONTATE_END_REGEX='^([[:space:]]*)!#(.*)$'
[ -z "${CONTATE_INLINE}" ] && CONTATE_INLINE='^.*#!\$\((.+)\)!#.*$'
# 1- Target file - realpath is fine, are we being passed that? it's fine to set it
# 2- Output file- this needs to be a realpath + any directories that have been or need to be made
contate_file(){ 
	dbg "TARGET=$1"
	dbg "OUTPUT=$2"
	TARGET="$1"
	OUTPUT="$2"
	IS_C=false
	unset TMP_SCRIPT
	while IFS='' read -r line || [[ -n "$line" ]]; do # if the EOF comes with no NL, second term helps
		# IN SCRIPT
		if [ -n "${TMP_SCRIPT}" ]; then
			# END BLOCK..
			if [[ "$line" =~ $CONTATE_END_REGEX ]]; then
				dbg "END SCRIP on '$line' with rematch '${BASH_REMATCH[2]}'"
				c_execute # TODO what do with output?
				if [ "${OUTPUT}" = "/dev/stdout" ]; then
					echo "${CoE}${BASH_REMATCH[2]}"
				else
					echo "${CoE}${BASH_REMATCH[2]}" >> ${OUTPUT}
				fi
				rm "$TMP_SCRIPT"
				unset TMP_SCRIPT
			else
				dbg "Process line '$line'"
				echo "${line#$INDENTATION}" >> "$TMP_SCRIPT"
			fi
		# Start Contate Block
		elif [[ "$line" =~ $CONTATE_START_REGEX ]] && [[ "${BASH_REMATCH[1]}" != *"#" ]]; then
			dbg "START SCR on '$line' with rematch: '${BASH_REMATCH[1]}'"
			if grep -qe "tcc -run" <<< "$line"; then
				IS_C=true
			fi
			# set filetype
			TMP_SCRIPT="$(mktemp)"
			echo "${line#${BASH_REMATCH[1]}}" >> "$TMP_SCRIPT"
			if [ "${OUTPUT}" = "/dev/stdout" ]; then
				echo -n "${CoS}${BASH_REMATCH[1]}"
			else
				echo -n "${CoS}${BASH_REMATCH[1]}" >>"$OUTPUT"
			fi
			[[ "$line" =~ ^([[:space:]]*) ]]
			INDENTATION="${BASH_REMATCH[1]}"
			dbg "Captured indentation: '$INDENTATION'"
		# Direct copy
		elif [[ "$line" =~ $CONTATE_INLINE ]]; then
			dbg "Whole inline line: ${BASH_REMATCH[0]}"
			dbg "Inline replacement: '${BASH_REMATCH[1]}'"
			INLINE_OUTPUT="$(eval "${BASH_REMATCH[1]}")"
			must $? "${BASH_REMATCH[1]} failed to be eval'ed"
			dbg "INLINE OUTPUT: $INLINE_OUTPUT"
			INLINE_OUTPUT="${CoI}${line/\#\!$\(${BASH_REMATCH[1]})\!#/$INLINE_OUTPUT}"
			if [ -n "$INLINE_OUTPUT" ]; then
				if [ "${OUTPUT}" = "/dev/stdout" ]; then
					echo "${INLINE_OUTPUT}"
				else
					echo "${INLINE_OUTPUT}" >> "$OUTPUT"
				fi
			fi
		else
			if [ "${OUTPUT}" = "/dev/stdout" ]; then
				echo "${CoC}$line"
			else
				echo "${CoC}$line" >> "$OUTPUT"
			fi
		fi
	done < "$TARGET"
	if [ -z "$TMP_SCRIPT" ]; then
		dbg "WROTE SCRIPT"
	else
		dbg "Reached EOF w/in script"
		c_execute # TODO what do with output?
		if [ "${OUTPUT}" = "/dev/stdout" ]; then
			echo "${CoE}${BASH_REMATCH[2]}"
		else
			echo "${CoE}${BASH_REMATCH[2]}" >> ${OUTPUT}
		fi
		rm "$TMP_SCRIPT"
		unset TMP_SCRIPT
	fi
}
#!/bin/bash

# BASIC INITIALIZATIONS

shopt -s nullglob globstar

set -e

# An array of all the variables we've created
declare -a CONTATE_TEMP_FILE_LIST

# contate_clean is a function to delete those temps and call dictionary's clean too, which celetes the variable temp

dbg "top-ish of main"
d_start
dbg "trap contate_clean()"
trap d_clean EXIT

# SET VARIABLES

[ -z "${CONTATE_RECURSE}" ] && CONTATE_RECURSE=false
[ -z "${CONTATE_QUIET}" ] && CONTATE_QUIET=false
[ -z "${CONTATE_PATTERN}" ] && CONTATE_PATTERN='(.*).contate$'
[ -z "${CONTATE_DRY}" ] && CONTATE_DRY=false
export CONTATE_QUIET CONTATE_RECURSE CONTATE_PATTERN CONTATE_OUTPUT CONTATE_DRY

while getopts ":o:p:c:dqrh" opt; do
	dbg "Getopts iteration"
	case "$opt" in
	c)
		CONTATE_CONFIG="$OPTARG"
		;;
	d)
		CONTATE_DRY=true
		;;
	o)
		CONTATE_OUTPUT="${OPTARG}"
		if [ "$CONTATE_OUTPUT" = "-" ]; then
			CONTATE_OUTPUT="/dev/stdout"
		fi
		;;
	r)
		CONTATE_RECURSE=true
		;;
	p)
		CONTATE_PATTERN="${OPTARG}"
		;;
	q)
		CONTATE_QUIET=true
		;;
	h)
		CONTATE_QUIET=false
		usage
		exit 0
		;;
	*)
		err	"Unkown opt: $OPTARG"
		usage 
		exit 1
		;;
	esac
done

dbg "All arguments passed:: ${*}"	

shift "$(( OPTIND - 1 ))"

dbg "CONTATE_DRY=${CONTATE_DRY}"
dbg "COTATE_CONFIG=${CONTATE_CONFIG}"
dbg "CONTATE_OUTPUT=${CONTATE_OUTPUT}"
dbg "CONTATE_RECURSE=${CONTATE_RECURSE}"
dbg "CONTATE_DEBUG=${CONTATE_DEBUG}"
dbg "CONTATE_QUIET=${CONTATE_QUIET}"
dbg "CONTATE_PATTERN=${CONTATE_PATTERN}"
dbg "CONTATE FILES: ${*}"

# VALIDATE FLAGS
validate_input() {
	if ${CONTATE_QUIET} && ${CONTATE_DEBUG}; then
		err "You cannot set both quiet and debug"
		exit 1
	fi

	if [[ -d "${CONTATE_OUTPUT}" ]]; then
		if [[ -w "${CONTATE_OUTPUT}" ]]; then
			dbg "CONTATE_OUTPUT is a directory"
			CONTATE_OUTPUT="$(realpath "${CONTATE_OUTPUT}")"
		else
			err "Can't write to ${CONTATE_OUTPUT}"
			exit 1
		fi
	elif [ -z "${CONTATE_OUTPUT}" ] || [ "${CONTATE_OUTPUT}" = "/dev/stdout" ]; then
		dbg "CONTATE_OUTPUT not set, so stdout"
		CONTATE_OUTPUT="/dev/stdout"
	else
		err "Contate cannot currently create directories or use a custom naming scheme for files beyond -p"
		usage
		exit
	fi
	dbg "CONTATE_OUTPUT reset to: ${CONTATE_OUTPUT}"

	if [ -n "${CONTATE_COBFIG}" ] && [ ! -d "${CONTATE_CONFIG}" ] && [ ! -f "${CONTATE_CONFIG}" ]; then
		err "-c Must set a file or directory"
		exit 1
	fi
	if [ -d "${CONTATE_CONFIG}" ]; then
		CONTATE_CONFIG="${CONTATE_CONFIG}/*"
		dbg "New CONTATE_CONFIG after directory expansion:"
		dbg "${CONTATE_CONFIG}"
	fi
}
validate_input

# RECURSE THROUGH INPUTS
recurse() {
	dbg "Passed to recurse: ${@}"
	for target; do
		if [[ -d "${target}" ]] && ! $CONTATE_RECURSE; then
			err "${target} is a directory but didn't specify -r"
			usage
			exit 1
		fi
	done
	for target; do
		dbg "Recurse is looping on ${target}"
		if [ "$D_MASTER" = "$$" ]; then
			if [ -f "$target" ]; then
				export REMOVABLE_PATH="$(dirname $(realpath $target))"/
			else
				export REMOVABLE_PATH="$(realpath $target)"/
			fi
			dbg "REMOVABLE_PATH: $REMOVABLE_PATH"
		fi
		if [[ -d "${target}" && -x "${target}" && -r "${target}" ]]; then
			dbg "TARGET DIRECTORY: ${target}"
			dbg recurse target: ${target}/*
			contate $(realpath ${target})/*
			must $? "Contate failed on one of $(realpath ${target})/*"
		elif [[ -f "${target}" && -r "${target}" ]]; then
			target="$(realpath $target)"
			dbg "TARGET FILE: ${target}"
			if [[ "${target}" =~ ${CONTATE_PATTERN} ]]; then
				if [ "${CONTATE_OUTPUT}" != "/dev/stdout" ] && [ "$SPECIFIC_OUTPUT" != "/dev/null" ]; then
					if [ -n "${BASH_REMATCH[1]}" ]; then
						dbg "Calculating output: "
						dbg "BASH_REMATCH: ${BASH_REMATCH[1]}"
						dbg "REMOVABLE_PATH: ${REMOVABLE_PATH}"
						OUTPUT_SUBPATH="${BASH_REMATCH[1]#${REMOVABLE_PATH}}"
					else
						dbg "Calculating output: "
						dbg "target: ${target}"
						dbg "REMOVABLE_PATH: ${REMOVABLE_PATH}"
						OUTPUT_SUBPATH="${target#${REMOVABLE_PATH}}"
					fi
					SPECIFIC_OUTPUT="${CONTATE_OUTPUT}/${OUTPUT_SUBPATH}"
					dbg "Is a contate file! it's output is: ${SPECIFIC_OUTPUT}"
					mkdir -p "$(dirname ${SPECIFIC_OUTPUT})"
				elif [ -z "${SPECIFIC_OUTPUT}" ]; then
					SPECIFIC_OUTPUT="${CONTATE_OUTPUT}"
				fi
				if ! $CONTATE_DRY; then 
					contate_file "${target}" "${SPECIFIC_OUTPUT}" 
					must $? "Contate file failed somewhere on ${target} to ${SPECIFIC_OUTPUT}"
				fi
			fi
		fi
	done
}
if [ -n "${CONTATE_CONFIG}" ]; then
	dbg "In contate config"
	export SPECIFIC_OUTPUT="/dev/null"
	recurse "${CONTATE_CONFIG}"
	unset SPECIFIC_OUTPUT
	unset CONTATE_CONFIG
fi

dbg "recurse targets: ${@}"
recurse "${@}"
