-----------------------------------------------------------------------------
-- inconcertWebHandler WSAPI handler
--
-- Author: Ignacio Burgueño
-- Copyright (c) 2007 inConcert
--
-----------------------------------------------------------------------------

local common = require "wsapi.common"

local _M = {}

-------------------------------------------------------------------------------
-- Implements WSAPI
-------------------------------------------------------------------------------

local function set_cgivars (req, diskpath, path_info_pat, script_name_pat, extra_vars)
	diskpath = diskpath or req.diskpath or ""
	req.cgivars = {
		SERVER_SOFTWARE = req.serversoftware,
		SERVER_NAME = req.parsed_url.host,
		GATEWAY_INTERFACE = "CGI/1.1",
		SERVER_PROTOCOL = "HTTP/1.1",
		SERVER_PORT = req.parsed_url.port,
		REQUEST_METHOD = req.cmd_mth,
		DOCUMENT_ROOT = diskpath,
		PATH_INFO = string.match(req.parsed_url.path, path_info_pat) or "",
		PATH_TRANSLATED = script_name_pat and (diskpath .. script_name_pat),
		SCRIPT_NAME = script_name_pat,
		QUERY_STRING = req.parsed_url.query or "",
		REMOTE_ADDR = string.gsub (req.socket:getpeername (), ":%d*$", ""),
		CONTENT_TYPE = req.headers ["content-type"],
		CONTENT_LENGTH = req.headers ["content-length"],
	}
	if req.cgivars.PATH_INFO == "" then req.cgivars.PATH_INFO = "/" end
	for n,v in pairs (extra_vars or {}) do
		req.cgivars[n] = v
	end
	for n,v in pairs (req.headers) do
		req.cgivars ["HTTP_"..string.gsub (string.upper (n), "-", "_")] = v
	end
end

