#!python
""" r_parse.py: parse a r_info.txt file and allow queries on its contents.

    Eventual goal: we ought to be able to generate statistical reports
    on just about anything monsters can do.  For example, we could generate
    a histogram of the maximum amount of damage doable by fire by 1000 randomly
    generated monsters per level.  This would allow us to notice whether some
    levels had too many or too few fire-breathers.
"""
import string
import pprint

class Monster:
    def set(self,name,value):
        setattr(self,name,value)
    def get(self,name,value=None):
        return self.__dict__.setdefault(name,value)
    def hasAttributes(self, textKeys, stringsWanted, maxMatchesNeeded):
        text = string.join([self.get(name,"") for name in textKeys])
        return foundEnoughMatches(stringsWanted,text,maxMatchesNeeded)
    def __repr__(self):
        return "<Monster ~"+self.name+"~, index #"+str(self.number)+">"

class AngbandTextRecord:
    def __init__(self, parsed):
        self.contents = {}
        for description in parsed:
            id, description = description[0], description[1:]
            self.contents[id] = description

    def parse(self,textblock):
        lines = string.split(textblock,"\n")
        monster = Monster()
        for line in lines:
            line = string.strip(line) # remove whitespace
            if line == "" or line[0] == '#': continue # skip comments
            assert len(string.split(line,":",1)) == 2, (line +
                                                        "\n is an invalidly " +
                                                        "formed line in the " +
                                                        "text block:\n" +
                                                        textblock
                                                        )
            id, rest = string.split(line,":",1)
            if not id in self.contents.keys(): continue
            syntax = self.contents[id]
            if len(syntax) == 0:
                text = monster.get(id,rest)
                if text is not rest:
                    monster.set(id,text+" "+rest)
            else:
                components = string.split(rest,":",len(syntax)-1)
                for name in syntax:
                    assert monster.get(name,None) is None, ("I assume that no"
                                                            + " definitions"
                                                            + " are repeated.")
                    assert len(components) > 0, "Empty "+name+" in line: '"+line+"'"
                    monster.set(name, components[0])
                    del components[0]
                assert components == [], "Should use up all input, " + \
                       "instead left " + str(components)
        return monster


#################### add variants here ########################
# The shape of the list will determine how I will parse the r_info file.
parsers = {
  "pern": AngbandTextRecord(
      [
         ['G', "char","attr"],
         ['I', "speed","hp","i3","i4","i5"],
         ['W', "w1","w2","corpse_mass","w4"],
         ['E', "weapons","armors","shields","rings","hats","shoes"],
         ['O', "treasure_drop","combat_drop","magic_drop","tool_drop"],
         ['B', ], # attacks
         ['F', ], # flags
         ['S', ], # spells
         ['D', ]  # description
      ] ),
 }

def foundEnoughMatches(list,search,desiredHits):
    assert desiredHits > 0
    maxMisses = len(list) - desiredHits
    hits = 0
    for match in list:
        if string.find(search,match) == -1:
            maxMisses = maxMisses-1
            if maxMisses < 0: return 0
        else: hits = hits+1
    return hits

class MonsterSet:
    def __init__(self):
        self.monsters = {}
    def __repr__(self):
        return repr(self.monsters.values())
    def __str__(self):
        return pprint.pformat(self.monsters.values())
    def __setitem__(self,name,data):
        self.monsters[name] = data
    def __getitem__(self,goalName):
        keys = [name for name in self.names()
                     if name.find(goalName) != -1]
        if len(keys) == 1: return self.monsters[keys[0]]
        result = []
        for name in keys:
            result.append(self.monsters[name])
        return result
    def __len__(self): return len(self.monsters)
    def __sub__(self,other):
        result = self.copy()
        for name in other.names():
            del result.monsters[name]
        return result
    def __add__(self,other):
        result = self.copy()
        result.update(other)
        return result
    def copy(self):
        result = MonsterSet().update(self)
        return result
    def update(self, other):
        self.monsters.update(other.monsters)
        return self
    def names(self):   return self.monsters.keys()

    def queryName(self,goalName):
        keys = [name for name in self.names() if name.find(goalName) != -1]
        result = MonsterSet()
        for name in keys:
            result[name] = self.monsters[name]
        return result
    
    def queryFlags(self,attributes,characteristics,goal):
        """ Return a MonsterSet of the monsters which have at least
            /goal/ out of the /characteristics/ in their /attributes/.
            If /goal/ is negative, return only the monsters which don't have
            enough of the attributes.
        """
        if goal == 0: return self
        monsters = MonsterSet()
        for name in self.monsters.keys():
            foundEnough = self.monsters[name].hasAttributes(attributes,
                                                            characteristics,
                                                            abs(goal))
            if (goal > 0 and foundEnough) or (goal < 0 and not foundEnough):
                monsters[name] = self.monsters[name]
        return monsters
    
    def queryValues(self, attribute, testTuple):
        result = MonsterSet()
        test = testTuple[0]; value = testTuple[1]
        for name in self.names():
            if test(self.monsters[name].get(attribute,"0"),value):
                result[name] = self.monsters[name]
        return result

class RaceFile(MonsterSet):
    def __init__(self,raceFile,parser):
        self.filename = raceFile
        self.parser = parser
        self.monsters = {}.copy()
        info = open(self.filename).read()
        info = info[string.index(info,"N:")+2:] # strip the opening comments
        info = string.split(info,"\nN:")
        for race in info:
            name,description = string.split(race,'\n',1)
            self.monsters[name] = parser.parse(description)
            mNumber, mName = string.split(name,":",1)
            self.monsters[name].name = mName
            self.monsters[name].number = int(mNumber)

def GT(x,y):
    return compare(GT,x,y)
def GE(x,y):
    return compare(GE,x,y)
def LT(x,y):
    return compare(LT,x,y)
def LE(x,y):
    return compare(LE,x,y)
def EQ(x,y):
    return compare(EQ,x,y)

def compare(test, text,value):
    if type(value) == type(""):
        conversion = str
    else: conversion = int
    assert type(conversion("0")) == type(value)

    result = cmp(conversion(text),value)
    
    if result < 0:
        return test is LE or test is LT
    elif result > 0:
        return test is GE or test is GT
    else:
        return test is EQ or test is GE or test is LE
