local cosmo = require"cosmo"
local sanitizer = require "sanitize"
local inconcert = inconcert	-- capture the global table 'inconcert'

local scriptHandler = require "webrender.scriptHandler"
local stylesHandler = require "webrender.stylesheetHandler"
local cosmoPage = require "webrender.cosmoPage"
local cgiluaPage = require "webrender.cgiluaPage"
local LogWarning = LogWarning
local table, setmetatable, require, string, next = table, setmetatable, require, string, next
local type, assert, pairs, ipairs = type, assert, pairs, ipairs
local rawset = rawset

local dashboard = nil

local wsapi_response = require "wsapi.response"
local wsapi_request = require "wsapi.request"

--local __module_location = select(2, ...)
--assert(__module_location, "should be running under inconcertWebHandler")
local m_appLocation = debug.getinfo( 1, 'S' ).source
m_appLocation = assert(m_appLocation:match("@(.+)"), "Invalid current location")

module((...))

local function initialize(render, web, env, how)
	render.web = web
	--render.env = env or {}
	assert(not how or type(how) == "table", "'how' must be nil or a table")
	
	local function env_index(tab, name)
		local var = (env and env[name])
		rawset(tab, name, var)
		return var
	end
	-- we want a new table each time because we're polluting env
	render.env = setmetatable({}, { __index = env_index })
	
	if not how or how.fullpage then
		local scripts, stylesheets = {}, {}
		-- los scripts y las stylesheets no las renderizo directamente, sino que las voy juntando
		render.extension.Scripts = scriptHandler.MakeCollector(scripts)
		render.extension.Stylesheets = stylesHandler.MakeCollector(stylesheets)
	
		if render.m_commonScripts then
			render.extension.Scripts.Add(render.m_commonScripts)
		end
		if render.m_commonStylesheets then
			render.extension.Stylesheets.Add(render.m_commonStylesheets)
		end
	
		-- un widget es algo que necesita scripts y stylesheets
		render.extension.HaveWidget = function(widget)
			render.extension.Scripts.Add(widget.GetScripts())
			render.extension.Stylesheets.Add(widget.GetStylesheets())
		end
	else
		if how.snippet then
			-- un collector que loguee un error avisando que estan tratando de agregar un script o una hoja de estilos
			render.extension.Scripts = scriptHandler.MakeForbiddenCollector()
			render.extension.Stylesheets = stylesHandler.MakeForbiddenCollector()
		end
	end
end

local function finish(render, content)
	if content then
		local content_type = render.web.headers["Content-Type"]
		local stylesheets = render.extension.Stylesheets.Collect() or {}
		local scripts = render.extension.Scripts.Collect() or {}
		
		if #stylesheets ~= 0 or #scripts ~= 0 then
			-- muy tosco, por ahora, pero cuelgo los stylesheets y los scripts antes de cerrar el tag head (más vale que haya uno!)
			-- puede no haber uno, en caso de ScriptLp, ScriptCosmo, StylesheetLp y StylesheetCosmo, pero ahí no inserto Scripts
			local substitutions
			content, substitutions = string.gsub(content, "</head>", function()
				return table.concat(stylesheets, "\r\n") .. "\r\n" .. table.concat(scripts, "\r\n") .. "</head>"
			end)
			if substitutions == 0 then
				LogWarning("No substitutions were performed for script '%s'\r\nMake sure there is a <head> node", render.web.script_name .. render.web.path_info)
			end
		end
	end
	
	content = content or ""
	
	--- Hook que se llama cuando el contenido está pronto y antes de enviarlo
	--- Me permite modificarlo
	if type(render.onContentReady) == "function" then
		content = render.onContentReady(render, content)
	end

	-- limpiamos todo
	render.web = nil
	render.env = nil
	
	return content
end

