local decoder = require("luacheck.decoder")
local utils = require("luacheck.utils")
local core_utils = {}

-- Attempts to evaluate a node as a Lua value, without resolving locals.
-- Returns Lua value and its string representation on success, nothing on failure.
function core_utils.eval_const_node(node)
	if node.tag == "True" then
		return true, "true"
	elseif node.tag == "False" then
		return false, "false"
	elseif node.tag == "String" then
		local chars = decoder.decode(node[1])
		return node[1], chars:get_printable_substring(1, chars:get_length())
	else
		local is_negative

		if node.tag == "Op" and node[1] == "unm" then
			is_negative = true
			node = node[2]
		end

		if node.tag ~= "Number" then return end

		local str = node[1]

		if str:find("[iIuUlL]") then -- Ignore LuaJIT cdata literals.
		return end

		-- On Lua 5.3 convert to float to get same results as on Lua 5.1 and 5.2.
		if _VERSION == "Lua 5.3" and not str:find("[%.eEpP]") then
			str = str .. ".0"
		end

		local number = tonumber(str)

		if not number then return end

		if is_negative then number = -number end

		if number == number and number < 1 / 0 and number > -1 / 0 then
			return number, (is_negative and "-" or "") .. node[1]
		end
	end
end

local statement_containing_tags = utils.array_to_set({"Do", "While", "Repeat", "Fornum", "Forin", "If"})

-- `items` is an array of nodes or nested item arrays.
local function scan_for_statements(chstate, items, tags, callback, ...)
	for _, item in ipairs(items) do
		if tags[item.tag] then callback(chstate, item, ...) end

		if not item.tag or statement_containing_tags[item.tag] then
			scan_for_statements(chstate, item, tags, callback, ...)
		end
	end
end

-- Calls `callback(chstate, node, ...)` for each statement node within AST with tag in given array.
function core_utils.each_statement(chstate, tags_array, callback, ...)
	local tags = utils.array_to_set(tags_array)

	for _, line in ipairs(chstate.lines) do
		scan_for_statements(chstate, line.node[2], tags, callback, ...)
	end
end

local function location_comparator(warning1, warning2)
	if warning1.line ~= warning2.line then
		return warning1.line < warning2.line
	elseif warning1.column ~= warning2.column then
		return warning1.column < warning2.column
	else
		return warning1.code < warning2.code
	end
end

-- Sorts an array of warnings by location information as provided in `line` and `column` fields.
function core_utils.sort_by_location(warnings)
	table.sort(warnings, location_comparator)
end

return core_utils