#!/usr/bin/python
import sys
from lxml import etree as ET

# this does not recognize octal unlike int(s, 0)
def intdh(s):
	if s.startswith("0x"):
		return int(s[2:], 16)
	else:
		return int(s)

if sys.argv[1:2]:
	xml = sys.argv[1]
	tree = ET.parse(xml)
else:
	tree = ET.parse(sys.stdin)

root = tree.getroot();

# TODO: make this configurable and autoselect the most frequent
reg_default = "reg32"
type_defaults = set(("enum", "bitfield", "hexa"))
attr_defaults = {"prefix" : "none"}
true_strings = ("1", "yes", "true")

ns = "{http://nouveau.freedesktop.org/}"

out = sys.stdout

indent = ""

comments = {}
empty_line = False

def process(container):
	global indent
	indent += "\t"
	_process(container)
	indent = indent[:-1]

def get_pos(elem, which):
	pos = elem.attrib.get(which, None)
	if pos is not None:
		return intdh(pos)
	else:
		return intdh(elem.attrib.get("pos", "-1"))

def _process(container):
	global empty_line
	maxvalue = max([-1] + [max(intdh(elem.attrib.get("offset", "-1")), intdh(elem.attrib.get("value", "-1"))) for elem in container.iterchildren(tag=ET.Element)])
	maxlow = max([-1] + [get_pos(elem, "low") for elem in container.iterchildren(tag=ET.Element)])
	maxhigh = max([-1] + [get_pos(elem, "high") for elem in container.iterchildren(tag=ET.Element)])

	any_multibit = False
	for elem in container.iterchildren(tag=ET.Element):
		if "low" in elem.attrib and "high" in elem.attrib and elem.attrib["low"] != elem.attrib["high"]:
			any_multibit = True
			break
	value_hexdigits = len(hex(maxvalue)) - 2 if maxvalue >= 0 else -1

	offset_format = "%0" + str(value_hexdigits) + "x" if maxvalue >= 0 else None
	if maxvalue >= 16:
		value_len = value_hexdigits + 2
		# TODO: smart pretty print value instead of this
		value_format = "0x%0" + str(value_hexdigits) + "x"
	elif maxvalue >= 0:
		value_len = len(str(maxvalue))
		# TODO: smart pretty print value instead of this
		value_format = "%" + str(value_len) + "d"
	else:
		value_len = -1
		value_format = None

	low_format = "%0" + str(len(str(maxlow))) + "d" if maxlow >= 0 else None
	high_format = "%0" + str(len(str(maxhigh))) + "d" if maxhigh >= 0 else None

	for elem in container.iterchildren(tag=ET.Element):
		tag = elem.tag
		assert tag.startswith(ns)
		tag = tag[len(ns):]
		if tag == "brief" or tag == "doc":
			continue

		is_reglike = False
		line = ""
		if ((tag[:3] == "reg" and tag[3:].isdigit()) or tag == "array" or tag == "stripe") and "offset" in elem.attrib:
			line = offset_format % (intdh(elem.attrib.get("offset", -1)),)
			del elem.attrib["offset"]
			if "stride" in elem.attrib:
				line += " {" + elem.attrib["stride"] + "}"
				del elem.attrib["stride"]
			if "length" in elem.attrib:
				line += " [" + elem.attrib["length"] + "]"
				del elem.attrib["length"]
			if "name" in elem.attrib:
				line += " " + elem.attrib["name"]
				del elem.attrib["name"]
			is_reglike = True
		elif tag == "bitfield":
			if "low" not in elem.attrib and "pos" not in elem.attrib:
				print >> sys.stderr, "Bitfield " + elem.attrib.get("name", "<unnamed>") + " has no low bit!"
			low = get_pos(elem, "low")
			high = get_pos(elem, "high")
			if high < 0:
				high = low
			if low != high:
				line = low_format % (low,) + "-" + high_format % (high,)
			elif any_multibit:
