import types
import subnetviz.format
from subnetviz.format \
    import DisplayNode, Network_DisplayNode, Unallocated_DisplayNode
from subnetviz.address_utils import *

def format_net (net, td_attrs, file):
    """Formats net (a DisplayNode) to an HTML \"td\" element with
    attributes from td_attrs.  Writes the HTML to file.

    This function may be overridden by passing format_net_fn to
    print_tree."""
    
    length, length_log2 = net.length, net.length_log2
    td_attrs = list (td_attrs)
    classes = []
    if isinstance (net, Network_DisplayNode) and length == 1:
        classes.append ("leaf")
    if net.label is None:
        classes.append ("dummy")
    if isinstance (net, Network_DisplayNode):
        classes.append ("allocated")
    elif isinstance (net, Unallocated_DisplayNode):
        classes.append ("unallocated")
    if len (classes) > 0:
        td_attrs.append (("class", " ".join (classes)))
    
    assert length > 0
    if length > 1:
        if length < 100000:
            length_s = str (length)
        elif isinstance (length_log2, (types.IntType, types.LongType)):
            length_s = "2^%d" % (length_log2,)
        else:
            length_s = "~2^%.1f" % (length_log2,)
        tooltip = ("%s addresses from %s to %s" %
                   (length_s, address_to_IPy (net.start),
                    address_to_IPy (net.end)))
    else:
        tooltip = "1 address"
    td_attrs.append (("title", tooltip))
    
    file.write (html_encode_element_open ("td", td_attrs))
    if isinstance (net, Network_DisplayNode):
        if net.label is not None:
            file.write ("<div class='title'>%s</div>" %
                        (html_encode_text (net.label),))
        file.write ("<div class='address'>%s</div>" %
                    (html_encode_text (network_to_IPy (net.address)),))
    elif isinstance (net, Unallocated_DisplayNode):
        assert net.label is None
        file.write ("<div class='title'>(unallocated)</div>")
    file.write ("</td>")

@subnetviz.format.registered_format ("html")
def print_tree (net, file, format_net_fn = format_net):
    """HTML table-based format."""
    
    net.offset = net.depth = 0
    subnetviz.format.annotate (net)
    
    table = build_table (net)
    file.write ("<html><head>")
    file.write ("<title>%s</title>" % (html_encode_text (page_title (net)),))
    file.write ("<link rel='stylesheet' href='style.css'>")
    file.write ("</head>")
    file.write ("<body>")
    skip_root = net.label is None and len (net.children) > 0
    print_table (table, file, format_net_fn, skip_root)
    file.write ("</body></html>")

def page_title (net):
    if net.label is None and len (net.children) > 0:
        nets = [child for child in net.children
                if not isinstance (child, subnetviz.format.Unallocated_DisplayNode)]
    else:
        nets = [net]
    nets = [str (network_to_IPy (net.address)) for net in nets]
    if len (nets) > 2:
        del nets[2:]
        nets.append ("...")
    return "Subnets: " + ", ".join (nets)

def build_table (net):
    """Transforms the given root of a DisplayNode tree to a table
    (depth-major list of lists), with parent nodes in the tree
    laterally spanning all their descendant nodes.  Spanning is
    represented as the duplication of nodes (by object identity)
    laterally (breadth-wise) through a minor list.  Empty areas are
    represented by None."""
    
    width = net.width
    height = net.height ()
    table = [None] * height
    for i in xrange (height):
        table[i] = [None] * width
    assert net.offset == 0
    assert net.depth == 0
    x_pos = y_pos = 0
    for subnet in net.iter_breadth_first ():
        if subnet.depth != y_pos:
            x_pos = 0
            y_pos += 1
        assert subnet.depth == y_pos
        assert x_pos <= subnet.offset
        x_pos = subnet.offset
        while x_pos < subnet.offset + subnet.width:
            table[y_pos][x_pos] = subnet
            x_pos += 1
        assert x_pos <= width
    return table

def print_table (table, file, format_net_fn, skip_root):
    """Prints an internal table of DisplayNodes (as generated by
    build-table) as an HTML table to file.  format_net_fn is the
    function that formats DisplayNodes to HTML td elements.  When
    skip_root is true, the root-level of the table is skipped."""
    
    width, height = len (table), (len (table[0]) if len (table) > 0 else 0)
    x_start_pos = 1 if skip_root else 0
    file.write ("<table class='subnettree'>")
    for y_pos in xrange (height):
        file.write ("<tr>")
        for x_pos in xrange (x_start_pos, width):
            subnet = table[x_pos][y_pos]
            if y_pos > 0 and subnet is table[x_pos][y_pos - 1]:
                continue
            y_end = y_pos
            while y_end < height and subnet is table[x_pos][y_end]:
                y_end += 1
            rowspan = y_end - y_pos
            td_attrs = [("rowspan", rowspan)] if rowspan > 1 else []
            if subnet is None:
                file.write (html_encode_element_open ("td", td_attrs))
                file.write ("</td>")
            else:
                format_net_fn (subnet, td_attrs, file)
        file.write ("</tr>")
    file.write ("</table>")

def html_encode_text (s):
    return (unicode (s).
            replace ("&", "&amp;").
            replace ("<", "&lt;").
            replace (">", "&gt;").
            replace ("'", "&#39;").
            replace ("\"", "&quot;"))

def html_encode_attrs (attrs):
    return " ".join (("%s=\"%s\"" % (name, html_encode_text (value))
                      for (name, value) in attrs))

def html_encode_element_open (tagname, attrs = []):
    return "<%s%s>" % (tagname,
                       (" %s" % (html_encode_attrs (attrs),))
                       if len (attrs) > 0 else "")
