#encoding: utf8

import os, gettext, time, copy, sys, re

import unit_tree.helpers as helpers
import wesnoth.wmlparser2 as wmlparser2

pics_location = "../../pics"

html_header = '''
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href=\"%(path)sstyle.css\" type=\"text/css\"/>
<script type="text/javascript" src="%(path)s/menu.js"></script>
<title>%(title)s</title>
</head>
<body><div>'''.strip()

top_bar = '''
<div class="header">
<a href="http://www.wesnoth.org">
<img src="%(path)swesnoth-logo.jpg" alt="Wesnoth logo"/>
</a>
</div>
<div class="topnav">
<a href="%(path)sindex.html">Wesnoth Units database</a>
</div>'''.strip()

html_footer = '''
<div id="footer">
<p>%(generation_note)s</p>
<p><a href="http://wiki.wesnoth.org/Site_Map">Site map</a></p>
<p><a href="http://www.wesnoth.org/wiki/Wesnoth:Copyrights">Copyright</a> &copy; 2003-2012 The Battle for Wesnoth</p>
<p>Supported by <a href="http://www.jexiste.fr/">Jexiste</a></p>
</div>
</div>
</body></html>
'''.strip()

all_written_html_files = []

error_only_once = {}
def error_message(message):
    if message in error_only_once: return
    error_only_once[message] = 1
    write_error(message)

helpers.error_message = error_message

def reset_errors():
    error_only_once = {}

class MyFile:
    """
    Python 2 is a bit weird with encodings, really should switch this to
    Python 3.
    """
    def __init__(self, filename, mode):
        self.filename = filename
        self.f = open(filename, mode)
    def write(self, x):
        x = x.encode("utf8")
        self.f.write(x)
    def close(self):
        self.f.close()


class Translation:
    def __init__(self, localedir, langcode):
        self.catalog = {}
        self.localedir = localedir
        self.langcode = langcode
        class Dummy:
            def ugettext(self, x):
                if not x: return ""
                caret = x.find("^")
                if caret < 0: return x
                return x[caret + 1:]
        self.dummy = Dummy()

    def translate(self, string, textdomain):
        if textdomain not in self.catalog:
            try:
                self.catalog[textdomain] = gettext.translation(
                    textdomain, self.localedir, [self.langcode])
                self.catalog[textdomain].add_fallback(self.dummy)
            except IOError:
                self.catalog[textdomain] = self.dummy
            except AttributeError:
                self.catalog[textdomain] = self.dummy
            except IndexError:
                # not sure why, but this happens within the
                # gettext.translation call sometimes
                self.catalog[textdomain] = self.dummy

        r = self.catalog[textdomain].ugettext(string)

        return r


class GroupByRace:
    def __init__(self, wesnoth, campaign):
        self.wesnoth = wesnoth
        self.campaign = campaign

    def unitfilter(self, unit):
        if not self.campaign: return True
        return unit.campaigns and self.campaign == unit.campaigns[0]

    def groups(self, unit):
        return [T(unit.race, "plural_name")]

    def group_name(self, group):
        if not group: return "None"
        return group

class GroupByNothing:
    def __init__(self):
        pass

    def unitfilter(self, unit):
        return True

    def groups(self, unit):
        return ["units"]

    def group_name(self, group):
        return "units"

class GroupByFaction:
    def __init__(self, wesnoth, era):
        self.wesnoth = wesnoth
        self.era = era

    def unitfilter(self, unit):
        return self.era in unit.eras

    def groups(self, unit):
        return [x for x in unit.factions if x[0] == self.era]

    def group_name(self, group):
        era = self.wesnoth.era_lookup[group[0]]
        if group[1]:
            faction = era.faction_lookup[group[1]]
            name = T(faction, "name")
            name = name[name.rfind("=") + 1:]
        else:
            name = "factionless"
        return name

global_htmlout = None
def T(tag, att):
    if not tag: return "none"
    return tag.get_text_val(att, translation = global_htmlout.translate)


