
import sys
import os
import re

__all__ = ["Formatter"]

try:
    version = os.environ['VERSION_NUMBER']
except:
    version = ""

styles = {
    'b':        "\\fB@\\fR",
    'i':        "\\fI@\\fR",
    'em':       "\\fI@\\fR",
    'code':     "\\fC@\\fR",
    'span':     "\\fC@\\fR",
    'sup':      "\\u@\\d",
    'hr':       ""
    }

formats = {
    'br':       "\n.br\n",
    'h2':       "\n.SH @",
    'h3':       "\n.SS @",
    'h4':       "\n.SS @",
    'dt':       ("\n.IP \"@\" 4m", 'no_nl'),
    'dd':       "\n.br\n@",
    'ul':       ("\n.RS 4n\n@\n.RE\n", 'in_ul'),
    'menu':     ("\n.RS 4n\n@\n.RE\n", 'in_ul'),
    'dir':      ("\n.RS 4n\n@\n.RE\n", 'in_ul'),
    'ol':       ("\n.IP\n@\n.PP\n", 'index'),
    'p':        "\n.PP\n@",
    'pre':      ("\n.br\n.nf\n\\fC\n@\n\\fR\n.fi\n", 'preformat')
    }

formats.update(styles)

def is_string(x):
    return isinstance(x, str)

def is_tuple(x):
    return isinstance(x, tuple)

def is_list(x):
    return isinstance(x, list)

def is_blank(s):
    return is_string(s) and s.strip() == ""

def clean(content):
    return [item for item in content if not is_blank(item)]

class Formatter:
    def __init__(self, filename, stream = sys.stdout):
	self.stream = stream
	self.style = dict(preformat = False,
			  in_ul = False,
			  no_nl = False,
			  in_table = False,
			  in_tr = False,
			  index = [])
	self.stack = []
	self.strip_re = re.compile("^[ \t]+")
	self.filename = filename
	self.at_bol = True

    def warning(self, msg):
	sys.stderr.write(msg + '\n')

    def set(self, var, val):
	self.style[var] = val

    def get(self, var):
	return self.style[var]

    def push(self, **kwargs):
	self.stack.append(self.style.copy())
	self.style.update(**kwargs)

    def pop(self):
	self.style = self.stack.pop()

    def show(self, s):
	self.stream.write(s)
	if s != '':
	    self.at_bol = s.endswith('\n')

    def pp_with(self, content, var, val):
	self.push()
	self.set(var, val)
	self.pp(content)
	self.pop()

    def fmt(self, format, content, var = None):
	# String.partition is only in 2.5+
	# (pre,sep,post) = format.partition("@")
	if self.get('no_nl') and '\n' in format:
	    self.warning("can't handle line breaks in <dt>...</dt>")
	    format = "@"
	f = format.split('@', 1)
	pre = f[0]
	if len(f) > 1:
	    sep = '@'
	    post = f[1]
	else:
	    sep = ''
	    post = ''

	if pre != "":
	    self.show(pre)
	if sep != "":
	    if var:
		if var == 'index':
		    val = self.get('index') + [0]
		else:
		    val = True
		self.pp_with(content, var, val)
	    else:
		self.pp(content)
	if post != "":
	    self.show(post)

    def pp_li(self, content):
	if self.get('in_ul'):
	    self.fmt("\n.IP \(bu 4n\n@", content)
	else:
	    idx = self.get('index')
	    idx[-1] += 1
	    sec = ".".join(map(str,idx))
	    self.show("\n.IP \\fB%s\\fR\n" % sec)
	    self.set('index', idx)
	    self.pp(content)

    def pp_title(self):
	self.show("\n.TH " +
		  os.path.basename(self.filename).replace(".html","") +
		  " 1 \"\" \"GRASS " +
		  version +
		  "\" \"Grass User's Manual\"")

    def pp_tr(self, content):
	content = clean(content)
	self.push(in_tr = True)
	col = 0
	for item in content:
	    if not is_tuple(item):
		self.warning("invalid item in table row: %s" % str(item))
		continue
	    (tag, attrs, body) = item
	    if tag not in ['td', 'th']:
		self.warning("invalid tag in table row: %s" % tag)
		continue
	    if col > 0:
		self.show("\t \t")
	    self.show("T{\n")
	    self.pp(body)
	    self.show("\nT}")
	    col += 1
	self.show("\n")
	self.pop()

    def pp_tbody(self, content):
	for item in content:
	    if is_tuple(item):
		(tag, attrs, body) = item
		if tag in ['thead', 'tbody', 'tfoot']:
		    self.pp_tbody(body)
		elif tag == 'tr':
		    self.pp_tr(body)
		    self.show(".sp 1\n")

    def count_cols(self, content):
	cols = 0
	for item in content:
	    n = 0
	    if is_blank(item):
		pass
	    elif is_tuple(item):
		(tag, attrs, body) = item
		if tag in ['thead', 'tbody', 'tfoot']:
		    n = self.count_cols(body)
		elif tag == 'tr':
		    n = len(clean(body))
		cols = max(cols, n)
	    else:
		self.warning("invalid item in table: %s" % str(item))
	return cols

    def pp_table(self, content):
	cols = self.count_cols(content)
	if cols == 0:
	    return
	self.show("\n.TS\nexpand;\n")
	self.show(" lw1 ".join(["lw60" for i in range(cols)]) + ".\n")
	self.pp_tbody(content)
	self.show("\n.TE\n")

    def pp_tag(self, tag, content):
	if self.get('in_tr') and tag not in styles:
	    self.pp(content)
	elif tag in formats:
	    spec = formats[tag]
	    if is_string(spec):
		self.fmt(spec, content)
	    else:
		(fmt, var) = spec
		self.fmt(fmt, content, var)
	elif tag == 'table':
	    if self.get('in_table'):
		self.warning("cannot handle nested tables")
		return
	    self.push(in_table = True)
	    self.pp_table(content)
	    self.pop()
	elif tag == 'li':
	    self.pp_li(content)
	elif tag == 'title':
	    self.pp_title()
	else:
	    self.pp(content)

    def pp_string(self, content):
	if content == "":
	    return
	s = content
	if self.get('no_nl'):
	    s = s.replace("\n"," ")
	s = s.replace("\\", "\\(rs")
	s = s.replace("'", "\\(cq")
	s = s.replace("\"", "\\(dq")
	s = s.replace("`", "\\(ga")
	s = s.replace("-", "\\-")
	if self.at_bol and s[0] in [".","'"]:
	    s = "\\&" + s
	self.show(s)

    def pp_text(self, content):
	if content == "":
	    return
	lines = content.splitlines(True)
	if len(lines) != 1:
	    for line in lines:
		self.pp_text(line)
	    return
	else:
	    content = lines[0]
	if self.at_bol and not self.get('preformat'):
	    content = self.strip_re.sub('', content)
	self.pp_string(content)

    def pp_list(self, content):
	for item in content:
	    self.pp(item)

    def pp(self, content):
	if is_list(content):
	    self.pp_list(content)
	elif is_tuple(content):
	    (tag, attrs, body) = content
	    self.pp_tag(tag, body)
	elif is_string(content):
	    self.pp_text(content)