local function wsapihandler (req, res, wsapi_run, app_prefix, docroot, app_path, extra_vars)
	local path_info_pat = "^" .. (app_prefix or "") .. "(.*)"    -- para obtener PATH_INFO
	set_cgivars(req, docroot, path_info_pat, app_prefix, extra_vars)

	local get_cgi_var = function (var)
		return req.cgivars[var] or ""
	end

	local err = {
		write = function(self, ...)
			LogError(...)
		end
	}
	local wsapi_env = common.wsapi_env{
		input       = req.socket,
		read_method = "receive",
		error       = err,
		env         = get_cgi_var
	}
	wsapi_env.APP_PATH = app_path

	local function set_status(status)
		if type(status) == "number" or status:match("^%d+$") then
			status = status .. " " .. common.status_codes[tonumber(status)]
		end
		res.statusline = "HTTP/1.1 " .. (status or "500 Internal Server Error")
	end

	local function send_headers(headers)
		for h, v in pairs(headers) do
			if h == "Status" or h == "Content-Type" then
				res.headers[h] = v
			elseif type(v) == "string" then
				res:add_header(h, v)
			elseif type(v) == "table" then
				for _, v in ipairs(v) do
					res:add_header(h, tostring(v))
				end
			else
				res:add_header(h, tostring(v))
			end
		end
		--res:send_headers() esto apareció en xavante
	end

	local ok, status, headers, res_iter = common.run_app(wsapi_run, wsapi_env)
	if ok then
		if status == "reparse" then
			req.cmd_url = headers["Location"] or "/"
			if headers["x-inConcert-propagate-SID"] then
				req.headers["x-inConcert-propagate-SID"] = headers["x-inConcert-propagate-SID"]
				headers["x-inConcert-propagate-SID"] = nil
			end
			-- este manejo de cookies es medio aberrante. tendria que escribir un modulito wsapi para manipularlos
			local cookies = string.gsub(";" .. (headers["Set-Cookie"] or "") .. ";", "%s*;%s*", ";")
			local newCookies = ""
			for cookie_name, cookie_value in string.gmatch(cookies, ";(.-)=(.-);") do
				newCookies = cookie_name .. "=" .. cookie_value .. ";" .. newCookies
			end
			if newCookies ~= "" then
				req.headers["cookie"] = newCookies .. ";" .. (req.headers["cookie"] or "")
			end
			return status
		else
			set_status(status or 500)
			send_headers(headers or {})
			common.send_content(res, res_iter, "send_data")
		end
	else
		if wsapi_env.STATUS == 404 then
			res.statusline = "HTTP/1.1 404"
			send_headers({ ["Content-Type"] = "text/html", ["Content-Length"] = #status })
			res:send_data(status)
		else
			LogError("Error 500 when processing request '%s'\n%s", req.cmd_url, status)

			local mode = os.getenv("INCONCERT_ENV_MODE")
			mode = (mode and mode:upper()) or "PRODUCTION"
			local content
			if mode == "DEVELOPMENT" then
			 	content = common.error_html(status)
			else
				content = common.error_html("")
			end

			res.statusline = "HTTP/1.1 500"
			send_headers({ ["Content-Type"] = "text/html", ["Content-Length"] = #content })
			res:send_data(content)
		end
	end
end

-- Handler WSAPI para aplicaciones SAPI (en el que me jopeo Rings)
function _M.custom_sapi_loader(wsapi_env)
	common.normalize_paths(wsapi_env)

	_G.CGILUA_ISDIRECT = true
	
	local app = require "wsapi.sapi"
	local status, headers, iterator = app.run(wsapi_env)
	-- it is necessary to reload Cgilua packages and wsapi.sapi. it has way too much dependency on global
	-- environment
	package.loaded["cgilua"] = nil
	cgilua.SAPI = nil
	package.loaded["wsapi.sapi"] = nil
	
	return status, headers, iterator
end

-- Makes a WSAPI handler for a single WSAPI application
function _M.makeHandler (applicationFunction, applicationPrefix, documentRoot, applicationPath, extra_vars)
	return function (req, res)
		return wsapihandler(req, res, applicationFunction, applicationPrefix, documentRoot, applicationPath, extra_vars)
	end
end

-------------------------------------------------------------------------------
-- Returns a handler that does not force a login (for testing purposes)
-------------------------------------------------------------------------------
function _M.makeRawSapiHandler (diskpath)
	return function (req, res)
		return wsapihandler(req, res, _M.custom_sapi_loader, nil, diskpath)
	end
end

-------------------------------------------------------------------------------
-- Returns a handler launches the SAPI handler after passing the request
-- through a series of filters
-------------------------------------------------------------------------------
function _M.makeFilteredSapiHandler (diskpath, filters)
	local filteredApp = _M.custom_sapi_loader
	if filters then
		for _,filter in ipairs(filters) do
			if type(filter) == "table" then
				filteredApp = filter.filter(filteredApp, filter.params and unpack(filter.params))
			else
				filteredApp = filter(filteredApp)
			end
		end
	end
	return _M.makeHandler(filteredApp, nil, diskpath)
end

-------------------------------------------------------------------------------
-- Returns a handler that launches the a given application or function after
-- passing the request through a series of filters
-------------------------------------------------------------------------------
function _M.makeFilteredHandler (diskpath, appOrFunction, filters)
	local filteredApp = appOrFunction
	if filters then
		for _,filter in ipairs(filters) do
			if type(filter) == "table" then
				if type(filter.params) == "table" then
					filteredApp = filter.filter(filteredApp, unpack(filter.params))
				else
					filteredApp = filter.filter(filteredApp, unpack(filter))
				end
			else
				filteredApp = filter(filteredApp)
			end
		end
	end
	return _M.makeHandler(filteredApp, nil, diskpath)
end

-------------------------------------------------------------------------------
-- Returns the WSAPI handler for SAPI, with rings
-------------------------------------------------------------------------------
function _M.makeSapiHandler(docroot, reload)
	local sapi = require "wsapi.sapi"
	if reload then
		return function (req, res)
			return wsapihandler(req, res, sapi.run, nil, docroot)
		end
	else
		return function (req, res)
			return wsapihandler(req, res, sapi.run, nil, docroot)
		end
	end
end

-- Makes a generic WSAPI handler, that launches WSAPI application scripts
-- See the wsapi script for the possible values of the "params" table
function _M.makeGenericHandler(docroot, params, extra_vars)
	params = params or { isolated = true }
	return function (req, res)
		return wsapihandler(req, res, common.make_loader(params), nil, docroot, nil, extra_vars)
	end
end

-------------------------------------------------------------------------------
-- Returns a WSAPI filter that executes before constructing the response
-- @filter is a function that receives the wsapi environment. It must return
--   the modified environment.
-------------------------------------------------------------------------------
function _M.MakePreFilter(appToFilter, filter)
	if type(appToFilter) == "table" and type(appToFilter["run"]) == "function" then
		return function(wsapi_env)
			wsapi_env = filter(wsapi_env)
			return appToFilter.run(wsapi_env)
		end
	else
		return function(wsapi_env)
			wsapi_env = filter(wsapi_env)
			return appToFilter(wsapi_env)
		end
	end
end

-------------------------------------------------------------------------------
-- Returns a WSAPI filter that executes after the response is ready
-- @filter is a function that receives the status, the headers and the iterator.
--   It must return the modified status, headers and iterator.
-------------------------------------------------------------------------------
function _M.MakePostFilter(appToFilter, filter)
	if type(appToFilter) == "table" and type(appToFilter["run"]) == "function" then
		return function(wsapi_env)
			local status, headers, iterator = appToFilter.run(wsapi_env)
			return filter(status, headers, iterator)
		end
	else
		return function(wsapi_env)
			local status, headers, iterator = appToFilter(wsapi_env)
			return filter(status, headers, iterator)
		end
	end
end

return _M
