module ExpressionProcessing

export expr_to_postfix, is_binary_operator
export PostfixType
export Operator, ADD, SUBTRACT, MULTIPLY, DIVIDE, POWER, ABS, LOG, EXP, SQRT
export ElementType, EMPTY, FLOAT32, OPERATOR, INDEX
export ExpressionElement

@enum Operator ADD=1 SUBTRACT=2 MULTIPLY=3 DIVIDE=4 POWER=5 ABS=6 LOG=7 EXP=8 SQRT=9
@enum ElementType EMPTY=0 FLOAT32=1 OPERATOR=2 INDEX=3

const unary_operators = [ABS, LOG, EXP, SQRT]

struct ExpressionElement
	Type::ElementType
	Value::Int32 # Reinterpret the stored value to type "ElementType" when using it
end

const PostfixType = Vector{ExpressionElement}
const cache = Dict{Expr, PostfixType}()
"
Converts a julia expression to its postfix notation.
NOTE: All 64-Bit values will be converted to 32-Bit. Be aware of the lost precision
"
function expr_to_postfix(expr::Expr)::PostfixType
	if haskey(cache, expr)
		return cache[expr]
	end

	postfix = PostfixType()
	@inbounds operator = get_operator(expr.args[1])

	@inbounds for j in 2:length(expr.args)
		arg = expr.args[j]

		if typeof(arg) === Expr
			append!(postfix, expr_to_postfix(arg))
		elseif typeof(arg) === Symbol # variables/parameters
			# maybe TODO: replace the parameters with their respective values, as this might make the expr evaluation faster
			exprElement = convert_to_ExpressionElement(convert_var_to_int(arg))
			push!(postfix, exprElement)
		else
			exprElement = convert_to_ExpressionElement(convert(Float32, arg))
			push!(postfix, exprElement)
		end

		# only add operator if at least 2 values are added. Needed because e.g. multiple consecutive additions are one subtree with one operator, but multiple operators need to be added to the postfix notation. 
		# For the case where another expression has already been added, we check if we are at the first iteration or not ( j != 2)
		if length(postfix) >= 2 && j != 2
			exprElement = convert_to_ExpressionElement(operator)
			push!(postfix, exprElement)
		end
	end

	# For the case this expression has an operator that only takes in a single value like "abs(x)"
	if operator in unary_operators 
		push!(postfix, convert_to_ExpressionElement(operator))
	end
	return postfix
end

function get_operator(op::Symbol)::Operator
	if op == :+
		return ADD
	elseif op == :-
		return SUBTRACT
	elseif op == :*
		return MULTIPLY
	elseif op == :/
		return DIVIDE
	elseif op == :^
		return POWER
	elseif op == :abs
		return ABS
	elseif op == :log
		return LOG
	elseif op == :exp
		return EXP
	elseif op == :sqrt
		return SQRT
	elseif op == :powabs
		return POWER # TODO: Fix this
	else
		throw("Operator unknown")
	end
end

"Extracts the number from a variable/parameter and returns it. If the symbol is a parameter ```pn```, the resulting value will be negativ.

```x0 and p0``` are not allowed."
function convert_var_to_int(var::Symbol)::Int32
	varStr = String(var)
	number = parse(Int32, SubString(varStr, 2))

	if varStr[1] == 'p'
		number = -number
	end

	return number
end

function convert_to_ExpressionElement(element::Int32)::ExpressionElement
	value = reinterpret(Int32, element)
	return ExpressionElement(INDEX, value)
end
function convert_to_ExpressionElement(element::Int64)::ExpressionElement
	value = reinterpret(Int32, convert(Int32, element))
	return ExpressionElement(INDEX, value)
end
function convert_to_ExpressionElement(element::Float32)::ExpressionElement
	value = reinterpret(Int32, element)
	return ExpressionElement(FLOAT32, value)
end
function convert_to_ExpressionElement(element::Float64)::ExpressionElement
	value = reinterpret(Int32, convert(Float32, element))
	return ExpressionElement(FLOAT32, value)
end
function convert_to_ExpressionElement(element::Operator)::ExpressionElement
	value = reinterpret(Int32, element)
	return ExpressionElement(OPERATOR, value)
end

function is_binary_operator(operator::Operator)::Bool
	if operator == ADD
		return true
	elseif operator == SUBTRACT
		return true
	elseif operator == MULTIPLY
		return true
	elseif operator == DIVIDE
		return true
	elseif operator == POWER
		return true
	elseif operator == ABS
		return false
	elseif operator == LOG
		return false
	elseif operator == EXP
		return false
	elseif operator == SQRT
		return false
	else
		throw(ArgumentError("Unknown operator '$operator'. Cannot determine if it is binary or not."))
	end
end

end