--
-- helper functions for initialize_xxx_escaping
local escaping_helpers = {
	lp = function(...)
		return inconcert.translator:Text(...)
	end,
	cosmo = function(message, ...)	-- message es usualmente un array de strings en el caso de Cosmo
		if type(message) == "table" then
			local acc = {}
			for _,text in ipairs(message) do
				acc[#acc + 1] = inconcert.translator:Text(text, ...)
			end
			return table.concat(acc, " ")
		end
		return inconcert.translator:Text(message, ...)
	end
}
-- end helpers

--
local function initialize_html_escaping(render, template_engine)
	assert(type(render) == "table")
	
	local env = render.env
	env.ToJs = function(...)
		return sanitizer.ToJs(...)
	end
	env.ToHtml = function(...)
		return sanitizer.ToHtml(...)
	end
	
	if type(inconcert) == "table" and inconcert.translator then
		local _T = escaping_helpers[template_engine]
		if not _T then
			error("template engine must be either 'lp' or 'cosmo'")
		end
		
		env._T = function(...)
			return env.ToHtml(_T(...))
		end
		env._T_raw = function(...)
			return _T
		end
		env._T_js = function(...)
			return env.ToJs(_T(...))
		end
	end
end

local function initialize_js_escaping(render, template_engine)
	assert(type(render) == "table")
	
	local env = render.env
	env.ToJs = function(...)
		return sanitizer.ToJs(...)
	end
	env.ToHtml = function(...)
		return sanitizer.ToHtml(...)
	end
	
	if type(inconcert) == "table" and inconcert.translator then
		local _T = escaping_helpers[template_engine]
		if not _T then
			error("template engine must be either 'lp' or 'cosmo'")
		end
		
		env._T = function(...)
			return env.ToJs(_T(...))
		end
		env._T_raw = function(...)
			return _T
		end
		env._T_html = function(...)
			return env.ToHtml(_T(...))
		end
	end
end

--[=[
Renderiza una página Cosmo.
Lo que pertenece a self es accesible directamente en las páginas
]=]
function RenderPage(self, url, web, env, how)
	initialize(self, web, env, how)
	initialize_html_escaping(self, "cosmo")
	
	return finish( self, cosmoPage.Fill(self, url, env) )
end

--[=[
Renderiza una página CgiLua (LP).
Lo que pertenece a self es accesible directamente en las páginas
]=]
function RenderPageLP(self, url, web, env, how)
	initialize(self, web, env, how)
	initialize_html_escaping(self, "lp")
	
	local filename
	if url:sub(1, 1) == "/" then
		filename = web.doc_root .. url
	else
		filename = web.real_path .. "/" .. url
	end
	return finish( self, cgiluaPage.Fill(self, filename, env) )
end

--[=[
Como el Render anterior, pero este usa por defecto la masterpage
]=]
--[[
function Render(self, web, env)
	local env = env or {}
	if not dashboard then
		dashboard = require "apps.dashboard"
	end
	env.Dashboard = function()
		return dashboard:Render(self, web, env)
	end
	return RenderPage(self, "/inconcert/shared/render/pages/masterpage.html", web, env)
end
--]]

--[=[
Esto es para renderizar javascripts. Esta función es candidata a cambiar en un futuro, cuando se haga algo más inteligente 
para manipular los javascripts a que vuele o pase a su propio módulo.
Ojo, tienen mucho código repetido. Son candidatas a un buen refactoreo.
]=]
function ScriptLp(self, url, web, env)
	web.headers["Content-Type"] = "text/javascript"
	
	initialize(self, web, env, { snippet = true })
	initialize_js_escaping(self, "lp")
	
	local filename
	if url:sub(1, 1) == "/" then
		filename = web.doc_root .. url
	else
		filename = web.real_path .. "/" .. url
	end
	return finish( self, cgiluaPage.Fill(self, filename, env) )
end

function ScriptCosmo(self, url, web, env)
	web.headers["Content-Type"] = "text/javascript"
	
	initialize(self, web, env, { snippet = true })
	initialize_js_escaping(self, "cosmo")
	
	return finish( self, cosmoPage.Fill(self, url, env) )
end

--[=[
Idem a la función ScriptLp y ScriptCosmo
]=]
function StylesheetLp(self, url, web, env)
	web.headers["Content-Type"] = "text/css"
	initialize(self, web, env, { snippet = true })
	
	local filename
	if url:sub(1, 1) == "/" then
		filename = web.doc_root .. url
	else
		filename = web.real_path .. "/" .. url
	end
	return finish( self, cgiluaPage.Fill(self, filename, env) )
end

function StylesheetCosmo(self, url, web, env)
	web.headers["Content-Type"] = "text/css"
	initialize(self, web, env, { snippet = true })
	
	return finish( self, cosmoPage.Fill(self, url, env) )
end

--[=[
Mapea un objeto a ser renderizado. Estos objetos tienen que tener una función llamada "Render" de la forma:
	Render(self, render, web, environment)  donde:
		- render, es el renderer usado actualmente
		- web es la tabla web de wsapi correspondiente al request actual
		- environment, la tabla donde se propaga el entorno que usa la función para buscar datos extras
		
	y una función "GetStyles" de la forma:
	GetStyles(self, web, environment) donde los parámetros son análogos a la función Render
]=]
function Plug(render, name, object)
	assert(object)
	if next(object) then
		render.extension[name] = {
			Render = function()
				assert(object.Render)
				return object:Render(render, render.web, render.environment)
			end,
			GetStyles = function()
				assert(object.GetStyles)
				return object:GetStyles(render.web, render.environment)
			end
		}
	else
		LogWarning("Render.Plug: Plugging empty object!")
		render.extension[name] = {
			Render = function() return "" end,
			GetStyles = function() return  "" end
		}
	end
end

--[=[
Adds a script that is included in everything the render outputs.
--]=]
function AddCommonScript(self, script)
	self.m_commonScripts = self.m_commonScripts or {}
	table.insert(self.m_commonScripts, script)
end

--[=[
Adds a stylesheet that is included in everything the render outputs.
--]=]
function AddCommonStylesheet(self, stylesheet)
	self.m_commonStylesheets = self.m_commonStylesheets or {}
	table.insert(self.m_commonStylesheets, stylesheet)
end

--[=[
Sets an extension to the render, with a given name.
]=]
function Extend(self, name, extension)
	self.extension[name] = extension
end

--[=[
IncludeLP must be called within an invocation to Render, RenderPage or RenderPageLP
]=]
function IncludeLP(self, url, env)
	assert(self.web)
	initialize_html_escaping(self, "lp")
	return cgiluaPage.Fill(self, url, env)
	--[=[
	else
		self.web = {}
		local result = cgiluaPage.Fill(self, url, env)
		self.web = nil
		return result
	end
	]=]
end

function IncludeCosmo(self, url, env)
	assert(self.web)
	initialize_html_escaping(self, "cosmo")
	return cosmoPage.Fill(self, url, env)
--[=[
	if self.web then
		return cosmoPage.Fill(self, url, env)
	else
		self.web = {}
		local result = cosmoPage.Fill(self, url, env)
		self.web = nil
		return result
	end
	]=]
end

local moduleMT = {
	__index = _M
}


function new(applicationName, parameters)
	local newInstance = { extension = {} }
	
	setmetatable(newInstance, moduleMT)
	
	if type(parameters) == "table" then
		for k,v in pairs(parameters) do
			newInstance.extension[k] = v
		end
	end
	return newInstance
end


--
-- filter for WSAPI. Emulates what Orbit is doing because the render is tied to Orbit's 'web' 
function makeWsapiCompatible(applicationName, renderEngine, env)
	local render = new(applicationName)
	if type(renderEngine) == "string" then
		renderEngine = _M[renderEngine]
		assert(type(renderEngine) == "function", "'render engine not found'")
	end
	return function(wsapi_env)
		local newenv = setmetatable({}, { __index = env })	-- don't pollute env
		local web = {
			status = "200 Ok",
			response = "",
			headers = { ["Content-Type"]= "text/html" }, 
			cookies = {},
			real_path = wsapi_env.APP_PATH,
			doc_root = wsapi_env.DOCUMENT_ROOT,
			path_translated = wsapi_env.PATH_TRANSLATED,
			prefix = wsapi_env.SCRIPT_NAME,
			script_name = wsapi_env.SCRIPT_NAME,
			vars = wsapi_env
		}
		if web.path_translated == "" then
			web.path_translated = wsapi_env.SCRIPT_FILENAME
		end
		
		local req = wsapi_request.new(wsapi_env)
		local res = wsapi_response.new(web.status, web.headers)
		
		web.path_info = req.path_info
		web.method = string.lower(req.method)
		web.input, web.cookies = req.params, req.cookies
		web.GET, web.POST = req.GET, req.POST
		
		local content = renderEngine(render, wsapi_env.PATH_INFO, web, newenv)
		res:write(content)
		return res:finish()
	end
end