meta = {
	domain = "compudanzas.net",
	title = "compudanzas",
	description = "explorations of computing at a human scale",
	fecha = os.date("%Y-%m-%d")
}

webdir = "web" -- web output directory
gemdir = "gem" -- gemini output directory
templates = {
	webEn = { 
		header = { path = "pageheader.html", template = ""},
		footer = { path = "pagefooter.html", template = ""},
		incoming = { path = "pageincoming.html", template = ""}
	},
	webEs = { 
		header = { path = "pageheader.html", template = ""},
		footer = { path = "pagefooter_es.html", template = ""},
		incoming = { path = "pageincoming_es.html", template = ""}
	},
	gemEn = { 
		header = { path = "pageheader.gmi", template = ""},
		footer = { path = "pagefooter.gmi", template = ""},
		incoming = { path = "pageincoming.gmi", template = ""}
	},
	gemEs = { 
		header = { path = "pageheader.gmi", template = ""},
		footer = { path = "pagefooter_es.gmi", template = ""},
		incoming = { path = "pageincoming_es.gmi", template = ""}
	},
}

pages = {}

function slugify( s )
	return (string.gsub( s, "%s", "_" ))
end

function spacify( s ) -- opposite of slugify (?)
	return (string.gsub( s, "_", " "))
end

function fillTemplate( template, pagemeta )
	return ( string.gsub( template, "({.-})", function (key)
			local var = string.sub(key,2,-2) -- remove { }
			return meta[var] or pagemeta[var]
		end) )
end

function getHeader( line ) -- returns title, level
	local head, title = string.match(line,"^(#+)%s*(.-)$")
	return title, #head
end

function getLink( line ) -- returns dest, text
	return string.match( line, "^=>%s*(%S-)%s+(.-)$")
end

function isImagePath( text )
	return text:match(".jpg$") or text:match(".gif$") or text:match(".png$")
end

function sanitize( line ) -- replace relevant html entities
	return line:gsub("<","&lt;"):gsub(">","&gt;")
end

function closeTags()
	local close = ""
	if flags.list then
		close = "</ul>\n"
		flags.list = false
	elseif flags.p  then
		close = "</p>\n"
		flags.p = false
	elseif flags.gallery then
		close = "</gallery>\n"
		flags.gallery = false
	end

	return close
end

function insertIncoming( pagename, incomingname )
	if incomingname == "pages" then return false end

	local incoming = pages[pagename].incoming
	for i = 1, #incoming do
		if incoming[i] == incomingname then
			return false
		end
	end

	table.insert( incoming, incomingname)
	return true
end