class HTMLOutput:
    def __init__(self, isocode, output, addon, campaign, is_era, wesnoth, verbose = False):
        global global_htmlout
        self.output = output
        self.addon = addon
        self.campaign = campaign
        self.is_era = is_era
        self.verbose = verbose
        self.target = "index.html"
        self.wesnoth = wesnoth
        self.forest = None
        self.translation = Translation(options.transdir, isocode)
        self.isocode = isocode
        global_htmlout = self

    def translate(self, string, domain):
        return self.translation.translate(string, domain)

    def analyze_units(self, grouper):
        """
        This takes all units belonging to a campaign, then groups them either
        by race or faction, and creates an advancements tree out of it.
        """

        # Build an advancement tree forest of all units.
        forest = self.forest = helpers.UnitForest()
        units_added = {}
        for uid, u in self.wesnoth.unit_lookup.items():
            if grouper.unitfilter(u):
                forest.add_node(helpers.UnitNode(u))
                units_added[uid] = u

        #print("    %d/%d units" % (len(units_added), len(self.wesnoth.unit_lookup)))

        # Always add any child units, even if they have been filtered out..
        while units_added:
            new_units_added = {}
            for uid, u in units_added.items():
                for auid in u.advance:
                    if not auid in units_added:
                        try:
                            au = self.wesnoth.unit_lookup[auid]
                        except KeyError:
                            error_message(
                                "Warning: Unit %s not found as advancement of %s\n" %
                                (auid, repr(uid)))
                            continue
                        forest.add_node(helpers.UnitNode(au))
                        new_units_added[auid] = au
            units_added = new_units_added
        
        # Also add parent units
        added = True
        while added:
            added = False
            for uid, u in self.wesnoth.unit_lookup.items():
                if uid in forest.lookup: continue
                for auid in u.advance:
                    if auid in forest.lookup:
                        forest.add_node(helpers.UnitNode(u))
                        added = True
                        break

        forest.update()

        # Partition trees by race/faction of first unit.
        groups = {}
        breadth = 0

        for tree in forest.trees.values():
            u = tree.unit
            ugroups = grouper.groups(u)

            for group in ugroups:
                groups[group] = groups.get(group, []) + [tree]
                breadth += tree.breadth

        thelist = groups.keys()
        thelist.sort(key = lambda x: grouper.group_name(x))

        rows_count = breadth + len(thelist)
        # Create empty grid.
        rows = []
        for j in range(rows_count):
            column = []
            for i in range(6):
                column.append((1, 1, None))
            rows.append(column)

        # Sort advancement trees by name of first unit and place into the grid.
        def by_name(t1, t2):
            u1 = t1.unit
            u2 = t2.unit

            u1name = T(u1, "name")
            u2name = T(u2, "name")
            return cmp(u1name, u2name)

        def grid_place(nodes, x):
            nodes.sort(by_name)
            for node in nodes:
                level = node.unit.level
                if level < 0: level = 0
                if level > 5: level = 5
                rows[x][level] = (1, node.breadth, node)
                for i in range(1, node.breadth):
                    rows[x + i][level] = (0, 0, node)
                grid_place(node.children, x)
                x += node.breadth
            return x

        x = 0
        for group in thelist:
            node = helpers.GroupNode(group)
            node.name = grouper.group_name(group)

            rows[x][0] = (6, 1, node)
            for i in range(1, 6):
                rows[x][i] = (0, 0, None)
            nodes = groups[group]
            x += 1
            x = grid_place(nodes, x)

        self.unitgrid = rows
        
        return len(forest.lookup)

    def write_navbar(self, report_type):
        def write(x): self.output.write(x)
        
        all_written_html_files.append((self.isocode, self.output.filename))

        languages = self.wesnoth.languages_found
        langlist = languages.keys()
        langlist.sort()
        
        write(top_bar % {"path" : "../../"})

        write("""
        

        <div class="navbar">
        """)

        write("<ul class=\"navbar\">")

        def abbrev(name):
            abbrev = name[0]
            word_seperators = [" ", "_", "+", "(", ")"]
            for i in range(1, len(name)):
                if name[i] in ["+", "(", ")"] or name[i - 1] in word_seperators and name[i] not in word_seperators:
                    abbrev += name[i]
            return abbrev

        def add_menu(id, name, class2=""):
            write("""<li class="popuptrigger"
                onclick="toggle_menu(this, '""" + id + """', 2)"
                onmouseover="toggle_menu(this, '""" + id + """', 1)"
                onmouseout="toggle_menu(this, '""" + id + """', 0)">""")
            write('<a class="' + class2 + '">' + name + "</a>")
            write('<div class="popupmenu" id="' + id + '">')
            write("<div>" + name + "</div>")

        # We may not have all the required info yet so defer writing the
        # campaigns/eras navigation.
        
        # Campaigns
        x = self.translate("addon_type^Campaign", "wesnoth")
        add_menu("campaigns_menu", x)
        write("PLACE CAMPAIGNS HERE\n")
        write("</div></li>\n")
        
        # Eras
        x = self.translate("Era", "wesnoth")
        add_menu("eras_menu", x)
        write("PLACE ERAS HERE\n")
        write("</div></li>\n")

        # Races / Factions

        if not self.is_era:
            x = self.translate("Race", "wesnoth-lib")
            add_menu("races_menu", x)

            write("<a href=\"mainline.html\">%s</a><br/>\n" % (
                self.translate("all", "wesnoth-editor")))

            r = {}, {}
            for u in self.wesnoth.unit_lookup.values():

                race = u.race
                racename = T(race, "plural_name")

                m = 1
                if u:
                    m = 0

                r[m][racename] = race.get_text_val("id") if race else "none"
            racenames = sorted(r[0].items())
            if r[1].items():
                racenames += [("-", "-")] + sorted(r[1].items())

            for racename, rid in racenames:
                if racename == "-":
                    write(" -<br/>")
                else:
                    write(" <a href=\"%s#%s\">%s</a><br/>" % (
                        self.target, racename, racename))

            write("</div></li>\n")
        else:
            x = self.translate("Factions", "wesnoth-help")
            add_menu("races_menu", x)

            for row in self.unitgrid:
                for column in range(6):
                    hspan, vspan, un = row[column]
                    if not un: continue
                    if isinstance(un, helpers.GroupNode):
                        html = "../%s/%s.html" % (
                            self.isocode, self.campaign)
                        write(" <a href=\"%s#%s\">%s</a><br/>" % (
                            html, un.name, un.name))

            write("</div></li>\n")

        # Add entries for the races also to the navbar itself.
        if not self.is_era:
            class Entry: pass
            races = {}

            for uid, u in self.wesnoth.unit_lookup.items():
                if self.campaign != "units":
                    if self.campaign not in u.campaigns: continue
                if u.race:
                    racename = T(u.race, "plural_name")
                else:
                    racename = "none"
                runits = races.get(racename, [])
                runits.append(uid)
                races[racename] = runits

            racelist = sorted(races.keys())
            got_menu = False
            menuid = 0
            for r in racelist:
                if not r: continue
                if got_menu: write("</div></li>\n")
                add_menu("units_menu" + str(menuid), r, "unitmenu")
                menuid += 1
                got_menu = True
                c = self.campaign
                if c == "units": c = "mainline"
                write("<a href=\"%s#%s\">%s</a><br/>" % (
                    self.target, r, r))
                for uid in races[r]:
                    un = self.wesnoth.unit_lookup[uid]
                    if "mainline" in un.campaigns: addon = "mainline"
                    else: addon = self.addon
                    link = "../../%s/%s/%s.html" % (addon, self.isocode, uid)
                    name = self.wesnoth.get_unit_value(un,
                        "name", translation=self.translation.translate)
                    if not name:
                        error_message("Warning: Unit uid=" + uid + " has no name.\n")
                        name = uid
                    write("<a href=\"" + link + "\">" + name + "</a><br />")

            if got_menu: write("</div></li>\n")

        # Languages
        x = self.translate("Language", "wesnoth")
        add_menu("languages_menu", x)
        col = 0
        maxcol = len(langlist) - 1
        write("<table>")
        write("<tr>")
        for lang in langlist:
            col += 1
            write("<td>")
            labb = lang
            underscore = labb.find("_")
            if underscore > 0: labb = labb[:underscore]
            if self.addon == "mainline":
                write(" <a title=\"%s\" href=\"../%s/%s\">%s</a><br/>\n" % (
                    languages[lang], lang, self.target,
                        labb))
            else:
                write(" <a title=\"%s\" href=\"../%s/%s\">%s</a><br/>\n" % (
                    languages[lang], lang, "mainline.html",
                        labb))    
            write("</td>")
            if col % 5 == 0:
                if col < maxcol: write("</tr><tr>")

        write("</tr>")
        write("</table>")
        write("</div></li>\n")
        
        write("<li><div>&nbsp;</div></li>")
        write("<li><div>&nbsp;</div></li>")
        write('<li><a class="unitmenu" href="../../overview.html">Overview</a></li>')

        write("</ul>\n")

        write("</div>\n")

    def pic(self, u, x, recursion = 0):
        if recursion >= 4:
            error_message(
                "Warning: Cannot find image for unit %s(%s).\n" % (
                u.get_text_val("id"), x.name))
            return None, None
        image = self.wesnoth.get_unit_value(x, "image")
        portrait = x.get_all(tag="portrait")
        if not portrait:
            bu = self.wesnoth.get_base_unit(u)
            if bu:
                portrait = bu.get_all(tag="portrait")
        if portrait:
            portrait = portrait[0].get_text_val("image")
        if not image:
            if x.name == "female":
                baseunit = self.wesnoth.get_base_unit(u)
                if baseunit:
                    female = baseunit.get_all(tag="female")
                    return self.pic(u, female[0], recursion = recursion + 1)
                else:
                    return self.pic(u, u, recursion = recursion + 1)
            error_message(
                "Warning: Missing image for unit %s(%s).\n" % (
                u.get_text_val("id"), x.name))
            return None, None
        picname = image_collector.add_image(self.addon, image)
        image = os.path.join(pics_location, picname)
        if portrait:
            picname = image_collector.add_image(self.addon, portrait,
                no_tc=True)
            portrait = os.path.join(pics_location, picname)
        return image, portrait

    def get_abilities(self, u):
        anames = []
        already = {}
        for abilities in u.get_all(tag="abilities"):
            try: c = abilities.get_all()
            except AttributeError: c = []
            for ability in c:
                id = ability.get_text_val("id")
                if id in already: continue
                already[id] = True
                name = T(ability, "name")
                if not name: name = id
                if not name: name = ability.name
                anames.append(name)
        return anames

    def get_recursive_attacks(self, this_unit):

        def copy_attributes(copy_from, copy_to):
            for c in copy_from.data:
                if isinstance(c, wmlparser2.AttributeNode):
                    copy_to.data.append(c)

        # Use attacks of base_units as base, if we have one.
        base_unit = self.wesnoth.get_base_unit(this_unit)
        attacks = []
        if base_unit:
            attacks = copy.deepcopy(self.get_recursive_attacks(base_unit))

        base_attacks_count = len(attacks)
        for i, attack in enumerate(this_unit.get_all(tag="attack")):
            # Attack merging is order based.
            if i < base_attacks_count:
                copy_attributes(attack, attacks[i])
            else:
                attacks.append(attack)

        return attacks

    def write_units(self):
        def write(x): self.output.write(x)
        def _(x, c="wesnoth"): return self.translate(x, c)
        rows = self.unitgrid
        write("<table class=\"units\">\n")
        write("<colgroup>")
        for i in range(6):
            write("<col class=\"col%d\" />" % i)
        write("</colgroup>")

        pic = image_collector.add_image("general",
            "../../../images/misc/leader-crown.png", no_tc=True)
        crownimage = os.path.join(pics_location, pic)
        ms = None
        for row in range(len(rows)):
            write("<tr>\n")
            for column in range(6):
                hspan, vspan, un = rows[row][column]
                if vspan:
                    attributes = ""
                    if hspan == 1 and vspan == 1:
                        pass
                    elif hspan == 1:
                        attributes += " rowspan=\"%d\"" % vspan
                    elif vspan == 1:
                        attributes += " colspan=\"%d\"" % hspan

                    if un and isinstance(un, helpers.GroupNode):
                        # Find the current multiplayer side so we can show the
                        # little crowns..
                        ms = None
                        if self.is_era:
                            try:
                                eid, fid = un.data
                                era = self.wesnoth.era_lookup[eid]
                                if fid:
                                    ms = era.faction_lookup[fid]
                            except TypeError:
                                pass
                        racename = un.name
                        attributes += " class=\"raceheader\""
                        write("<td%s>" % attributes)
                        write("<a name=\"%s\">%s</a>" % (racename, racename))
                        write("</td>\n")
                    elif un:
                        u = un.unit
                        attributes += " class=\"unitcell\""
                        write("<td%s>" % attributes)

                        uid = u.get_text_val("id")
                        def uval(name):
                            return self.wesnoth.get_unit_value(u, name,
                                translation=self.translation.translate)
                        name = uval("name")
                        cost = uval("cost")
                        hp = uval("hitpoints")
                        mp = uval("movement")
                        xp = uval("experience")
                        level = uval("level")

                        crown = ""
                        if ms:
                            if un.id in ms.units:
                                crown = u" ♟"
                            if un.id in ms.is_leader:
                                crown = u" ♚"

                        uaddon = "mainline"
                        if "mainline" not in u.campaigns: uaddon = self.addon
                        link = "../../%s/%s/%s.html" % (uaddon, self.isocode, uid)
                        write("<div class=\"i\"><a href=\"%s\" title=\"id=%s\">%s</a>" % (
                            link, uid, u"i"))
                        write("</div>")
                        write("<div class=\"l\">L%s%s</div>" % (level, crown))
                        write("<a href=\"%s\">%s</a><br/>" % (link, name))

                        write('<div class="pic">')
                        image, portrait = self.pic(u, u)

                        write('<a href=\"%s\">' % link)

                        if crown == u" ♚":
                            write('<div style="background: url(%s)">' % image)
                            write('<img src="%s" alt="(image)" />' % crownimage)
                            write("</div>")
                        else:
                            write('<img src="%s" alt="(image)" />' % image)

                        write('</a>\n</div>\n')
                        write("<div class=\"attributes\">")
                        write("%s%s<br />" % (_("Cost: ", "wesnoth-help"), cost))
                        write("%s%s<br />" % (_("HP: "), hp))
                        write("%s%s<br />" % (_("MP: "), mp))
                        write("%s%s<br />" % (_("XP: "), xp))

                        # Write info about abilities.
                        anames = self.get_abilities(u)
                        if anames:
                            write("\n<div style=\"clear:both\">")
                            write(", ".join(anames))
                            write("</div>")

                        # Write info about attacks.
                        write("\n<div style=\"clear:both\">")
                        attacks = self.get_recursive_attacks(u)
                        for attack in attacks:

                            n = T(attack, "number")
                            x = T(attack, "damage")
                            x = "%s - %s" % (x, n)
                            write("%s " % x)

                            r = T(attack, "range")
                            t = T(attack, "type")
                            write("%s (%s)" % (_(r), _(t)))

                            s = []
                            specials = attack.get_all(tag="specials")
                            if specials:
                                for special in specials[0].get_all(tag=""):
                                    sname = T(special, "name")
                                    if sname:
                                        s.append(sname)
                                s = ", ".join(s)
                                if s: write(" (%s)" % s)
                            write("<br />")
                        write("</div>")

                        write("</div>")
                        write("</td>\n")
                    else:
                        write("<td class=\"empty\"></td>")
            write("</tr>\n")
        write("</table>\n")

    def write_units_tree(self, grouper, title):
        self.output.write(html_header % {"path": "../../",
            "title": title})

        n = self.analyze_units(grouper)
        self.write_navbar("units_tree")

        self.output.write("<div class=\"main\">")

        self.output.write("<h1>%s</h1>" % title)

        self.write_units()

        self.output.write('<div id="clear" style="clear:both;"></div>')
        self.output.write("</div>")

        self.output.write(html_footer % {
            "generation_note": "generated on " + time.ctime()})
        
        return n

    def write_unit_report(self, output, unit):
        def write(x): self.output.write(x)
        def _(x, c="wesnoth"): return self.translate(x, c)

        def find_attr(what, key):
            if unit.movetype:
                mtx = unit.movetype.get_all(tag=what)
                mty = None
                if mtx:
                    mty = mtx[0].get_text_val(key)
            x = unit.get_all(tag=what)
            y = None
            if x:
                y = x[0].get_text_val(key,
                translation=self.translation.translate)
            if y:
                return True, y
            if unit.movetype and mty != None:
                return False, mty
            return False, "-"

        def uval(name):
            return self.wesnoth.get_unit_value(unit, name,
                translation=self.translation.translate)

        # Write unit name, picture and description.
        uid = unit.get_text_val("id")
        uname = uval("name")
        display_name = uname

        self.output = output
        write(html_header % {"path": "../../",
            "title": display_name})
        self.write_navbar("unit_report")

        self.output.write("<div class=\"main\">")

        female = unit.get_all(tag="female")
        if female:
            fname = T(female[0], "name")
            if fname and fname != uname:
                display_name += "<br/>" + fname

        write('<div class="unit-columns">')

        write('<div class="unit-column-left">')

        write("<h1>%s</h1>\n" % display_name)

        write('<div class="pic">')
        if female:
            mimage, portrait = self.pic(unit, unit)
            fimage, fportrait = self.pic(unit, female[0])
            if not fimage: fimage = mimage
            if not fportrait: fportrait = portrait
            write('<img src="%s" alt="(image)" />\n' % mimage)
            write('<img src="%s" alt="(image)" />\n' % fimage)
        else:
            image, portrait = self.pic(unit, unit)
            write('<img src="%s" alt="(image)" />\n' % image)
        write('</div>\n')

        description = uval("description")

        # TODO: what is unit_description?
        if not description: description = uval("unit_description")
        if not description: description = "-"
        write("<p>%s</p>\n" % re.sub("\n", "\n<br />", description))

        # Base info.
        hp = uval("hitpoints")
        mp = uval("movement")
        xp = uval("experience")
        level = uval("level")
        alignment = uval("alignment")

        write("<h2>Information</h2>\n")
        write("<table class=\"unitinfo\">\n")
        write("<tr>\n")
        write("<td>%s" % _("Advances from: ", "wesnoth-help"))
        write("</td><td>\n")
        for pid in self.forest.get_parents(uid):
            punit = self.wesnoth.unit_lookup[pid]
            if "mainline" in unit.campaigns and "mainline" not in punit.campaigns:
                continue
            
            if "mainline" in unit.campaigns: addon = "mainline"
            else: addon = self.addon
            link = "../../%s/%s/%s.html" % (addon, self.isocode, pid)
            name = self.wesnoth.get_unit_value(punit, "name",
                translation=self.translation.translate)
            write("\n<a href=\"%s\">%s</a>" % (link, name))
        write("</td>\n")
        write("</tr><tr>\n")
        write("<td>%s" % _("Advances to: ", "wesnoth-help"))
        write("</td><td>\n")
        for cid in self.forest.get_children(uid):
            try:
                cunit = self.wesnoth.unit_lookup[cid]
                if "mainline" in cunit.campaigns: addon = "mainline"
                else: addon = self.addon
                link = "../../%s/%s/%s.html" % (addon, self.isocode, cid)
                if "mainline" in unit.campaigns and "mainline" not in cunit.campaigns:
                    continue
                name = self.wesnoth.get_unit_value(cunit, "name",
                    translation=self.translation.translate)
            except KeyError:
                error_message("Warning: Unit %s not found.\n" % cid)
                name = cid
                if "mainline" in unit.campaigns: continue
                link = self.target
            write("\n<a href=\"%s\">%s</a>" % (link, name))
        write("</td>\n")
        write("</tr>\n")

        for val, text in [
            ("cost", _("Cost: ", "wesnoth-help")),
            ("hitpoints", _("HP: ")),
            ("movement", _("Movement", "wesnoth-help") + ": "),
            ("experience", _("XP: ")),
            ("level", _("Level") + ": "),
            ("alignment", _("Alignment: ")),
            ("id", "ID")]:
            write("<tr>\n")
            write("<td>%s</td>" % text)
            x = uval(val)
            if val == "alignment": x = _(x)
            write("<td class=\"val\">%s</td>" % x)
            write("</tr>\n")

        # Write info about abilities.
        anames = self.get_abilities(unit)

        write("<tr>\n")
        write("<td>%s</td>" % _("Abilities: ", "wesnoth-help"))
        write("<td class=\"val\">" + (", ".join(anames)) + "</td>")
        write("</tr>\n")

        write("</table>\n")

        # Write info about attacks.
        write("<h2>" + _("unit help^Attacks", "wesnoth-help") + " <small>(damage - count)</small></h2> \n")
        write("<table class=\"unitinfo attacks\">\n")
        write('<colgroup><col class="col0" /><col class="col1" /><col class="col2" /><col class="col3" /><col class="col4" /></colgroup>')
        attacks = self.get_recursive_attacks(unit)
        for attack in attacks:
            write("<tr>")

            aid = attack.get_text_val("name")
            aname = T(attack, "description")

            icon = attack.get_text_val("icon")
            if not icon:
                icon = "attacks/%s.png" % aid

            image = image_collector.add_image_check(self.addon,
                icon, no_tc = True)
            if not image.ipath:
                error_message("Error: No attack icon '%s' found for '%s'.\n" % (
                    icon, uid))
                icon = os.path.join(pics_location, "unit$elves-wood$shaman.png")
            else:
                icon = os.path.join(pics_location, image.id_name)
            write("<td><img src=\"%s\" alt=\"(image)\"/></td>" % icon)

            write("<td><b>%s</b>" % aname)

            r = T(attack, "range")
            write("<br/>%s</td>" % _(r))

            n = attack.get_text_val("number")
            x = attack.get_text_val("damage")
            x = "%s - %s" % (x, n)
            write("<td><i>%s</i>" % x)

            t = T(attack, "type")
            write("<br/>%s</td>" % _(t))

            s = []
            specials = attack.get_all(tag="specials")

            if specials:
                for special in specials[0].get_all(tag=""):
                    sname = T(special, "name")
                    if sname:
                        s.append(sname)
                    else:
                        error_message(
                            "Warning: Weapon special %s has no name for %s.\n" % (
                            special.name, uid))
            s = "<br/>".join(s)
            write("<td>%s</td>" % s)


            write("</tr>")
        write("</table>\n")

        # Write info about resistances.
        resistances = [
            ("blade", "attacks/sword-human.png"),
            ("pierce", "attacks/spear.png"),
            ("impact", "attacks/club.png"),
            ("fire", "attacks/fireball.png"),
            ("cold", "attacks/iceball.png"),
            ("arcane", "attacks/faerie-fire.png")]

        write("<h2>%s</h2>\n" % _("Resistances: ").strip(" :"))
        write("<table class=\"unitinfo resistances\">\n")
        write('<colgroup><col class="col0" /><col class="col1" /><col class="col2" /><col class="col3" /><col class="col4" /><col class="col5" /><col class="col6" /><col class="col7" /></colgroup>')
        write("<tr>\n")

        write("</tr>\n")
        row = 0
        for rid, ricon in resistances:
            special, r = find_attr("resistance", rid)
            if r == "-": r = 100
            try: r = "<i>%d%%</i>" % (100 - int(r))
            except ValueError:
                error_message("Warning: Invalid resistance %s for %s.\n" % (
                    r, uid))
            rcell = "td"
            if special: rcell += ' class="special"'
            if row % 2 == 0: write("<tr>\n")
            else: write("<td></td>")
            picname = image_collector.add_image(self.addon, ricon,
                no_tc = True)
            icon = os.path.join(pics_location, picname)
            write("<td><img src=\"%s\" alt=\"(icon)\" /></td>\n" % (icon, ))
            write("<th>%s</th><td class=\"num\">%s</td>\n" % (_(rid), r))
            if row % 2 == 1: write("</tr>\n")
            row += 1
        write("</table>\n")

        # end left column
        write('</div>')
        write('<div class="unit-column-right">')

        if portrait:
            write('<div class="portrait">')
            if female:
                write('<img width="200" src="%s" alt="(portrait)" />\n' % portrait)
                write('<img width="200" src="%s" alt="(portrait)" />\n' % fportrait)
            else:
                write('<img width="200" src="%s" alt="(portrait)" />\n' % portrait)
            write('</div>\n')

        # Write info about movement costs and terrain defense.
        write("<h2>" + _("Terrain", "wesnoth-help") + "</h2>\n")
        write("<table class=\"unitinfo terrain\">\n")
        write('<colgroup><col class="col0" /><col class="col1" /><col class="col2" /><col class="col3" /><col class="col4" /></colgroup>')

        write("<tr><th colspan=\"2\"></th><th colspan=\"2\">%s</th></tr>\n" % (
            _("Movement Cost", "wesnoth-help")))
        write("<tr><th colspan=\"2\">%s</th><th></th><th class=\"numheader\">%s</th></tr>\n" % (
            _("Terrain", "wesnoth-help"), _("Defense", "wesnoth-help")))

        terrains = self.wesnoth.terrain_lookup
        terrainlist = []
        already = {}
        for tstring, t in terrains.items():
            tid = t.get_text_val("id")
            if tid in ["off_map", "off_map2", "fog", "shroud", "impassable",
                "void", "rails"]: continue
            if t.get_all(att="aliasof"): continue
            if tid in already: continue
            already[tid] = 1
            name = T(t, "name")
            ticon = t.get_text_val("symbol_image")
            if tid == "flat": ticon = "grass/green-symbol"
            elif tid == "frozen": ticon = "frozen/ice"
            elif tid == "unwalkable": ticon = "unwalkable/lava"
            elif tid == "village": ticon = "village/human-tile"
            elif tid == "forest": ticon = "forest/pine-tile"
            terrainlist.append((name, tid, ticon))
        terrainlist.sort()

        for tname, tid, ticon in terrainlist:
            not_from_race, c = find_attr("movement_costs", tid)

            ccell = "td"
            if c == "99": ccell += ' class="grayed"'
            dcell = "td"
            not_from_race, d = find_attr("defense", tid)

            if d == "-": d = 100
            try: d = "%d%%" % (100 - int(d))
            except ValueError:
                error_message("Warning: Invalid defense %s for %s.\n" % (
                    d, uid))

            write("<tr>\n")
            picname = image_collector.add_image(self.addon,
                "terrain/" + ticon + ".png", no_tc=True)
            icon = os.path.join(pics_location, picname)
            write("<td><img src=\"%s\"  alt=\"(icon)\" /></td>\n" % (icon, ))
            write("<td>%s</td><%s><i>%s</i></td><%s class=\"num\"><i>%s</i></td>\n" % (
                tname, ccell, c, dcell, d))
            write("</tr>\n")
        write("</table>\n")

        write('</div>') # right column

        write('</div>') # columns parent

        self.output.write('<div id="clear" style="clear:both;"></div>')
        write('</div>') # main

        self.output.write(html_footer % {
            "generation_note": "generated on " + time.ctime()})


