#!/usr/bin/env python
#encoding: utf8
"""
wmlunits -- tool to output information on all units in HTML

Run without arguments to see usage.
"""

# Makes things faster on 32-bit systems
try: import psyco; psyco.full()
except ImportError: pass

import sys, os, re, glob, shutil, copy, urllib2, gc

import wesnoth.wmldata as wmldata
import wesnoth.wmlparser as wmlparser
import wesnoth.wmltools as wmltools

sys.path.append(".")
import unit_tree.helpers as helpers
import unit_tree.animations as animations

html_header = '''
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel=stylesheet href=\"%(path)sstyle.css\" type=\"text/css\">
</head>
<body>'''.strip()

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

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.campaign == self.campaign

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

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

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]]
        faction = era.faction_lookup[group[1]]
        name = faction.get_text_val("name")
        name = name[name.rfind("=") + 1:]
        name = era.get_text_val("name") + " / " + name
        return name

class HTMLOutput:
    def __init__(self, isocode, output, campaign, wesnoth, verbose = False):
        self.isocode = isocode
        self.output = output
        self.campaign = campaign
        self.verbose = verbose
        self.target = "index.html"
        self.wesnoth = wesnoth
        self.forest = None

    def get_translation(self, textdomain, key):
        return self.wesnoth.parser.translations.get(textdomain, self.isocode,
            key, key)

    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

        # 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:
                            sys.stderr.write(
                                "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


        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()

        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 = u1.get_text_val("name")
            u2name = u2.get_text_val("name")
            return cmp(u1name, u2name)

        def grid_place(nodes, x):
            nodes.sort(by_name)
            for node in nodes:
                level = node.unit.level
                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

    def write_navbar(self):
        def write(x): self.output.write(x)
        def _(x): return self.get_translation("wesnoth", x)
        languages = find_languages()
        langlist = languages.keys()
        langlist.sort()

        write("""
        <div class="header">
        <a href="http://www.wesnoth.org">
        <img src="../wesnoth-logo.jpg" alt="Wesnoth logo"/>
        </a>
        </div>
        <div class="topnav">
        <a href="../index.html">Wesnoth Units database</a>
        </div>
        """)

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

        write("<tr>\n")

        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

        # Campaigns
        x = _("TitleScreen button^Campaign")
        write("<th>%s</th></tr><tr><td>" % x)
        cids = [[], []]
        for cid in self.wesnoth.campaign_lookup.keys():
            if cid in self.wesnoth.is_mainline_campaign:
                cids[0].append(cid)
            else:
                cids[1].append(cid)

        for i in range(2):
            cnames = cids[i]
            cnames.sort()
            for cname in cnames:
                lang = self.isocode
                if cname == "mainline":
                    campname = self.get_translation("wesnoth", "Multiplayer")
                    campabbrev = campname
                else:
                    campname = self.wesnoth.campaign_lookup[cname].get_text_val("name")
                    if not campname:
                        campname = cname
                    campabbrev = self.wesnoth.campaign_lookup[cname].get_text_val("abbrev")
                    if not campabbrev:
                        campabbrev = abbrev(campname)

                write(" <a title=\"%s\" href=\"../%s/%s.html\">%s</a><br/>\n" % (
                    campname, lang, cname, campabbrev))
            if i == 0 and cids[1]:
                write("-<br/>\n")
        write("</td>\n")
        write("</tr>\n")

        # Eras
        write("<tr>\n")
        x = _("Era")
        write("<th>%s</th></tr><tr><td>" % x)
        eids = [[], []]
        for eid in self.wesnoth.era_lookup.keys():
            if eid in self.wesnoth.is_mainline_era:
                eids[0].append(eid)
            else:
                eids[1].append(eid)
        for i in range(2):
            eranames = [(self.wesnoth.era_lookup[eid].get_text_val("name"),
                eid) for eid in eids[i]]
            eranames.sort()
            for eraname, eid in eranames:
                write(" <a title=\"%s\" href=\"../%s/%s.html\">%s</a><br/>" % (
                        eraname, lang, eid, abbrev(eraname)))
            if i == 0 and eids[1]:
                write("-<br/>\n")
        write("</td>\n")
        write("</tr>\n")

        # Races
        if self.campaign == "mainline":
            write("<tr>\n")
            x = _("Race")
            write("<th>%s</th></tr><tr><td>" % x)
            write("<a href=\"mainline.html\">%s</a><br/>\n" % (
                self.get_translation("wesnoth-editor", "all")))
            racenames = {}
            for u in self.wesnoth.unit_lookup.values():
                if u.campaign != self.campaign: continue
                race = u.race
                racename = race.get_text_val("plural_name")
                racenames[racename] = race.get_text_val("id")
            racenames = racenames.items()
            racenames.sort()

            for racename, rid in racenames:
                write(" <a href=\"mainline.html#%s\">%s</a><br/>" % (
                    racename, racename))
            write("</td>\n")
            write("</tr>\n")

        # Languages
        write("<tr>\n")
        x = _("Language")
        write("<th>%s</th></tr><tr><td>" % x)
        for lang in langlist:
            labb = lang
            underscore = labb.find("_")
            if underscore > 0: labb = labb[:underscore]
            write(" <a title=\"%s\" href=\"../%s/%s\">%s</a><br/>\n" % (
                languages[lang], lang, self.target,
                    labb))
        write("</td>\n")
        write("</tr>\n")

        write("</table>\n")

    def pic(self, u, x):
        image = self.wesnoth.get_unit_value(x, "image")
        if not image:
            if x.name == "female":
                baseunit = self.wesnoth.get_base_unit(u)
                if baseunit:
                    female = baseunit.get_first("female")
                    return self.pic(u, female)
            sys.stderr.write(
                "Warning: Missing image for unit %s(%s).\n" % (
                u.get_text_val("id"), x.name))
            return None
        picname = image_collector.add_image(u.campaign, image)
        image = os.path.join("../pics", picname)
        return image

    def get_abilities(self, u):
        anames = []
        already = {}
        for abilities in u.get_all("abilities"):
            try: c = abilities.children()
            except AttributeError: c = []
            for ability in c:
                id = ability.get_text_val("id")
                if id in already: continue
                already[id] = True
                name = ability.get_text_val("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.children():
                if isinstance(c, wmldata.DataText):
                    copy_to.set_text_val(c.name, c.data)

        # 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("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): return self.get_translation("wesnoth", x)
        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("mainline",
            "../../../images/misc/leader-crown.png")
        crownimage = os.path.join("../pics", 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
                        try:
                            eid, fid = un.data
                            era = self.wesnoth.era_lookup[eid]
                            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")
                        name = self.wesnoth.get_unit_value(u, "name")
                        cost = self.wesnoth.get_unit_value(u, "cost")
                        hp = self.wesnoth.get_unit_value(u, "hitpoints")
                        mp = self.wesnoth.get_unit_value(u, "movement")
                        xp = self.wesnoth.get_unit_value(u, "experience")
                        level = self.wesnoth.get_unit_value(u, "level")

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

                        link = "../%s/%s.html" % (self.isocode, uid)
                        write("<div class=\"i\"><a href=\"%s\" title=\"id=%s\">%s</a>" % (
                            link, uid, u"ℹ"))
                        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 = 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 />" % (
                            self.get_translation("wesnoth", "Cost: "), cost))
                        write("%s%s<br />" % (
                            self.get_translation("wesnoth", "HP: "), hp))
                        write("%s%s<br />" % (
                            self.get_translation("wesnoth", "MP: "), mp))
                        write("%s%s<br />" % (
                            self.get_translation("wesnoth", "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 = attack.get_text_val("number")
                            x = attack.get_text_val("damage")
                            x = "%s - %s" % (x, n)
                            write("%s " % x)

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

                            s = []
                            specials = attack.get_first("specials")
                            if specials:
                                for special in specials.children():
                                    sname = special.get_text_val("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" : "../"})

        self.analyze_units(grouper)
        self.write_navbar()

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

        self.write_units()

        self.output.write(html_footer)

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

        def find_attr(what, key):
            if unit.movetype:
                mtx = unit.movetype.get_first(what)
                mty = None
                if mtx: mty = mtx.get_text_val(key)
            x = unit.get_first(what)
            y = None
            if x: y = x.get_text_val(key)
            if y:
                return True, y
            if unit.movetype and mty != None:
                return False, mty
            return False, "-"

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

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

        female = unit.get_first("female")
        if female:
            fname = female.get_text_val("name")
            if fname and fname != uname:
                display_name += "<br/>" + fname

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

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

        description = self.wesnoth.get_unit_value(unit, "description")
        
        # TODO: what is unit_description?
        if not description: description = unit.get_text_val("unit_description")
        if not description: description = "-"
        write("<p>%s</p>\n" % __import__('re').sub("\n","\n<br />", description) )

        # Base info.
        hp = self.wesnoth.get_unit_value(unit, "hitpoints")
        mp = self.wesnoth.get_unit_value(unit, "movement")
        xp = self.wesnoth.get_unit_value(unit, "experience")
        level = self.wesnoth.get_unit_value(unit, "level")
        alignment = self.wesnoth.get_unit_value(unit, "alignment")

        write("<table class=\"unitinfo\">\n")
        write("<tr>\n")
        write("<th>%s" % _("Advances from: "))
        write("</th><td>\n")
        for pid in self.forest.get_parents(uid):
            punit = self.wesnoth.unit_lookup[pid]
            if unit.campaign == "mainline" and punit.campaign != "mainline":
                continue
            link = "../%s/%s.html" % (self.isocode, pid)
            name = self.wesnoth.get_unit_value(punit, "name")
            write("\n<a href=\"%s\">%s</a>" % (link, name))
        write("</td>\n")
        write("</tr><tr>\n")
        write("<th>%s" % self.get_translation("wesnoth", "Advances to: "))
        write("</th><td>\n")
        for cid in self.forest.get_children(uid):
            link = "../%s/%s.html" % (self.isocode, cid)
            try:
                cunit = self.wesnoth.unit_lookup[cid]
                if unit.campaign == "mainline" and cunit.campaign != "mainline":
                    continue
                name = self.wesnoth.get_unit_value(cunit, "name")
            except KeyError:
                sys.stderr.write("Warning: Unit %s not found.\n" % cid)
                name = cid
                if unit.campaign == "mainline": continue
            write("\n<a href=\"%s\">%s</a>" % (link, name))
        write("</td>\n")
        write("</tr>\n")

        for val, text in [
            ("cost", _("Cost: ")),
            ("hitpoints", _("HP: ")),
            ("movement", _("Movement") + ": "),
            ("experience", _("XP: ")),
            ("level", _("Level") + ": "),
            ("alignment", _("Alignment: ")),
            ("id", "ID")]:
            write("<tr>\n")
            write("<th>%s</th>" % text)
            x = self.wesnoth.get_unit_value(unit, val)
            if val == "alignment": x = self.get_translation("wesnoth", x)
            write("<td>%s</td>" % x)
            write("</tr>\n")
        
        # Write info about abilities.
        anames = self.get_abilities(unit)

        write("<tr>\n")
        write("<th>%s</th>" % _("Abilities: "))
        write("<td>" + (", ".join(anames)) + "</td>")
        write("</tr>\n")

        write("</table>\n")

        # Write info about attacks.
        write("<table class=\"unitinfo\">\n")

        attacks = self.get_recursive_attacks(unit)
        for attack in attacks:
            write("<tr>")

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

            picname, error = image_collector.add_image_check(unit.campaign, icon)
            if error:
                sys.stderr.write("Error: No attack icon '%s' found for '%s'.\n" % (
                    icon, uid))
            icon = os.path.join("../pics", picname)
            write("<td><img src=\"%s\" alt=\"(image)\"/></td>" % icon)

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

            t = attack.get_text_val("type")
            write("<br/>%s</td>" % self.get_translation("wesnoth", t))

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

            r = attack.get_text_val("range")
            write("<br/>%s</td>" % self.get_translation("wesnoth", r))

            s = []
            specials = attack.get_first("specials")
            if specials:
                for special in specials.children():
                    sname = special.get_text_val("name")
                    if sname:
                        s.append(sname)
                    else:
                        sys.stderr.write(
                            "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",
            "pierce",
            "impact",
            "fire",
            "cold",
            "arcane"]

        write("<table class=\"unitinfo\">\n")
        write("<tr>\n")
        write("<th colspan=\"2\">%s</th>\n" %
            self.get_translation("wesnoth", "Resistances: "))

        write("</tr>\n")
        for rid in resistances:
            special, r = find_attr("resistance", rid)
            if r == "-": r = 100
            try: r = "%d%%" % (100 - int(r))
            except ValueError:
                sys.stderr.write("Warning: Invalid resistance %s for %s.\n" % (
                    r, uid))
            rcell = "td"
            if special: rcell += ' class="special"'
            write("<tr>\n")
            write("<th>%s</th><td>%s</td>\n" % (
                self.get_translation("wesnoth", rid), r))
            write("</tr>\n")
        write("</table>\n")

        # Write info about movement costs and terrain defense.
        write("<table class=\"unitinfo\">\n")
        write("<tr>\n")
        write("<th>%s</th><th>%s</th><th>%s</th>\n" % (
            _("Terrain"), _("Movement Cost"), _("Defense") ))
        write("</tr>\n")

        terrains = self.wesnoth.terrain_lookup
        terrainlist = []
        for tstring, t in terrains.items():
            tid = t.get_text_val("id")
            if tid in ["off_map", "fog", "shroud"]: continue
            if t.get_first("aliasof"): continue
            name = t.get_text_val("name")
            terrainlist.append((name, tid))
        terrainlist.sort()

        for tname, tid 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:
                sys.stderr.write("Warning: Invalid defense %s for %s.\n" % (
                    d, uid))

            write("<tr>\n")
            write("<td>%s</td><%s>%s</td><%s>%s</td>\n" % (
                tname, ccell, c, dcell, d))
            write("</tr>\n")
        write("</table>\n")

        write(html_footer)

languages_found = {}
def find_languages():
    """
    Returns a dictionary mapping isocodes to languages.
    """
    global languages
    if languages_found: return languages_found
    parser = wmlparser.Parser(datadir)
    WML = wmldata.DataSub("WML")
    parser.parse_text("{languages}\n")
    parser.parse_top(WML)

    for locale in WML.get_all("locale"):
        isocode = locale.get_text_val("locale")
        name = locale.get_text_val("name")
        languages_found[isocode] = name
    return languages_found

class MyFile:
    """
    I don't understand why this is needed..
    """
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
    def write(self, x):
        x = x.encode("utf8")
        self.f.write(x)
    def close(self):
        self.f.close()

def generate_campaign_report(out_path, isocode, campaign, wesnoth):
    print "Generating report for %s_%s." % (isocode, campaign)
    path = os.path.join(out_path, isocode )
    if not os.path.isdir(path): os.mkdir(path)
    output = MyFile(os.path.join(path, "%s.html" % campaign), "w")
    html = HTMLOutput(isocode, output, campaign, wesnoth)
    html.target = "%s.html" % campaign
    grouper = GroupByRace(wesnoth, campaign)

    if campaign == "mainline":
        title = html.get_translation("wesnoth", "Multiplayer")
    else:
        title = wesnoth.campaign_lookup[campaign].get_text_val("name")
        if not title:
            title = campaign

    html.write_units_tree(grouper, title)

def generate_era_report(out_path, isocode, eid, wesnoth):
    print "Generating report for %s_%s." % (isocode, eid)
    path = os.path.join(out_path, isocode)
    if not os.path.isdir(path): os.mkdir(path)

    era = wesnoth.era_lookup[eid]
    ename = era.get_text_val("name")
    output = MyFile(os.path.join(path, "%s.html" % eid), "w")

    html = HTMLOutput(isocode, output, eid, wesnoth)
    html.target = "%s.html" % eid
    grouper = GroupByFaction(wesnoth, eid)
    title = ename
    html.write_units_tree(grouper, title)

def generate_single_unit_reports(out_path, isocode, wesnoth):

    odir = os.path.join(out_path, isocode)
    if not os.path.isdir(odir):
        os.mkdir(odir)

    html = HTMLOutput(isocode, None, "units", wesnoth)
    grouper = GroupByRace(wesnoth, None)
    html.analyze_units(grouper)

    for uid, unit in wesnoth.unit_lookup.items():
        output = MyFile(os.path.join(odir, "%s.html" % uid), "w")
        html.target = "%s.html" % uid
        html.write_unit_report(output, unit)

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=C/mainline.html">
    </head>
    <body>
    <a href="C/mainline.html">Redirecting to Wesnoth units database...</a>
    </body>
    </html>
    """)

def copy_images():
    if not options.nocopy:
        print "Copying files."
        image_collector.copy_and_color_images(options.output)
        shutil.copy2(os.path.join(datadir, "tools/unit_tree/style.css"), options.output)
        for grab in [
            "http://www.wesnoth.org/mw/skins/glamdrol/headerbg.jpg",
            "http://www.wesnoth.org/mw/skins/glamdrol/wesnoth-logo.jpg",
            "http://www.wesnoth.org/mw/skins/glamdrol/navbg.png"]:
            local = os.path.join(options.output, grab[grab.rfind("/") + 1:])
            if not os.path.exists(local):
                print "Fetching", grab
                url = urllib2.urlopen(grab)
                file(local, "w").write(url.read())

def output(isocode):
    """
    We output:
    * All mainline units sorted by race
    * Each mainline era's units sorted by faction
    * Each mainline campaign's units sorted by race
    * Each addon era's units sorted by faction
    * Each addon campaign's units sorted by race
    """
    print "WML parser language reset to %s." % isocode

    stuff = helpers.WesnothList(isocode, datadir, userdir, transdir)

    # Parse some stuff we may need.
    print "Parsing terrains ...",
    n = stuff.add_terrains()
    print "%d terrains found." % n

    print "Parsing mainline eras ...",
    n = stuff.add_mainline_eras()
    print "%d mainline eras found." % n
    stuff.is_mainline_era = {}
    for c in stuff.era_lookup.keys():
        stuff.is_mainline_era[c] = True

    print "Parsing mainline campaigns ...",
    n = stuff.add_mainline_campaigns()
    print "%d mainline campaigns found." % n
    stuff.is_mainline_campaign = {}
    for c in stuff.campaign_lookup.keys():
        stuff.is_mainline_campaign[c] = True

    # Parse all unit data
    # This reads in units.cfg, giving us all the mainline units (and races).
    print "Parsing mainline units ...",
    sys.stdout.flush()
    WML = stuff.parser.parse("{core/units.cfg}")
    n = stuff.add_units(WML, "mainline")
    print n, "mainline units found."

    # Now we read each mainline campaign in turn to get its units.
    cnames = stuff.campaign_lookup.keys()
    for cname in cnames:
        print "Parsing %s units ..." % cname,
        sys.stdout.flush()
        campaign = stuff.campaign_lookup[cname]
        define = campaign.get_text_val("define")
        WML = stuff.parser.parse("""
            #define %s\n#enddef
            {campaigns}""" % define,
            ignore_macros = lambda x: x.find("/scenarios") == -1)
        n = stuff.add_units(WML, cname)
        image_collector.add_binary_pathes_from_WML(cname, WML)
        print n, "units found."
    sys.stderr.flush()

    if userdir:
        print "Parsing addons ...",
        sys.stdout.flush()
        n = stuff.add_addons(image_collector)
        print "%d units found." % n

    # Now we read each addon campaign in turn to get its units.
    cnames = stuff.campaign_lookup.keys()
    for cname in cnames:
        if cname in stuff.is_mainline_campaign: continue
        campaign = stuff.campaign_lookup[cname]
        print "Parsing %s units ..." % cname,
        sys.stdout.flush()

        define = campaign.get_text_val("define")
        WML = stuff.parser.parse("""
            #define %s\n#enddef
            {~add-ons}""" % define,
            ignore_macros = lambda x: x.find("/scenarios") == -1)
        n = stuff.add_units(WML, cname)
        image_collector.add_binary_pathes_from_WML(cname, WML)
        print n, "units found."

    stuff.find_unit_factions()

    # Report generation
    if not os.path.isdir(options.output):
        os.mkdir(options.output)
    write_index(options.output)

    campaigns = stuff.campaign_lookup.keys()

    generate_campaign_report(options.output, isocode, "mainline", stuff)

    for campaign in campaigns:
        generate_campaign_report(options.output, isocode, campaign, stuff)

    for eid in stuff.era_lookup.keys():
        generate_era_report(options.output, isocode, eid, stuff)

    # Single unit reports.
    generate_single_unit_reports(options.output, isocode, stuff)

    return stuff

if __name__ == '__main__':
	
    # We change the process name to "wmlunits"
    try:
        import ctypes
        libc = ctypes.CDLL("libc.so.6")
        libc.prctl(15, "wmlunits", 0, 0, 0)
    except: # oh well...
        pass

    global options
    global image_collector
    import optparse
    
    gc.disable()

    op = optparse.OptionParser()
    op.add_option("-l", "--language", default = "all",
        help = "Specify a language.")
    op.add_option("-o", "--output",
        help = "Specify output directory.")
    op.add_option("-n", "--nocopy", action = "store_true",
        help = "No copying of files.")
    op.add_option("-d", "--datadir",
        help = "Specify Wesnoth's data to use. Default is <current directory>/data.")
    op.add_option("-u", "--userdir",
        help = "Specify user data dir to use, which is automatically scanned "+\
        "for addons. For example -u ~/.wesnoth/data")
    op.add_option("-t", "--transdir",
        help = "Specify the directory which has a po subfolder for translations. "+\
        "(Defaults to the current directory.)")
    options, args = op.parse_args()

    if not options.output:
        op.print_help()
        sys.exit(-1)

    options.output = os.path.expanduser(options.output)

    if not options.datadir:
        #wmltools.pop_to_top("wmlunits")
        datadir = os.getcwd() + "/data"
    else:
        datadir = options.datadir

    userdir = options.userdir
    if options.transdir:
        transdir = options.transdir
    else:
        transdir = os.getcwd()

    if options.language == "all":
        languages = find_languages().keys()
        languages.sort()
    else:
        languages = [options.language]

    image_collector = helpers.ImageCollector(datadir, userdir)

    once_without_translation = False
    for isocode in languages:
        wesnoth = output(isocode)
        if not once_without_translation:
            copy_images()
            once_without_translation = True
            for u in wesnoth.unit_lookup.values():
                r = u.race
                if r: r = r.get_text_val("id", "none")
                else: r = "none"
                # Store rid and id for each units so we have it easier
                u.rid = r
                u.id = u.get_text_val("id")

            # Write a list with all unit ids, just for fun.
            uids = wesnoth.unit_lookup.keys()
            def by_race(u1, u2):
                r = cmp(wesnoth.unit_lookup[u1].rid,
                    wesnoth.unit_lookup[u2].rid)
                if r == 0: r = cmp(u1, u2)
                return r
            uids.sort(by_race)
            race = None
            f = MyFile(os.path.join(options.output, "uids.html"), "w")
            f.write("<html><body>")
            for uid in uids:
                u = wesnoth.unit_lookup[uid]
                if u.rid != race:
                    if race != None: f.write("</ul>")
                    f.write("<p>%s</p>\n" % u.rid)
                    f.write("<ul>")
                    race = u.rid
                f.write("<li>%s</li>\n" % uid)
            f.write("</ul>")
            f.write("</body></html>")
            f.close()

            # Write animation statistics
            f = MyFile(os.path.join(options.output, "animations.html"), "w")
            animations.write_table(f, wesnoth)
            f.close()
        
        # Kill all references, this makes sure the reference counting
        # kicks in and throws away all data for the previous
        # translation - saving a bit of memory since otherwise two
        # translations would be kept at a time.
        wesnoth = None
        
        # We run a GC collection here, this ensures that also data
        # which still are referenced but not reachable (yes, yes,
        # this shouldn't happen) are freed.
        gc.collect()