function firstPass()
	-- load templates
	for _,t in pairs(templates) do
		-- read templates
		for k,subtemp in pairs( t ) do
			local f = assert(io.open(string.format("templates/%s",subtemp.path),"r"))
			subtemp.template = f:read("a")
			f:close()
		end
	end

	-- for each page:
	-- convert gmo to gmi and html
	-- and calculate incoming links
	for name,page in pairs(pages) do
		local pagemeta = pages[name]
		
		local gmopath = string.format("src/%s.gmo", pagemeta.slug)

		-- open file
		local f = assert( io.open(gmopath, "r") )

		-- initialize flags
		flags = { list = false, pre = false , p = false, gallery = false }

		-- table to store the lines to write to file
		local lines = { web = {}, gem = {} }

		local navlines = { "<ul>" }

		-- convert one line at a time
		local count = 1
		for line in f:lines() do
			-- the output line:
			local out = { gem = line, web = nil}

			if count == 1 then -- extract title
				pagemeta.ptitle = string.match(line, "^#+%s*(.-)$")
			elseif count == 2 then -- language
				if line == "" then
					pagemeta.lang = "en,es-MX" -- default
				else
					local langformat = "^lang=(%a+)"
					local tradformat = "%s(%a+)->{(.-)}$"
					pagemeta.lang = string.match(line,langformat)	
					local trlang, trname = string.match(line, tradformat)
					if trlang ~= nil then
						local ttext = "in english:"
						if string.match( trlang, "^es" ) then
							ttext = "en español:"
						end
						local weblink = string.format(
							"<a href='./%s.html' rel='alternate'>%s</a>",
							pages[ trname ].slug, trname )
						local gemlink = string.format(
							"=> ./%s.gmi %s",
							pages[ trname ].slug, trname )
						pagemeta.webtranslation = string.format(
							"<p id='translation'>%s %s</p>",
							ttext, weblink )
						pagemeta.gemtranslation = string.format(
							"%s\n%s\n", ttext, gemlink)
					end
				end
			elseif count == 3 then -- obtain page description
				pagemeta.pdescription,n = string.gsub(line,"[{}]","")
			end

			if count <=2 then goto nextline end -- skip normal processing for the first lines

			-- CONVERT LINES
			if string.match( line, "^```") then -- preformated
				if flags.pre then
					out.web = "</pre>"
				else
					out.web = "<pre>"
				end
				flags.pre = not flags.pre 
				goto insert -- skip more checks
			end

			if flags.pre then
				out.web = sanitize( line )
				goto insert
			end

			if string.match( line, "^%+") then -- + append html
				out.gem = nil
				out.web = string.sub(line,3) -- remove "+ "

			elseif string.match( line, "^&") then -- & append gemtext
				out.gem = string.sub(line,3) -- remove "& "

			elseif string.match( line, "^$") then -- empty line
				out.web = closeTags()

			elseif string.match( line, "^#+") then -- HEADERS
				local title, level = getHeader( line )	

				-- add h1 to navigation
				if level==1 then
					table.insert(navlines,string.format("<li><a href='#%s'>%s</a></li>",title,title))
				end

				local close = closeTags()
				out.web = string.format("%s<h%d id='%s'>%s</h%d>",close,level,title,title,level)

			elseif string.match( line, "^>") then -- BLOCKQUOTE
				local close = closeTags()
				out.web = string.format("%s<blockquote>%s</blockquote>", close, string.sub(line,3))

			elseif string.match( line, "^%*") then -- LIST ITEMS
				-- TODO reuse {wikilinks} code from paragraphs
				-- TODO apply the sanitization to everything?
				-- web
				local strippedline = sanitize(string.sub(line, 3)) -- remove "* "
				local webtext = string.gsub( strippedline, "{(.-)}", function (wikiname)
					return string.format("<a href='./%s.html'>%s</a>", pages[wikiname].slug, wikiname)
				end)

				local li = string.format("<li>%s</li>", webtext)
				if not flags.list then -- start list
					out.web = string.format("<ul>\n%s", li)
					flags.list = true
				else -- append list element
					out.web = li
				end

				-- gem
				local gemlinks = {}
				local gemline = string.gsub( line, "{(.-)}", function (wikiname)
					table.insert(gemlinks, string.format("=> ./%s.gmi %s",pages[wikiname].slug, wikiname))
					return wikiname
				end)

				out.gem = gemline
				-- append links if there were any
				if #gemlinks > 0 then
					out.gem = out.gem.."\n"..table.concat(gemlinks,"\n")
				end

			elseif string.match( line, "^=>") then -- LINKS
				local dest, text = getLink( line )
				if string.match( dest, "^%./") then --local links
					local cleantext = string.match( text, "^{(.-)}$" ) -- see if wikilink and clean
					local htmldest = string.gsub( dest, "gmi", "html" )
					if cleantext ~= nil then -- wikilink
						local close = closeTags()
						out.web = string.format("%s<p><a href='%s' class='arrow'>%s</a></p>",close,htmldest,cleantext)
						out.gem = string.format("=> %s %s",dest,cleantext)

					elseif isImagePath( dest ) then  -- images
						local img = string.format('<img src="%s" alt="%s" loading="lazy"/>',dest,text)
						if not flags.gallery then
							local close = closeTags()
							out.web = string.format("%s<gallery>\n%s", close, img)
							flags.gallery = true
						else
							out.web = img
						end

					else -- other files
						local close = closeTags()
						out.web = string.format("%s<p><a href='%s'>%s</a></p>",close,dest,text)
					end

				else -- external links
					out.web = string.format('<p><a href="%s" rel="external noreferrer noopener" target="_blank" class="arrow">%s</a></p>', dest, text)
				end

			else -- PARAGRAPHS
				-- web
				-- search and replace wikilinks
				local webline = string.gsub( line, "{(.-)}", function (wikiname)
					return string.format("<a href='./%s.html'>%s</a>", pages[wikiname].slug, wikiname)
				end)


				if not flags.p then
					out.web = string.format("<p>%s", webline)
					flags.p = true
				else
					out.web = string.format("<br/>\n%s",webline)
				end

				-- gem
				local gemlinks = {}
				local gemline = string.gsub( line, "{(.-)}", function (wikiname)
					table.insert(gemlinks, string.format("=> ./%s.gmi %s",pages[wikiname].slug, wikiname))
					return wikiname
				end)

				out.gem = gemline
				-- append links if there were any
				if #gemlinks > 0 then
					out.gem = out.gem.."\n"..table.concat(gemlinks,"\n")
				end
			end -- paragraphs

			::insert:: -- insert line in table
			for k,l in pairs(out) do
				if l ~= nil then
					table.insert( lines[k], l )
				end
			end


			-- skip incoming links calculation if <pre> mode 
			if flags.pre then goto nextline end

			-- calculate incoming links from outgoing 
			for outname in string.gmatch( line, "{([^ ]?.-[^ ]?)}" ) do
				insertIncoming( outname, name )
			end

			::nextline::
			count = count + 1
		end -- end for line in f:lines()

		-- finalize html
		local close = closeTags()
		if isPre then
			table.insert( lines.web, "</pre>" )
		elseif close~="" then
			table.insert( lines.web, close )
		end

		table.insert( navlines, "</ul>" )
		pagemeta.navcontent = table.concat( navlines, "\n" )

		-- set templates
		if string.match( pagemeta.lang, "^en" ) then
			pagemeta.outs.web.templates = templates.webEn
			pagemeta.outs.gem.templates = templates.gemEn
		else
			pagemeta.outs.web.templates = templates.webEs
			pagemeta.outs.gem.templates = templates.gemEs
		end
		

		-- fill templates and write results
		for key, out in pairs(pagemeta.outs) do
			local fo = assert( io.open( out.path, "w" ) )
			fo:write( fillTemplate(out.templates.header.template, pagemeta) )
			fo:write( table.concat( lines[key],"\n" )) -- content
			fo:write( "\n" )
			fo:close()
		end

		-- close source file
		f:close()
	end
