#!/usr/bin/python
from pyparsing import Word, alphas, operatorPrecedence, opAssoc, ParseException
from skdb import Package
import unittest
import functools
 
#thank you paul mcguire
#http://pyparsing.wikispaces.com/file/view/simpleBool.py
 
def check_package(package_name=None, data=None):
    if package_name == "True": return True
    elif package_name == "False": return False
    elif package_name == None: return False
    if data == None: raise ValueError, "check_package: data not given?"
    for package in data:
        if package.name == package_name:
            return True
    return False
 
class BoolOperand(object):
    def __init__(self,t):
        self.args = t[0][0::2]
        #self.vars = []
    def __str__(self):
        sep = " %s " % self.reprsymbol
        return "(" + sep.join(map(str,self.args)) + ")"
    @staticmethod
    def check_package(token):
        token = token[0]
        result = check_package(package_name=token, data=BoolOperand.vars)
        return result
    def check(self, name):
        if isinstance(name, bool): return name
        for package in self.vars:
            if package.name == name:
                return True
        return False
 
class BoolAnd(BoolOperand):
    reprsymbol = '&'
    def __nonzero__(self):
        for a in self.args:
            if a=="True": v = True
            elif a=="False": v = False
            elif isinstance(a,basestring):
                v = self.check(a)
            elif isinstance(a, bool):
                v = a
            elif isinstance(a, BoolOperand):
                v = a.__nonzero__()
            else: #you shouldn't get here
                v = self.check(a)
            if not v:
                return False
        return True
 
class BoolHave(BoolOperand):
    reprsymbol = 'have'
    def __init__(self,t):
        self.arg = t[0][1]
    def __str__(self):
        return "have " + str(self.arg)
    def __nonzero__(self):
        if isinstance(self.arg, basestring):
            v = self.check(self.arg)
            return v
        elif isinstance(self.arg, bool):
            return self.arg
        elif isinstance(a, BoolOperand):
            v = a.__nonzero__()
        else: return True
 
class BoolOr(BoolOperand):
    reprsymbol = '|'
    def __nonzero__(self):
        for a in self.args:
            if isinstance(a,basestring):
                v = self.check(a)
            elif isinstance(a, BoolOperand):
                v = a.__nonzero__()
            else:
                v = self.check(a)
            if v:
                return True
        return False
 
class BoolNot(BoolOperand):
    def __init__(self,t):
        self.arg = t[0][1]
    def __str__(self):
        return "~" + str(self.arg)
    def __nonzero__(self):
        if isinstance(self.arg,basestring):
            v = self.check(self.arg)
        elif isinstance(a, BoolOperand):
            v = a.__nonzero__()
        elif isinstance(self.arg, bool):
            return not self.arg
        else:
            v = self.check(self.arg)
        return not v
 
digits = "0123456789"
specials = "-_.+"
boolOperand = Word(alphas + digits + specials)
packageRef = boolOperand
 
boolExpr = operatorPrecedence( packageRef,
    [
    ("not", 1, opAssoc.RIGHT, BoolNot),
    ("have", 1, opAssoc.RIGHT, BoolHave),
    ("or",  2, opAssoc.LEFT,  BoolOr),
    ("and", 2, opAssoc.LEFT,  BoolAnd),
    ])
 
def parse(query, data=[]):
    BoolOperand.vars = data
    packageRef.setParseAction(BoolOperand.check_package)
    result = boolExpr.parseString(query, parseAll=True)
    return bool(result[0])
 
def parse2(query, data=[]):
    '''is exactly like parse() except it returns the result of the parsing (not the bool)'''
    BoolOperand.vars = data
    packageRef.setParseAction(BoolOperand.check_package)
    result = boolExpr.parseString(query, parseAll=True)
    #print "query: ", query
    #print "result: ", result
    #print "bool(result): ", bool(result[0])
    return result[0]
 
class TestParser(unittest.TestCase):
    def test_basic(self):
        packages = [Package("foo123"), Package("bar123"), Package("narf"), Package("foo123foo")]
 
        query_0 = "have foo123"
        self.assertTrue(parse(query_0, data=packages))
 
        query0 = "have foo1234"
        self.assertFalse(parse(query0, data=packages))
 
        query1 = "have foo123"
        self.assertTrue(parse(query1, data=packages))
 
        query2 = "foo123 and bar123"
        self.assertTrue(parse(query2, data=packages))
 
        query3 = "foo123 and bar123 and narf"
        self.assertTrue(parse(query3, data=packages))
 
        queryG = "narrrrf139401"
        self.assertFalse(parse(queryG, data=packages))
 
        query4 = "foo123 and nxarf123"
        self.assertFalse(parse(query4, data=packages))
 
        query5 = "foo123"
        self.assertFalse(parse2(query5, data=packages)==query5)
        self.assertTrue(parse(query5, data=packages))
 
        query6 = "foo1234"
        self.assertFalse(parse2(query6, data=packages)==query6)
        self.assertFalse(parse(query6, data=packages))
 
        query7 = "foo123foo"
        self.assertTrue(parse(query7, data=packages))
 
        query8 = "foo123foo1234"
        self.assertFalse(parse(query8, data=packages))
 
    def test_specials(self): #special characters like ._-
        packages=[]
 
        query9 = "bioperl-run"
        parse(query9, data=packages)
 
        query10 = "bioperl-1.0"
        parse(query10, data=packages)
 
        query_under = "some_package_name"
        parse(query_under, data=packages)
 
    def test_compound_statements(self):
        query1 = "True and True"
        self.assertTrue(parse2(query1))
 
    def test_errors(self):
        query1 = "and foo123"
        self.assertRaises(ParseException, parse, query1)
 
        query2 = "foo123 and"
        self.assertRaises(ParseException, parse, query2)
 
    def test_package_dependencies(self):
        fanpack = Package("fan")
        fanpack.dependencies = {}
        fanpack.dependencies["build"] = "(foo123 or foo123-2.0) and xyz"
 
        packages = [Package("foo123"), Package("xyz")]
        self.assertTrue(parse("(foo123 or foo123-2.0)", data=packages))
        self.assertTrue(parse(fanpack.dependencies["build"], data=packages))
 
if __name__ == "__main__":
    unittest.main()