local util = require "core:bitwise/util"

local tokenizer = require "core:bitwise/tokenizer"
local parser = require "core:bitwise/parser"

local types = tokenizer.types

local operatorToFunctionName =
{
	[types.lshift] = "bit.lshift",
	[types.rshift] = "bit.rshift",
	[types.bnot] = "bit.bnot",
	[types.band] = "bit.band",
	[types.bxor] = "bit.bxor",
	[types.bor] = "bit.bor"
}

local function compile(str, args, asFunction)
	if asFunction == nil then
		asFunction = true
	end

	local tokens = parser(str)

	local stack = { }

	local ids = { }

	for _, token in ipairs(tokens) do
		if table.has(tokenizer.numTypes, token.type) or token.type == types.id then
			if token.type == tokenizer.types.binNum then
				util.stack.push(stack, tonumber(token.value, 2))
			elseif token.type == tokenizer.types.hexNum then
				util.stack.push(stack, "0x"..token.value)
			else
				util.stack.push(stack, token.value)

				if token.type == types.id then
					local add = true

					for i = 1, #ids do
						if ids[i].value == token.value then
							add = false
							break
						end
					end

					if add then table.insert(ids, token) end
				end
			end
		else
			local fun = operatorToFunctionName[token.type]

			local a = util.stack.pop(stack)
			local b = token.type ~= types.bnot and util.stack.pop(stack)

			if not b then
				util.stack.push(stack, fun..'('..a..')')
			else
				util.stack.push(stack, fun..'('..b..','..a..')')
			end
		end
	end

	local strArgs = ""

	if args then
		for _, id in ipairs(ids) do
			if not table.has(args, id.value) then
				util.throw(id.column, "undefined identifier")
			end
		end
	else
		args = { }

		table.sort(ids, function(a, b) return a.value:upper() < b.value:upper() end)

		for _, id in ipairs(ids) do
			table.insert(args, id.value)
		end
	end

	for i = 1, #args do
		local str = args[i]

		for j = 1, #str do
			local char = str:sub(j,j)

			if
				not string.find(tokenizer.idChars, char) or
				(j == 1 and string.find(tokenizer.startingDigits, char))
			then error("invalid argument name ("..i..")") end
		end

		strArgs = strArgs..str

		if i ~= #args then
			strArgs = strArgs..','
		end
	end

	local code = stack[1]

	code = "function("..strArgs..") return "..code.." end"

	if asFunction then
		return load("return "..code)()
	else
		return code
	end
end

return compile