def generate_campaign_report(addon, isocode, campaign, wesnoth):
    if campaign:
        cid = campaign.get_text_val("id")
    else:
        cid = "mainline"
    if not cid: cid = addon + "_" + campaign.get_text_val("define")

    print("campaign " + addon + " " + cid + " " + isocode)

    path = os.path.join(options.output, addon, isocode)
    if not os.path.isdir(path): os.mkdir(path)
    output = MyFile(os.path.join(path, "%s.html" % cid), "w")
    html = HTMLOutput(isocode, output, addon, cid, False, wesnoth)
    html.target = "%s.html" % cid
    grouper = GroupByRace(wesnoth, cid)

    if campaign:
        title = campaign.get_text_val("name", translation = html.translate)
    else:
        title = html.translate("Units", "wesnoth-help")
    if not title:
        title = cid

    n = html.write_units_tree(grouper, title)
    
    output.close()
    
    return n

def generate_era_report(addon, isocode, era, wesnoth):
    eid = era.get_text_val("id")
    
    print("era " + addon + " " + eid + " " + isocode)

    path = os.path.join(options.output, addon, isocode)
    if not os.path.isdir(path): os.mkdir(path)
    
    output = MyFile(os.path.join(path, "%s.html" % eid), "w")
    html = HTMLOutput(isocode, output, addon, eid, True, wesnoth)
    html.target = "%s.html" % eid
    
    grouper = GroupByFaction(wesnoth, eid)

    ename = era.get_text_val("name", translation = html.translate)
    n = html.write_units_tree(grouper, ename)
    
    output.close()
    
    return n

