-- vim: ft=terra
-- string template generator:
-- returns a function that fills out a template
-- with the strings given

local util = lib.util
local pstr = lib.mem.ptr(int8)
local m = {}
function m.mk(tplspec)
	local str
	if type(tplspec) == 'string'
		then str = tplspec tplspec = {}
		else str = tplspec.body
	end
	local tplchar_o = tplspec.sigil or '@'
	local tplchar = tplchar_o
	local magic = {
		['%'] = true, ['$'] = true, ['^'] = true;
		['.'] = true, ['*'] = true, ['+'] = true;
		['-'] = true, ['?'] = true;
	}
	tplchar_o = string.gsub(tplchar_o,'%%','%%%%')
	tplchar = string.gsub(tplchar, '.', function(c)
		if magic[c] then return '%' .. c end
	end)
	local last = 1
	local fields = {}
	local segs = {}
	local docs = {}
	local constlen = 0
	-- strip out all irrelevant whitespace to tidy things up
	-- TODO: find way to exclude <pre> tags?
	str = str:gsub('[\n^]%s+','')
	str = str:gsub('%s+[\n$]','')
	str = str:gsub('\n','')
	str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed
	str = str:gsub(tplchar .. '%?([-%w]+)', function(file)
		if not docs[file] then docs[file] = data.doc[file] end
		return string.format('<a href="#help-%s" class="help">?</a>', file)
	end)
	for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)(%w+)()') do
		if string.sub(str,start-1,start-1) ~= '\\' then
			segs[#segs+1] = string.sub(str,last,start-1)
			fields[#segs] = { key = key, mode = (mode ~= '' and mode or nil) }
			last = stop
		end
	end
	segs[#segs+1] = string.sub(str,last)

	for i, s in ipairs(segs) do
		segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)
		constlen = constlen + string.len(segs[i])
	end

	for n,d in pairs(docs) do
		local html = string.format(
			'<div id="help-%s" class="modal"> <a href="#0">close</a> <div>%s</div></div>', n, d.text
		)
		segs[#segs] = segs[#segs] .. html
		constlen = constlen + #html
	end
	

	local runningtally = symbol(intptr)
	local tallyup = {quote
		var [runningtally] = 1 + constlen
	end}
	local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
	local symself = symbol(&rec)
	do local kfac = {}
		local sanmode = {}
		for afterseg,fld in ipairs(fields) do
			if not kfac[fld.key] then
				rec.entries[#rec.entries + 1] = {
					field = fld.key;
					type = lib.mem.ptr(int8);
				}
			end
			kfac[fld.key] = (kfac[fld.key] or 0) + 1
			sanmode[fld.key] = fld.mode == ':' and 6 or fld.mode == '!' and 5 or 1
		end
		for key, fac in pairs(kfac) do
			local sanfac = sanmode[key]
			
			tallyup[#tallyup + 1] = quote
				[runningtally] = [runningtally] + ([symself].[key].ct)*fac*sanfac
			end
		end
	end

	local copiers = {}
	local senders = {}
	local appenders = {}
	local symtxt = symbol(lib.mem.ptr(int8))
	local cpypos = symbol(&opaque)
	local accumulator = symbol(&lib.str.acc)
	local destcon = symbol(&lib.net.mg_connection)
	for idx, seg in ipairs(segs) do
		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
		appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end
		if fields[idx] and fields[idx].mode then
			local f = fields[idx]
			local fp = `symself.[f.key]
			copiers[#copiers+1] = quote 
				if fp.ct > 0 then
					var san = lib.html.sanitize(fp, [f.mode == ':'])
					[cpypos] = lib.mem.cpy([cpypos], [&opaque](san.ptr), san.ct)
					--san:free()
				end
			end
			senders[#senders+1] = quote
				if fp.ct > 0 then
					var san = lib.html.sanitize(fp, [f.mode == ':'])
					lib.net.mg_send([destcon], san.ptr, san.ct)
					--san:free()
				end
			end
			appenders[#appenders+1] = quote
				if fp.ct > 0 then
					var san = lib.html.sanitize(fp, [f.mode == ':'])
					[accumulator]:ppush(san)
					--san:free()
				end
			end
		elseif fields[idx] then
			local f = fields[idx]
			local fp = `symself.[f.key]
			copiers[#copiers+1] = quote 
				if fp.ct > 0 then
					[cpypos] = lib.mem.cpy([cpypos], [&opaque](fp.ptr), fp.ct)
				end
			end
			senders[#senders+1] = quote
				if fp.ct > 0 then
					lib.net.mg_send([destcon], fp.ptr, fp.ct)
				end
			end
			appenders[#appenders+1] = quote
				if fp.ct > 0 then [accumulator]:ppush(fp) end
			end
		end
	end

	local tid = tplspec.id or '<anonymous>'
	rec.methods.tostr = terra([symself])
		lib.dbg(['compiling template ' .. tid])
		[tallyup]
		var [symtxt] = lib.mem.heapa(int8, [runningtally])
		var [cpypos] = [&opaque](symtxt.ptr)
		[copiers]
		@[&int8](cpypos) = 0
		symtxt.ct = [&int8](cpypos) - symtxt.ptr
		return symtxt
	end
	rec.methods.append = terra([symself], [accumulator])
		lib.dbg(['appending template ' .. tid])
		[tallyup]
		accumulator:cue([runningtally])
		[appenders]
		return accumulator
	end
	rec.methods.head = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
		lib.dbg(['transmitting template headers ' .. tid])
		[tallyup]
		lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code))
		for i = 0, hd.ct do
			lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value)
		end
		lib.net.mg_printf([destcon],'Content-Length: %llu\r\n\r\n', [runningtally] + 1)
	end
	rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
		lib.dbg(['transmitting template ' .. tid])

		symself:head(destcon,code,hd)

		[senders]
		lib.net.mg_send([destcon],'\r\n',2)
	end
	rec.methods.sz = terra([symself])
		lib.dbg(['tallying template ' .. tid])
		[tallyup]
		return [runningtally] + 1
	end

	return rec
end

return m
