-- lx format

--- Apple property list dispatcher + OpenStep syntax
-- @module format.plist

local format = require("core.format")

--- Find first non-whitespace byte after optional UTF-8 BOM
-- @param data string opening file bytes
-- @return number position
local function find_text_start(data)
	local position = data:sub(1, 3) == "\239\187\191" and 4 or 1

	return data:find("[^%s]", position) or (#data + 1)
end

--- Remove leading XML comments before checking document marker
-- @param data string text beginning at first visible byte
-- @return string opening remaining opening text
local function skip_xml_comments(data)
	local opening = data

	while opening:sub(1, 4) == "<!--" do
		local close_position = opening:find("-->", 5, true)

		if not close_position then
			return opening
		end

		local next_position = find_text_start(opening:sub(close_position + 3))
		opening = opening:sub(close_position + 2 + next_position)
	end

	return opening
end

--- Detect content type stored inside a single property list file
-- @param ctx table detection input
-- @return boolean|table result detector decision
local function detect_plist(ctx)
	local data = tostring(ctx.data or "")

	-- Binary .plist
	if data:sub(1, 8) == "bplist00" then
		-- Handle like any other generic binary file
		return {
			action = "redirect",
			format = "binary"
		}
	end

	local position = find_text_start(data)
	local opening = skip_xml_comments(data:sub(position))

	-- Let's try XML .plist first
	if opening:sub(1, 5) == "<?xml"
		or opening:sub(1, 6) == "<plist"
		or opening:sub(1, 15) == "<!DOCTYPE plist" then

		-- We reflecc, we redirecc
		return {
			action = "redirect",
			format = "module:xml"
		}
	end

	-- Other text falls back to OpenStep property list format
	-- which kind of looks like weird JSON
	return true
end

format.register({
	name = "Property List (OpenStep)",
	shortname = "PLIST",
	author = "liqube",
	extensions = { "plist" },
	detect = detect_plist,
	comment = "//",

	patterns = {
		-- OpenStep property lists allow both line and block comments
		{ pattern = "//.-\n", type = "comment" },
		{ pattern = { "/%*", "%*/" }, type = "comment" },

		-- Quoted values may use either quote style
		{ pattern = { '"', '"', "\\" }, type = "string" },
		{ pattern = { "'", "'", "\\" }, type = "string" },

		-- Data values are hexadecimal bytes in angled brackets
		{ pattern = { "<", ">" }, type = "hex" },
		{ pattern = "-?%d+[%d%.]*", type = "number" },

		-- Bare names before '=' are dictionary keys
		{ pattern = "[%a_][%w_.-]*%s*%f[=]", type = "keyword" },
		{ pattern = "[%a_][%w_.-]*", type = "symbol" },
		{ pattern = "[{}%(%)=;,]", type = "operator" },
	},

	block_comment = {
		block_open = "/*",
		block_close = "*/",
		block_line_prefix = " * ",
		close_line_prefix = " ",
		open_on_own_line = false,
		close_on_own_line = true,
		strip_line_prefix = "^%s*%*%s?",
		allow_nested = false,
	},

	symbols = {
		["YES"] = "literal",
		["NO"] = "literal",
		["true"] = "literal",
		["false"] = "literal",
		["nil"] = "literal",
		["null"] = "literal",
	},
})