#				line = (" " * (len(str(maxlow)))) + low_format % (low,) + (" " * (len(str(maxhigh)) - len(str(maxlow)) + 1))
				line = low_format % (low,) + (" " * (len(str(maxhigh)) + 1))
			else:
				line = low_format % (low,)
                        if "pos" in elem.attrib:
                                del elem.attrib["pos"]
			if "low" in elem.attrib:
				del elem.attrib["low"]
			if "high" in elem.attrib:
				del elem.attrib["high"]
		elif tag == "value":
			if "value" in elem.attrib:
				line = (value_format % (intdh(elem.attrib["value"]),))
				if "name" in elem.attrib:
					line += " = "
				del elem.attrib["value"]
			else:
				if "name" not in elem.attrib:
					print >> sys.stderr, "Value has no value and no name attributes!"
				if value_len >= 0:
					line += " " * (value_len + 3)

			if "name" in elem.attrib:
				line += elem.attrib["name"]
				del elem.attrib["name"]
		elif tag == "import":
			line = "#import"
			if "file" in elem.attrib:
				file = elem.attrib["file"]
				line += " \"" + file + "\""
			else:
				print >> sys.stderr, "Import with no file attribute!"
			del elem.attrib["file"]
		elif tag[:4] == "use-":
			line = "use " + tag[4:]
		else:
			line = tag
		if "name" in elem.attrib:
			line += " " + elem.attrib["name"]
			del elem.attrib["name"]
		if is_reglike or tag == "bitfield":
			need_regwidth = tag[:3] == "reg" and tag != reg_default
			if ("type" in elem.attrib and elem.attrib["type"] not in type_defaults) or need_regwidth:
				line += " " + elem.attrib.get("type", "reg").replace(" ", ",") + (":" + tag[3:] if need_regwidth else "")
			if "type" in elem.attrib:
				del elem.attrib["type"]
		brief = "\n".join([elem2.text for elem2 in elem.iterchildren(tag=ET.Element) if elem2.tag == ns + "brief"])
		if brief:
			line += " \"" + brief + "\""

		if "varset" in elem.attrib and "variants" in elem.attrib:
			elem.attrib["variants"] = elem.attrib["varset"] + "=" + elem.attrib["variants"]
			del elem.attrib["varset"]

		for keyword in ("inline", "bare"):
			if keyword in elem.attrib:
				if elem.attrib[keyword].lower() in true_strings:
					line = keyword + " " + line
				del elem.attrib[keyword]

		for attr in elem.attrib.keys():
			value = elem.attrib[attr]
			if value != attr_defaults.get(attr):
				line += " " + (attr if attr != "variants" else "") + "(" + value + ")"
		if is_reglike:
			if tag == "array":
				line += " ="
			elif tag == "stripe":
				line += " :="
		elif tag == "bitfield" or tag == "value" or tag == "import" or tag[:4] == "use-":
			pass
		else:
			line += ":"
		our_comments = comments.get(elem, [])
		if our_comments:
			line += " // " + our_comments[0]
		if empty_line:
			print
			empty_line = False
		print indent + line
		if len(our_comments) > 1:
			for comment in our_comments[1:]:
				print indent + "\t// " + comment

		doc = "\n".join([elem2.text for elem2 in elem.iterchildren(tag=ET.Element) if elem2.tag == ns + "doc"])
		if doc:
			lines = [line.strip() for line in doc.split("\n")]
			while lines and not lines[0]:
				lines = lines[1:]
			while lines and not lines[-1]:
				lines = lines[:-1]
			for line in lines:
				print indent + "\t: " + line
		process(elem)
		if elem.tail and elem.tail.count("\n") > 1:
			empty_line = True

last_non_comment = None
for node in root.iter():
	if isinstance(node, ET._Comment) and last_non_comment is not None:
# reattach whitespace following comment to previous node
		prev = node.getprevious()
		if prev is not None:
			prev.tail = (prev.tail if prev.tail else "") + node.tail
		else:
			prev = node.getparent()
			if prev is not None:
				prev.text = (prev.text if prev.text else "") + node.tail

		node.getparent().remove(node)
		lines = node.text.split("\n")
		queue = []
		got_anything = comments.get(last_non_comment) is not None
		for line in lines:
			line = line.strip()
			if line or got_anything:
				queue.append(line)
			if line:
				comments.setdefault(last_non_comment, []).extend(queue)
				queue = []
				got_anything = True
	else:
		last_non_comment = node
_process(root)