end

function secondPass() -- write incoming links and footer

	for name,page in pairs(pages) do
		local pagemeta = page

		local doIncoming = true
		if #pagemeta.incoming > 0 then 
			local gemlines = {}
			local weblines = { "<ul>" }

			-- format list of incoming links
			for i = 1, #pagemeta.incoming do
				name = pagemeta.incoming[i]
				slug = pages[name].slug
				table.insert( gemlines, string.format("=> ./%s.gmi %s",slug,name) )
				table.insert( weblines, string.format("<li><a href='./%s.html'>%s</a></li>",slug,name) )
			end

			table.insert(weblines,"</ul>")

			page.gemincoming = table.concat( gemlines, "\n" )
			page.webincoming = table.concat( weblines )

		else
			doIncoming = false
			print( string.format("{%s} is an orphan!", name) )
		end
		
		-- fill incoming and footer templates and write results
		for key, out in pairs(pagemeta.outs) do
			local fo = assert( io.open( out.path, "a" ) )
			if doIncoming then
				fo:write( fillTemplate(out.templates.incoming.template, pagemeta) )
			end
			fo:write( fillTemplate(out.templates.footer.template, pagemeta) )
			fo:close()
		end
	end
end

function initPageMetadata( name, date ) -- return a table with metadata
	local meta = { 
		incoming = {},
		slug = slugify(name), name = name, 
		ptitle = "", pdescription = "",
		updatedate = date,
		navcontent = "",
		gemincoming = "", webincoming = "",
		gemtranslation = "", webtranslation = ""
	}
	meta.outs = {
		web = { path = string.format("%s/%s.html", webdir, meta.slug) },
		gem = { path = string.format("%s/%s.gmi", gemdir, meta.slug) }
	}
	return meta
end

function genIndex()
	local indexpath = "src/pages.gmo"

	-- initialize and open index file
	os.execute(string.format("cp templates/index.gmi %s",indexpath))
	local index = assert( io.open(indexpath,"a" ) )

	-- get gmo files in chronological order
	local gmofiles = io.popen("ls -to --time-style=+%F src/*gmo")
	local count = 0
	for filename in gmofiles:lines() do
		local date,basename = string.match(filename,"^.+%s(.-)%ssrc/(.-).gmo$")
		local name = spacify(basename)
		-- create an entry for each file
		if name ~= "pages" then
			local entry = string.format("=> ./%s.gmi {%s}\n", basename, name)
			index:write(entry)
		end
		-- also initialize each name in the pages table
		-- initialize page metadata
		pages[ name ] = initPageMetadata( name, date )
		count = count + 1
	end
	gmofiles:close()
	index:close()
	return count
end

