from django.db.models import Q

from staff.lib.utils.reflection import classproperty
from staff.lib.utils.text import camel_to_underscore

from .utils import smart_q, memoized_method


ANONYMOUS = object()


def q_eq(q1, q2):
    if q1.connector != q2.connector:
        return False

    if q1.connector == q2.connector and q1.children == q1.children:
        return True

    if len(q1.children) != len(q2.children):
        return False

    eq = True
    for child1, child2 in zip(q1.children, q2.children):
        if isinstance(child1, Q) and isinstance(child2, Q):
            eq &= q_eq(child1, child2)
            if not eq:
                return False
    return eq


class Condition(object):
    """Q-wrapper"""

    def __init__(self, q_or_dict=None, none=False, all=False):
        if sum(1 for c in (q_or_dict, none, all) if bool(c)) != 1:
            raise TypeError('There can be only one!')

        self.cond = q_or_dict
        self.is_none = none
        self.is_all = all

    def __add__(self, other):
        if self.is_none or other.is_all:
            return other

        elif self.is_all or other.is_none:
            return self

        else:
            return Condition(self.q | other.q)

    def __and__(self, other):
        if self.is_none or other.is_all:
            return self

        elif self.is_all or other.is_none:
            return other

        else:
            return Condition(self.q & other.q)

    @property
    def q(self):
        return smart_q(self.cond)

    def get_queryset(self, model):
        if self.is_none:
            return model.objects.none()

        elif self.is_all:
            return model.objects.all()

        else:
            return model.objects.filter(self.q)

    def __str__(self):
        if self.is_none:
            detail = 'NONE'
        elif self.is_all:
            detail = 'ALL'
        else:
            detail = self.cond
        return 'Condition(%s)' % detail

    def __eq__(self, other):
        if (self.is_all, self.is_none) != (other.is_all, other.is_none):
            return False
        elif (self.is_all is True) and (other.is_all is True):
            return True
        elif (self.is_none is True) and (other.is_none is True):
            return True
        else:
            return q_eq(self.q, other.q)


class Role(object):
    READ = 'r'
    WRITE = 'w'

    fields = {}

    def __init__(self, user, model):
        """
        @type user: staff.django_intranet_stuff.models.Staff
        @type model: Achievement | GivenAchievement | NoneType
        """
        self.user = user
        self.model = model

    @memoized_method
    def __bool__(self):
        return self.is_applicable()

    def is_applicable(self):
        """
        @rtype: bool
        """
        raise NotImplementedError('Subclass must implement this')

    def is_field_readable(self, field):
        """
        @type field: str
        @rtype: bool
        """
        return self.check(field, self.READ)

    def is_field_writable(self, field):
        """
        @type field: str
        @rtype: bool
        """
        return self.check(field, self.WRITE)

    def check(self, field, perm):
        return (
            perm in self.fields.get(self.model.__class__, {}).get(field, '')
        )

    @classmethod
    def get_query(cls, user, model_class):
        """
        @rtype: Condition
        """
        raise NotImplementedError('Subclass must implement this')

    @classproperty
    def name(cls):
        return camel_to_underscore(cls.__name__)

    @classmethod
    def manages(cls, model_class):
        return model_class in cls.fields.keys()
