module Utils

using CUDA

"Converts a vector of vectors into a matrix. The inner vectors do not need to have the same length.

All entries that cannot be filled have ```invalidElement``` as their value
"
function convert_to_matrix(vecs::Vector{Vector{T}}, invalidElement::T)::Matrix{T} where T
	maxLength = get_max_inner_length(vecs)

	# Pad the shorter vectors with the invalidElement
	paddedVecs = [vcat(vec, fill(invalidElement, maxLength - length(vec))) for vec in vecs]
	vecMat = hcat(paddedVecs...)

	return vecMat
end

"Retrieves the number of entries for the largest inner vector"
function get_max_inner_length(vecs::Vector{Vector{T}})::Int where T
	return maximum(length.(vecs))
end

"Returns a CuArray filed with the data provided. The inner vectors do not have to have the same length. All missing elements will be the value ```invalidElement```"
function create_cuda_array(data::Vector{Vector{T}}, invalidElement::T)::CuArray{T} where T
	dataMat = convert_to_matrix(data, invalidElement)
	cudaArr = CuArray(dataMat)

	return cudaArr
end

struct RegisterManager
	registers::Dict
	symtable::Dict
end

function get_next_free_register(manager::RegisterManager, name::String)::String
	if haskey(manager.registers, name)
		manager.registers[name] += 1
	else
		manager.registers[name] = 1
	end

	return string("%", name, manager.registers[name] - 1)
end

function get_register_definitions(manager::RegisterManager)::String
	registersBuffer = IOBuffer()

	for definition in manager.registers
		regType = ""
		if definition.first == "p"
			regType = ".pred"
		elseif definition.first == "f"
			regType = ".f32"
		elseif definition.first == "var"
			regType = ".f32"
		elseif definition.first == "param"
			regType = ".f32"
		elseif definition.first == "r"
			regType = ".b32"
		elseif definition.first == "rd"
			regType = ".b64"
		elseif definition.first == "parameter"
			regType = ".b64"
		elseif definition.first == "i"
			regType = ".b64"
		else
			throw(ArgumentError("Unknown register name used. Name '$(definition.first)' cannot be mapped to a PTX type."))
		end
		println(registersBuffer, ".reg $regType   %$(definition.first)<$(definition.second)>;")
	end

	return String(take!(registersBuffer))
end

"Returns the register for this variable/parameter and true if it is used for the first time and false otherwise."
function get_register_for_name(manager::RegisterManager, varName::String)
	if haskey(manager.symtable, varName)
		return (manager.symtable[varName], false)
	else
		reg = get_next_free_register(manager, "var")
		manager.symtable[varName] = reg
		return (reg, true)
	end
end

end