-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
-- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.

local glob = require 'posix.glob'
local json = require "jsonc"
local tpl = require "gluon.web.template"
local util = require "gluon.web.util"
local proto = require "gluon.web.http.protocol"


local function build_url(http, path)
	return (http:getenv("SCRIPT_NAME") or "") .. "/" .. table.concat(path, "/")
end


local function set_language(renderer, accept)
	local langs = {}
	local weights = {}
	local star = 0

	local function add(lang, q)
		if not weights[lang] then
			table.insert(langs, lang)
			weights[lang] = q
		end
	end

	for match in accept:gmatch("[^,]+") do
		local lang = match:match('^%s*([^%s;_-]+)')
		local q = tonumber(match:match(';q=(%S+)%s*$') or 1)

		if lang == '*' then
			star = q
		elseif lang and q > 0 then
			add(lang, q)
		end
	end

	add('en', star)

	table.sort(langs, function(a, b)
		return (weights[a] or 0) > (weights[b] or 0)
	end)

	renderer.set_language(langs)
end

local function dispatch(config, http, request)
	local tree = {nodes={}}
	local nodes = {[''] = tree}

	local function _node(path, create)
		local name = table.concat(path, ".")
		local c = nodes[name]

		if not c and create then
			local last = table.remove(path)
			local parent = _node(path, true)

			c = {nodes={}}
			parent.nodes[last] = c
			nodes[name] = c
		end
		return c
	end

	-- Init template engine
	local function attr(key, val)
		if not val then
			return ''
		end

		if type(val) == "table" then
			val = json.stringify(val)
		end

		return string.format(' %s="%s"', key, util.pcdata(tostring(val)))
	end

	local renderer = tpl(config, setmetatable({
		http        = http,
		request     = request,
		node        = function(path) return _node({path}) end,
		write       = function(...) return http:write(...) end,
		pcdata      = util.pcdata,
		urlencode   = proto.urlencode,
		attr        = attr,
		json        = json.stringify,
		url         = function(path) return build_url(http, path) end,
	}, { __index = _G }))


	local function createtree()
		local base = config.base_path .. "/controller/"

		local function load_ctl(path)
			local ctl = assert(loadfile(path))

			local _pkg

			local subdisp = setmetatable({
				package = function(name)
					_pkg = name
				end,

				node = function(...)
					return _node({...})
				end,

				entry = function(entry_path, target, title, order)
					local c = _node(entry_path, true)

					c.target = target
					c.title  = title
					c.order  = order
					c.pkg    = _pkg

					return c
				end,

				alias = function(...)
					local req = {...}
					return function()
						http:redirect(build_url(http, req))
					end
				end,

				call = function(func, ...)
					local args = {...}
					return function()
						func(http, renderer, unpack(args))
					end
				end,

				template = function(view, scope)
					local pkg = _pkg
					return function()
						renderer.render_layout(view, scope, pkg)
					end
				end,

				model = function(name)
					local pkg = _pkg
					return function()
						require('gluon.web.model')(config, http, renderer, name, pkg)
					end
				end,

				_ = function(text)
					return text
				end,
			}, { __index = _G })

			local env = setmetatable({}, { __index = subdisp })
			setfenv(ctl, env)

			ctl()
		end

		for _, path in ipairs(glob.glob(base .. "*.lua", 0) or {}) do
			load_ctl(path)
		end
		for _, path in ipairs(glob.glob(base .. "*/*.lua", 0) or {}) do
			load_ctl(path)
		end
	end

	set_language(renderer, http:getenv("HTTP_ACCEPT_LANGUAGE") or "")

	createtree()


	local node = _node(request)

	if not node or not node.target then
		http:status(404, "Not Found")
		renderer.render_layout("error/404", {
			message =
				"No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
				"If this URL belongs to an extension, make sure it is properly installed.\n",
		}, 'gluon-web')
		return
	end

	local ok, err = pcall(http.parse_input, http, node.filehandler)
	if not ok then
		http:status(400, "Bad request")
		http:prepare_content("text/plain")
		http:write(err .. "\r\n")
		return
	end

	ok, err = pcall(node.target)
	if not ok then
		http:status(500, "Internal Server Error")
		renderer.render_layout("error/500", {
			message =
				"Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
				"The called action terminated with an exception:\n" .. tostring(err or "(unknown)"),
		}, 'gluon-web')
	end
end

return function(config, http)
	local request = {}
	local pathinfo = proto.urldecode(http:getenv("PATH_INFO") or "", true)
	for node in pathinfo:gmatch("[^/]+") do
		table.insert(request, node)
	end

	local ok, err = pcall(dispatch, config, http, request)
	if not ok then
		http:status(500, "Internal Server Error")
		http:prepare_content("text/plain")
		http:write(err .. "\r\n")
	end
end
