#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
A little script to fix up HTML pages referring to files on the system
and other source fragments.

This assumes XHTML.
"""

import datetime
import os
import shutil
import sys
from xml.etree import ElementTree as etree


HTML_NS = "http://www.w3.org/1999/xhtml"


############ tiny DOM start (snarfed and simplified from DaCHS stanxml)
# (used to write HTML)

class _Element(object):
	"""An element within a DOM.

	Essentially, this is a simple way to build elementtrees.  You can
	reach the embedded elementtree Element as node.

	Add elements, sequences, etc, using indexation, attributes using function
	calls; names with dashes are written with underscores, python
	reserved words have a trailing underscore.
	"""
	_generator_t = type((x for x in ()))

	def __init__(self, name):
		self.node = etree.Element(name)

	def add_text(self, tx):
		"""appends tx either the end of the current content.
		"""
		if len(self.node):
			self.node[-1].tail = (self.node[-1].tail or "")+tx
		else:
			self.node.text = (self.node.text or "")+tx

	def __getitem__(self, child):
		if child is None:
			return

		elif isinstance(child, str):
			self.add_text(child)

		elif isinstance(child, (int, float)):
			self.add_text(str(child))

		elif isinstance(child, _Element):
			self.node.append(child.node)

		elif isinstance(child, etree.Element):
			self.node.append(child)

		elif hasattr(child, "__iter__"):
			for c in child:
				self[c]

		else:
			raise Exception("%s element %s cannot be added to %s node"%(
				type(child), repr(child), self.node.tag))
		return self
	
	def __call__(self, **kwargs):
		for k, v in kwargs.items():
			if k.endswith("_"):
				k = k[:-1]
			k = k.replace("_", "-")
			self.node.attrib[k] = v
		return self

	def dump(self, encoding="utf-8", dest_file=sys.stdout):
	    etree.ElementTree(self.node).write(
		dest_file, encoding=encoding)


class _T(object):
	"""a very simple templating engine.

	Essentially, you get HTML elements by saying T.elementname, and
	you'll get an _Element with that tag name.

	This is supposed to be instanciated to a singleton (here, T).
	"""
	def __getattr__(self, key):
		return  _Element(key)

T = _T()


class Context:
	"""A translation context.

	This keeps the state of the conversion, i.e., things like the ToC items,
	the files to copy, etc.
	"""
	def __init__(self, tree, opts):
		self.tree = tree
		self.opts = opts
		self.toc = []
		self.to_copy = []
		self.default_root = os.environ["HOME"]
		self.atexit_functions = []

	def atexit(self, func):
		"""arrange for calling func when done_traversing is called.

		func will be passed self.
		"""
		self.atexit_functions.append(func)

	def _copy_deps(self):
		"""do the copy operations planned in to_copy.

		This is run when done_traversing is called.
		"""
		for src, dest in self.to_copy:
			if dest is None:
				continue
			srcTime = os.path.getmtime(src)
			if not os.path.exists(dest) or srcTime>os.path.getmtime(dest):
				shutil.copyfile(src, dest)
				os.utime(dest, (srcTime, srcTime))

	def done_traversing(self):
		"""call this when the tree has been traversed.
		"""
		self._copy_deps()
		for func in self.atexit_functions:
			func(self)


class Converter:
	"""A singleton containing handler methods for our extension objects.

	For each element the conversion functions see, it sees if a corresponding
	convert_<elname> (class) method exists and then calls it with the
	element and the current Context object.  Namespaces are ignored for
	method lookup.

	Whatever the function returns will replace the current element in the
	tree.  Things will probably break if you return anything but
	an element tree element or an _Element instance.

	Use the convert method to run a conversion.  You cannot replace the
	root element (as the elementtree doc warns against it.
	"""
	@classmethod
	def convert_downloadable(cls, el, ctx):
		source_path = os.path.join(ctx.default_root, el.get("source"))
		file_name = os.path.basename(source_path)
		ctx.to_copy.append((source_path, file_name))
		return T.a(href=file_name, class_="downloadable")[file_name]

	@classmethod
	def convert_inlinesource(cls, el, ctx):
		source_path = os.path.join(ctx.default_root, el.get("source"))
		with open(source_path) as f:
			return T.div(class_="source")[
				T.pre[f.read()],
				T.p["File: "+source_path]]

	@classmethod
	def convert_linksource(cls, el, ctx):
		source_path = os.path.join(ctx.default_root, el.get("source"))
		target = el.get("target", os.path.basename(source_path))
		ctx.to_copy.append((source_path, target))
		return T.a(href=target)[el.text or target]

	@classmethod
	def convert_sourcefragment(cls, el, ctx):
		res = T.div(class_="source")[
			T.pre[el.text]]
		if el.get("source"):
			res[T.p["For: "+el.get("source")]]
		return res

	@classmethod
	def convert_h2(cls, el, ctx):
		if el.get("toc"):
			label = el.attrib.pop("toc")
			ctx.toc.append((label, el.text))
			return T.a(name=label)[el]

		return el

	@classmethod
	def convert_toc(cls, el, ctx):
		toc_el = T.div(id="toc")

		def fill_toc(ctx):
			toc_el[
				T.ul(class_="toc")[[
						T.li[T.a(href="#"+tag)[title]]
					for tag, title in ctx.toc]]]

		ctx.atexit(fill_toc)

		return toc_el

	@classmethod
	def convert_now(cls, el, ctx):
		return T.span(class_="now")[
			datetime.datetime.utcnow().strftime("%Y-%m-%d, %H:%M")+" UTC"]


	@classmethod
	def _convert(cls, element, ctx):
		"""applies conversions postorder to the element's children.
		"""
		for child in element:
			cls._convert(child, ctx)

		for index, child in enumerate(element):
			tag_name = child.tag.split("}")[-1]
			converter = getattr(cls, "convert_"+tag_name, None)
			if converter is not None:
				res = converter(child, ctx)
				tail = element[index].tail
				element[index] = res.node if isinstance(res, _Element) else res
				element[index].tail = tail

		return element

	@classmethod
	def convert(cls, ctx):
		"""converts ctx.tree in place.
		"""
		cls._convert(ctx.tree.getroot(), ctx)
		ctx.done_traversing()


def parseCommandLine():
	from optparse import OptionParser
	parser = OptionParser(usage="%prog [options]")
	parser.add_option("-d", "--deps", help="dump dependencies",
		dest="dumpDeps", action="store_true")
	opts, args = parser.parse_args()
	if args:
		parser.print_help()
		sys.exit(1)
	return opts, args


def main():
	etree.register_namespace("", HTML_NS)
	opts, _ = parseCommandLine()
	in_tree = etree.parse(sys.stdin.buffer)

	ctx = Context(in_tree, opts)
	Converter.convert(ctx)

	if opts.dumpDeps:
		sys.stdout.write(" ".join([src for src, dest in ctx.to_copy])+"\n")
	else:
		ctx.tree.write(sys.stdout.buffer, 
			encoding="utf-8")


if __name__=="__main__":
	main()
