tinylua = setmetatable({}, { __call = function(self, ...) return self.Wrap(...) end})
local INTERNAL = {}
local META = {}

local function pack(...) -- Convenient argument packer
	local len, tbl = select('#', ...), {...}
	local packFuncs = {}

	function packFuncs.unpack()
		return unpack(tbl, 1, len)
	end

	return setmetatable(tbl, {
		__index = function(self, index)
			return packFuncs[index] or tbl[index]
		end,
		__call = function(...)
			return len, tbl
		end
	})
end

local function Wrap(input)
	local values = {}
	local meta = {}

	for ind, val in pairs(input)do
		values[(tonumber(ind) and val or ind)] = val
	end

	for ind, val in pairs(META)do
		meta[ind] = val
	end

	return setmetatable(values, meta)
end

local function performCall(tbl, callback)
	local results = {}
	local errors = {}
	local calls = 0

	local iKey, iVal = nil, nil
	while true do
		local succ, err = pcall(function()
			while true do
				iKey, iVal = next(tbl, iKey)
				if iKey == nil then break end
				calls = calls + 1

				callback(results, iKey, iVal)
			end
		end)

		if not succ then errors[iKey] = err end
		if iKey == nil then break end
	end

	if table.Count(errors) == calls then
		if calls ~= 0 then
			local _, error = next(errors, nil)
			MsgC(Color(235, 111, 111), "[tinylua] "..error)
		else
			MsgC(Color(235, 111, 111), "[tinylua] No results!\n")
			return
		end
	end

	local result = Wrap(results)
	getmetatable(result)["errors"] = errors
	return result
end

function META:__index(index)
	if INTERNAL[index] then
		return function(_, ...)
			return INTERNAL[index](self, ...)
		end
	end

	return performCall(self, function(results, source, ent)
		local target = ent[index]

		if isfunction(target) then
			results[source] = function(fArg, ...)
				return target(ent, ...)
			end
		else
			results[source] = target
		end
	end)
end

function META:__newindex(index, value)
	performCall(self, function(results, source, ent)
		ent[index] = value
	end)
end

function META:__call(...)
	local args = pack(...)
	return performCall(self, function(results, source, ent)
		if isfunction(ent) then
			local rets = pack(ent(args:unpack()))
			if #rets ~= 1 then
				for _, ret in pairs(rets) do
					table.insert(results, ret)
				end
			else
				results[source] = rets[1]
			end
		end
	end)
end

-- Exposed Functions
tinylua.Wrap = Wrap

-- INTERNAL Extensions
local function makePrefix(input)
	if not input:match("\n") and isfunction(CompileString("return "..input, "", false)) then
		return "return "..input
	end

	return input
end

local function buildParser(input)
	if isfunction(input) then return input end
	local argStr, funcStr = input:match("(.-)->(.+)")

	if argStr and funcStr then
		local codeFull = string.format("return function(%s)\n%s\nend", argStr, makePrefix(funcStr))
		local funcFactory = CompileString(codeFull, "funcfactory")

		if getfenv(1) then
			setfenv(funcFactory, getfenv(1))
		end

		if funcFactory then
			return funcFactory()
		end
	end
end

function INTERNAL:map(input)
	local eval = buildParser(input)
	return performCall(self, function(results, source, ent)
		local rets = pack(eval(ent, source))
		if #rets ~= 1 then
			for _, val in pairs(rets) do
				table.insert(results, val)
			end
		else
			results[source] = rets[1]
		end
	end)
end

function INTERNAL:filter(input)
	local eval = buildParser(input)
	return performCall(self, function(results, source, ent)
		if eval(ent, source) then
			results[source] = ent
		end
	end)
end

function INTERNAL:set(vars, val)
	vars = (istable(vars) and vars or {vars})
	return performCall(self, function(results, source, ent)
		for _, var in ipairs(vars) do
			ent[var] = val
		end

		results[source] = ent
	end)
end

function INTERNAL:IsValid()
	return false
end

function INTERNAL:keys()
	return performCall(self, function(results, source, ent)
		results[source] = source
	end)
end

function INTERNAL:first()
	for _, ent in pairs(self) do
		return ent
	end
end

function INTERNAL:errors()
	return (getmetatable(self).errors or {})
end

function INTERNAL:get()
	return table.ClearKeys(self)
end
