--[[ Minimal JSON decoder for detect.py result files. ]]

local Json = {}

local decodeValue

local function skipWhitespace(s, i)
	local j = s:find('[^ \t\r\n]', i)
	return j or (#s + 1)
end

local ESCAPES = {
	['"'] = '"', ['\\'] = '\\', ['/'] = '/',
	b = '\b', f = '\f', n = '\n', r = '\r', t = '\t',
}

local function decodeString(s, i)
	local out = {}
	local j = i + 1
	while true do
		local c = s:sub(j, j)
		if c == '' then
			error('unterminated string at ' .. i)
		elseif c == '"' then
			return table.concat(out), j + 1
		elseif c == '\\' then
			local e = s:sub(j + 1, j + 1)
			if ESCAPES[e] then
				out[#out + 1] = ESCAPES[e]
				j = j + 2
			elseif e == 'u' then
				-- non-ASCII never appears in detect.py output; keep the low byte
				out[#out + 1] = string.char(tonumber(s:sub(j + 2, j + 5), 16) % 256)
				j = j + 6
			else
				error('bad escape at ' .. j)
			end
		else
			out[#out + 1] = c
			j = j + 1
		end
	end
end

local function decodeNumber(s, i)
	local numStr = s:match('^-?%d+%.?%d*[eE]?[%+%-]?%d*', i)
	local n = tonumber(numStr)
	if n == nil then
		error('bad number at ' .. i)
	end
	return n, i + #numStr
end

local function decodeArray(s, i)
	local arr = {}
	i = skipWhitespace(s, i + 1)
	if s:sub(i, i) == ']' then
		return arr, i + 1
	end
	while true do
		local v
		v, i = decodeValue(s, i)
		arr[#arr + 1] = v
		i = skipWhitespace(s, i)
		local c = s:sub(i, i)
		if c == ']' then
			return arr, i + 1
		elseif c ~= ',' then
			error('expected , or ] at ' .. i)
		end
		i = skipWhitespace(s, i + 1)
	end
end

local function decodeObject(s, i)
	local obj = {}
	i = skipWhitespace(s, i + 1)
	if s:sub(i, i) == '}' then
		return obj, i + 1
	end
	while true do
		if s:sub(i, i) ~= '"' then
			error('expected key at ' .. i)
		end
		local key
		key, i = decodeString(s, i)
		i = skipWhitespace(s, i)
		if s:sub(i, i) ~= ':' then
			error('expected : at ' .. i)
		end
		i = skipWhitespace(s, i + 1)
		local v
		v, i = decodeValue(s, i)
		obj[key] = v
		i = skipWhitespace(s, i)
		local c = s:sub(i, i)
		if c == '}' then
			return obj, i + 1
		elseif c ~= ',' then
			error('expected , or } at ' .. i)
		end
		i = skipWhitespace(s, i + 1)
	end
end

decodeValue = function(s, i)
	local c = s:sub(i, i)
	if c == '{' then
		return decodeObject(s, i)
	elseif c == '[' then
		return decodeArray(s, i)
	elseif c == '"' then
		return decodeString(s, i)
	elseif s:sub(i, i + 3) == 'true' then
		return true, i + 4
	elseif s:sub(i, i + 4) == 'false' then
		return false, i + 5
	elseif s:sub(i, i + 3) == 'null' then
		return nil, i + 4
	else
		return decodeNumber(s, i)
	end
end

function Json.decode(s)
	local ok, result = pcall(function()
		local v = decodeValue(s, skipWhitespace(s, 1))
		return v
	end)
	if ok then
		return result
	end
	return nil, tostring(result)
end

return Json
