from ..interface import Contract, ContractNotRespected
from contracts.syntax import W, S, Keyword, add_contract, add_keyword
from pyparsing import alphanums, Word

__all__ = ['IsInstance', 'isinstance_contract']

class IsInstance(Contract):
    
    """ Checks that one of the superclasses have the specified name. """
     
    def __init__(self, name, where=None):
        Contract.__init__(self, where)
        self.name = name

    def check_contract(self, context, value, silent):
        class_name, bases_names = get_all_super_names(value) 
        
        options = bases_names + [class_name]
        if not self.name in options:
            
            msg = ('Failed check isinstance(%s) for type %r and superclasses %r.' 
                   % (self.name, class_name, bases_names))
            
            # Check it is just a case problem
            lc_name = self.name.lower()
            lc_options = [x.lower() for x in options]
            if lc_name in lc_options:
                msg += '\n***Note: this is just a lower/upper case error!***'

            raise ContractNotRespected(self, msg, value, context)

    def __str__(self):
        return 'isinstance(%s)' % self.name

    def __repr__(self):
        return 'IsInstance(%r)' % self.name

    @staticmethod
    def parse_action(s, loc, tokens):
        where = W(s, loc)
        name = tokens['name']
        return IsInstance(name, where)


def get_all_super_names(value):
    """ Returns name of class, list of names of supers """
    if hasattr(value, '__class__'):
        # old style class
        klass = value.__class__
        class_name = klass.__name__
        bases = get_oldstyle_bases(klass)
        bases_names = [x.__name__ for x in bases]
    else:
        # new style
        t = type(value)
        class_name = t.__name__
        bases_names = [b.__name__ for b in t.mro()]
    return class_name, bases_names

def get_oldstyle_bases(klass):
    todo = [klass]
    res = []
    while todo:
        x = todo.pop(0)
        res.append(x)
        for b in x.__bases__:
            if not b in res:
                todo.append(b)
    return res

Identifier = Word(alphanums + '_')

isinstance_contract = (Keyword('isinstance') - S('(') - Identifier('name') - S(')'))

add_contract(isinstance_contract.setParseAction(IsInstance.parse_action))
add_keyword('isinstance')