def generate_single_unit_reports(addon, isocode, wesnoth):
    
    path = os.path.join(options.output, addon, isocode)
    if not os.path.isdir(path): os.mkdir(path)

    html = HTMLOutput(isocode, None, addon, "units", False, wesnoth)
    grouper = GroupByNothing()
    html.analyze_units(grouper)

    for uid, unit in wesnoth.unit_lookup.items():
        if "mainline" in unit.campaigns and addon != "mainline": continue
        filename = os.path.join(path, "%s.html" % uid)

        # We probably can come up with something better.
        if os.path.exists(filename):
            age = time.time() - os.path.getmtime(filename)
            # was modified in the last 12 hours - we should be ok
            if age < 3600 * 12: continue

        output = MyFile(filename, "w")
        html.target = "%s.html" % uid
        html.write_unit_report(output, unit)
        output.close()
        
def html_postprocess_file(filename, isocode, batchlist):

    print("postprocessing " + filename)
    
    chtml = u""
    ehtml = u""
    
    cids = [[], []]
    for addon in batchlist:
        for campaign in addon.get("campaigns", []):
            if campaign["units"] == "?": continue
            if campaign["units"] <= 0: continue
            if addon["name"] == "mainline": lang = isocode
            else: lang = "en_US"
            c = addon["name"], campaign["id"], campaign["translations"].get(
                lang, campaign["name"]), lang
            if addon["name"] == "mainline":
                cids[0].append(c)
            else:
                cids[1].append(c)

    for i in range(2):
        
        campaigns = cids[i]
        campaigns.sort(key = lambda x: "A" if x[1] == "mainline" else "B" + x[2])

        for campaign in campaigns:
            addon, cname, campname, lang = campaign

            chtml += u" <a title=\"%s\" href=\"../../%s/%s/%s.html\">%s</a><br/>\n" % (
                campname, addon, lang, cname, campname)
        if i == 0 and cids[1]:
            chtml += u"-<br/>\n"

    eids = [[], []]
    for addon in batchlist:
        for era in addon.get("eras", []):
            if era["units"] == "?": continue
            if era["units"] <= 0: continue
            if addon["name"] == "mainline": lang = isocode
            else: lang = "en_US"
            e = addon["name"], era["id"], era["translations"].get(
                lang, era["name"]), lang
            if addon["name"] == "mainline":
                eids[0].append(e)
            else:
                eids[1].append(e)
            
            
    for i in range(2):
        eras = eids[i]
        eras.sort(key = lambda x: x[2])

        for era in eras:
            addon, eid, eraname, lang = era

            ehtml += u" <a title=\"%s\" href=\"../../%s/%s/%s.html\">%s</a><br/>" % (
                eraname, addon, lang, eid, eraname)
        if i == 0 and eids[1]:
            ehtml += u"-<br/>\n"
            
    f = open(filename, "r+b")
    html = f.read().decode("utf8")
    html = html.replace(u"PLACE CAMPAIGNS HERE\n", chtml)
    html = html.replace(u"PLACE ERAS HERE\n", ehtml)
    f.seek(0)
    f.write(html.encode("utf8"))
    f.close()

def html_postprocess_all(batchlist):
    for isocode, filename in all_written_html_files:
        html_postprocess_file(filename, isocode, batchlist)

def write_index(out_path):
    output = MyFile(os.path.join(out_path, "index.html"), "w")
    output.write("""
    <html><head>
    <meta http-equiv="refresh" content="0;url=mainline/en_US/mainline.html">
    </head>
    <body>
    <a href="mainline/en_US/mainline.html">Redirecting to Wesnoth units database...</a>
    </body>
    </html>
    """)