function genLog()
-- generate the following log files:
--	* twtxt gemini and web
-- 	* atom feed gemini and web
--	* gmo log
--	* gmisub

	local logsrc = assert( io.open( "src/log.txt", "r" ) )

	local logs = { 
		gemtw = { path = string.format("%s/tw.txt",gemdir), head="templates/twheader.txt" },
		webtw = { path = string.format("%s/tw.txt",webdir), head="templates/twheader.txt" },
		gmolog = { path = "src/log.gmo", head="templates/logheader.gmi" },
		gematom = { path = string.format("%s/atom.xml",gemdir), head="templates/gematomheader.txt" },
		webatom = { path = string.format("%s/atom.xml",webdir), head="templates/webatomheader.txt" },
		gemfeed = { path = string.format("%s/feed.gmi",gemdir), head="templates/feedheader.gmi" }
	}

	-- create and open logs
	for _,log in pairs(logs) do
		os.execute(string.format("cp %s %s",log.head,log.path))
		log.f = assert( io.open( log.path, "a" ) )
	end

	logs.gematom.f:write(string.format("<updated>%sT12:00:00Z</updated>\n",meta.fecha))
	logs.webatom.f:write(string.format("<updated>%sT12:00:00Z</updated>\n",meta.fecha))


	-- process each line of log.txt
	for line in logsrc:lines() do
		local pattern = "^(.-)%s(.-)%s{(.-)}$"
		local timestamp, text, wikilink = string.match(line,pattern)
		local slug = slugify(wikilink)
		local date = string.sub(timestamp,1,10) -- YYYY-MM-DD

		-- gmo log
		local gmoformat = "+ <article>\n## %s\n%s\n=> ./%s.gmi {%s}\n+ </article>\n"
		logs.gmolog.f:write(string.format(gmoformat, date, text, slug, wikilink))

		-- gmi sub
		local gmisubformat = "=> ./%s.gmi %s %s\n"
		logs.gemfeed.f:write(string.format(gmisubformat, slug, date, text))

		-- twtxt
		local weburl = string.format("https://%s/%s.html",meta.domain,slug)
		local gemurl = string.format("gemini://%s/%s.gmi",meta.domain,slug)
		local twtxtformat = "%s\t%s | %s\n"
		logs.webtw.f:write(string.format(twtxtformat, timestamp, text, weburl))
		logs.gemtw.f:write(string.format(twtxtformat, timestamp, text, gemurl))

		-- url encode
		local encodedslug = slug:gsub("á","%%C3%%A1"):gsub("é","%%C3%%A9"):gsub("í","%%C3%%AD"):gsub("ó","%%C3%%B3"):gsub("ú","%%C3%%BA"):gsub("ñ","%%C3%%B1")

		-- atom
		local weburldate = string.format("https://%s/%s.html#%s",meta.domain,encodedslug,date)
		local atomformat = [[

<entry>
<title>%s</title>
<updated>%s</updated>
<link rel='alternate' href='%s'/>
<id>%s</id>
</entry>
]]
		logs.webatom.f:write(string.format(atomformat, text, timestamp, weburldate, weburldate))
		logs.gematom.f:write(string.format(atomformat, text, timestamp, gemurl, gemurl))
	end


	--close logs
	logs.webatom.f:write("</feed>\n")
	logs.gematom.f:write("</feed>\n")
	for _,log in pairs(logs) do
		log.f:close()
	end

	logsrc:close()
end


-- THE SCRIPT

-- create directories
os.execute(string.format("mkdir -p %s/static %s/img %s/img",webdir,webdir,gemdir))

print(meta.fecha)

-- clean up
print("limpiando archivos previos...")
os.execute(string.format('find %s %s -regex ".+.\\(gmi\\|html\\)$" -exec rm {} +',webdir,gemdir))

-- copy images
print("copiando imágenes y otros archivos...")
os.execute( string.format("find src/img/ -type f -exec cp -vut %s/img/ {} +",webdir))
os.execute( string.format("find src/img/ -type f -exec cp -vut %s/img/ {} +",gemdir))

-- copy stylesheet and misc files
os.execute(string.format("cp -vu templates/estilo.css %s/static/",webdir))

local miscfiles = { "src/llave_sejo.asc" }
for k = 1, #miscfiles do
	os.execute(string.format("cp -vu %s %s/", miscfiles[k], webdir))
	os.execute(string.format("cp -vu %s %s/", miscfiles[k], gemdir))
end


-- generate log files
print("generando log...")
genLog()

-- generate index
print("generando índice de páginas...")
local n = genIndex()

-- start first pass
-- converting from gmo to gmi and html and calculating incoming links
print(string.format("convirtiendo %d páginas...",n))
firstPass()

-- second pass, write incoming links
print("escribiendo incoming links...")
secondPass()

-- update index files with home
os.execute("cp web/home.html web/index.html")
os.execute("cp gem/home.gmi gem/index.gmi")

print("listx!")
