#!/usr/bin/env python
"""
wmltest -- tool to test the integrity and meaning of WML.

Use --help to see usage.
"""
#TODO:
#-Write function to check dependencies
#List of dependencies to check:
#-defense,movement_costs: keys should be valid terrain
#-resistance: keys should be valid damage types
#-effect: valid keys vary depending on name
#-[attack] type: value should be valid damage types
#-[filter_attack]'s keys' values

import wesnoth.wmldata as wmldata
import wesnoth.wmlparser as wmlparser
import wesnoth.wmlgrammar as wmlgrammar
import re

def print_indent(string, depth, char=' '):
    print "%s%s" % (depth * char, string)

class Tester:
    """
    The Tester class, this walks the WML tree and checks whether stuff has meaning. (among other things)
    """
    def __init__(self, wmltree, verbosity):
        self.wmltree = wmltree
        self.verbosity = verbosity
        self.grammar = wmlgrammar.Grammar().grammar()
    def test(self, tag=None, depth=0):
        """Tests whether things have meaning, walks the wml tree."""
        if not tag:
            tag = self.wmltree
        if self.verbosity > 2:
            print_indent(tag.name, depth, ' ')
        for item in tag.data:
            if isinstance(item, wmldata.DataSub):
                if item.name in self.grammar[tag.name][0]:
                    self.test(item, depth + 1)
                else:
                    found = False
                    for d in filter(lambda x:isinstance(x,dict),self.grammar[tag.name][0]):
                        # We only check the first key in the dict, as it should only have one
                        key = d.keys()[0]
                        if isinstance(key, str) and key == item.name \
                        or isinstance(key, re._pattern_type) and key.search(item.name):
                            found = True
                            item.name = d[key]
                            self.test(item, depth + 1)
                            break # Don't recurse into the catch-all
                    if not found:
                        print_indent("[%s] (%s:%d) is meaningless in [%s] (%s:%d)" % (item.name, item.file, item.line, tag.name, tag.file, tag.line), depth + 1, '*')
            elif isinstance(item, wmldata.DataText):
                if item.translatable:
                    underscore = " _ "
                else:
                    underscore = ""
                if item.name in self.grammar[tag.name][1] or any(map(lambda x:(bool)(x.search(item.name)),filter(lambda x:isinstance(x,re._pattern_type),self.grammar[tag.name][1]))):
                    if self.verbosity > 2:
                        print_indent("%s=%s\"%s\"" % (item.name, underscore, item.data), depth + 1, ' ')
                else:
                    print_indent("%s=%s\"%s\" (%s:%d) is meaningless in [%s] (%s:%d)" % (item.name, underscore, item.data, item.file, item.line, tag.name, tag.file, tag.line), depth + 1, '*')
            else:
                raise Exception( "Wmlparser gave us an object of class %s" % (item.__class__,) )


if __name__ == '__main__':
    import optparse, subprocess, os, codecs, sys

    # Ugly hack to force the output of UTF-8.
    # This prevents us from crashing when we're being verbose
    #  and encounter a non-ascii character.
    sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

    op = optparse.OptionParser()
    op.set_usage("Usage: %prog [options] [filename]")
    op.add_option("-p", "--path",
        help = "Specify Wesnoth's data dir.",
        dest = "path")
    op.add_option("-u", "--userpath",
        help = "Specify user data dir..",
        dest = "userpath")
    op.add_option("-v", "--verbose",
        action = "count",
        dest = "verbose",
        help = "Increase verbosity, 4 is the maximum.")
    op.add_option("-D", "--define",
        action = "append",
        dest = "defines",
        default = [],
        help = "Define (empty) preprocessor macros, for campaign/multiplayer inclusion.")
    (options, args) = op.parse_args()

    if not options.path:
        try:
            p = subprocess.Popen(["wesnoth", "--path"], stdout = subprocess.PIPE)
            path = p.stdout.read().strip()
            options.path = os.path.join(path, "data")
            sys.stderr.write("No Wesnoth path given.\nAutomatically found '%s'\n" % (options.path, ) )
        except OSError:
            options.path = '.'
            sys.stderr.write("Could not determine Wesnoth path.\nAssuming '%s'\n" % (options.path, ) )

    if len(args) < 1:
        args.append('%s/_main.cfg' % options.path)

    if options.verbose > 1:
        print "Options: %s\nArgs: %s\n"% (options, args)

    parser = wmlparser.Parser(options.path, options.userpath)

    if options.verbose > 3:
        parser.verbose = True

    if options.userpath:
        parser.parse_text("{~campaigns}")
    for file in args:
        parser.parse_file(file)
    for macro in options.defines:
        parser.parse_text("#define %s \n#enddef" % (macro, ) )

    data = wmldata.DataSub("WML")
    parser.parse_top(data)

    tester = Tester(data, options.verbose)
    tester.test()

# vim: tabstop=4: shiftwidth=4: expandtab: softtabstop=4: autoindent:
