--- Build strict file-reference rules for Apache configuration files
-- @module formats.htaccess.references

local common = require("core.common")

local htaccess_references = {}

--- Return the line prefix before one Apache directive argument
-- @param name string directive name
-- @return string prefix
local function directive_prefix(name)
	return "^%s*" .. name .. "%s+"
end

local directory_index_prefix = directive_prefix("DirectoryIndex")
local directory_index_line_pattern = common.caseless_pattern(directory_index_prefix)

local single_path_directives = {
	"Include",
	"IncludeOptional",
	"AuthUserFile",
	"AuthGroupFile",
	"HeaderName",
	"ReadmeName",
	"IndexStyleSheet",
	"DefaultIcon",
	"TypesConfig",
	"MagicFile",
	"SSLCertificateFile",
	"SSLCertificateKeyFile",
	"SSLCertificateChainFile",
	"SSLCACertificateFile",
	"SSLCARevocationFile",
	"SSLSessionTicketKeyFile",
	"FallbackResource",
}

--- Return whether one Apache argument is a literal local path
-- @param reference table parsed file reference
-- @return boolean accepted
local function is_local_path(reference)
	local path = reference.path

	return path ~= "disabled" and path ~= "-"
		and not path:find("$", 1, true)
		and not path:find("%{", 1, true)
		and not path:find("*", 1, true)
		and not path:find("?", 1, true)
		and not path:find("[", 1, true)
		and (path:match("^%a:[/\\]") ~= nil or path:match("^%a[%w+%.%-]*:") == nil)
end

--- Return whether one unquoted argument also reaches the line end or a comment
-- @param reference table parsed file reference
-- @param line_text string complete source line
-- @return boolean accepted
local function is_single_path(reference, line_text)
	return is_local_path(reference)
		and (line_text:sub(reference.col2):match("^%s*$") ~= nil
			or line_text:sub(reference.col2):match("^%s*#") ~= nil)
end

--- Return whether one DirectoryIndex argument belongs to that directive line
-- @param reference table parsed file reference
-- @param line_text string complete source line
-- @return boolean accepted
local function is_directory_index(reference, line_text)
	local comment_col = line_text:find("#", 1, true)

	return line_text:match(directory_index_line_pattern) ~= nil
		and (not comment_col or reference.col1 < comment_col)
		and is_local_path(reference)
end

--- Add quoted and plain variants for one direct path argument
-- @param rules table receiving rules
-- @param prefix string directive prefix
-- @param plain_validate function final check for the unquoted form
local function add_path_rules(rules, prefix, plain_validate)
	for _, quote in ipairs({ "\"", "'" }) do
		rules[#rules + 1] = {
			prefix = prefix,
			open = quote,
			close = quote,
			caseless = true,
			validate = is_local_path,
		}
	end
	rules[#rules + 1] = { prefix = prefix, caseless = true, validate = plain_validate }
end

--- Return direct local-file arguments from Apache directives
-- @return table rules
function htaccess_references.new()
	local rules = {}

	for _, name in ipairs(single_path_directives) do
		add_path_rules(rules, directive_prefix(name), is_single_path)
	end

	local error_prefix = directive_prefix("ErrorDocument") .. "%d+%s+"
	rules[#rules + 1] = { prefix = error_prefix, caseless = true, validate = is_single_path }

	add_path_rules(rules, directory_index_prefix, is_directory_index)
	for _, quote in ipairs({ "\"", "'" }) do
		rules[#rules + 1] = { prefix = "%s+", open = quote, close = quote, validate = is_directory_index }
	end
	rules[#rules + 1] = { prefix = "%s+", validate = is_directory_index }

	return rules
end

return htaccess_references
