import copy
from collections import OrderedDict


class Predicate:
    """
    A non-typed, non-lifted predicate (i.e. a proposition)
    """

    def __init__(self, name):
        self._name = name
        self.sign = 1  # whether true or the negation of the predicate

    @property
    def name(self):
        return self._name

    def is_grounded(self):
        return False

    def negate(self):
        """"
        Creates a copy of the predicate, negated
        """
        clone = copy.copy(self)
        clone.sign *= -1
        return clone

    def __str__(self):
        if self.sign < 0:
            return 'not ({})'.format(self.name)
        return self.name


class TypedPredicate(Predicate):
    """
    A typed, lifted predicate
    """

    def __init__(self, name, *types):
        super().__init__(name)
        self.grounding = list()
        self.param_types = list(types)

    def is_grounded(self):
        return len(self.grounding) > 0

    def add_typed_param(self, type):
        self.param_types.append(type)

    def ground_types(self):
        if not self.is_grounded():
            raise ValueError
        for object, type in zip(self.grounding, self.param_types):
            yield object, type

    # ground the predicate with actual objects!
    def __call__(self, *objects):
        """
        Returns a new predicate ground with the given objects
        """
        pred = copy.copy(self)
        pred.grounding = list(objects)
        if len(pred.grounding) != len(pred.param_types):
            raise ValueError("Expected {} objects. Got {}".format(len(pred.param_types), len(pred.grounding)))
        return pred

    # def __str__(self):
    #     raise NotImplementedError("Create a NamedParameterPredicate and provide parameter variable names to display")

    def __str__(self):
        var_names = [chr(ord('w') + i) for i in range(len(self.param_types))]
        s = '{} {}'.format(self.name,
                           ' '.join(['?{} - {}'.format(name, t) for name, t in
                                     zip(var_names, self.param_types)]))
        if self.sign < 0:
            return 'not ({})'.format(s)
        return s


# class for setting the variable names of each parameter!
class NamedParameterPredicate:
    def __init__(self, predicate: TypedPredicate, names=list()):
        self.predicate = predicate
        self.var_names = list()
        self.set_names(names)

    def set_names(self, names):
        if len(self.predicate.param_types) != len(names):
            raise ValueError("Expected {} objects. Got {}".format(len(self.predicate.param_types), len(names)))
        self.var_names = names

    def __str__(self):
        if len(self.var_names) == 0:
            return ValueError("Missing parameter variable names")

        # s = '{} {}'.format(self.predicate.name,
        #                    ' '.join(['?{} - {}'.format(name, t) for name, t in
        #                              zip(self.var_names, self.predicate.param_types)]))

        s = '{} {}'.format(self.predicate.name, ' '.join(['?{}'.format(name) for name in self.var_names]))

        if self.predicate.sign < 0:
            return 'not ({})'.format(s)
        return s
