from .helper import key_for_cypher, value_for_cypher
from .elements import (EqualClauseElement, NotEqualClauseElement, SubsetClauseElement,
                       NotSubsetClauseElement, NullClauseElement, NotNullClauseElement,
                       InClauseElement, NotInClauseElement, GtClauseElement, GteClauseElement,
                       LtClauseElement, LteClauseElement, RegexClauseElement)


class NodeAttribute(object):
    has_subquery = False
    acoustic = False

    def __init__(self, node, label):
        self.node = node
        self.label = label
        self.output_label = None

    def __hash__(self):
        return hash((self.node, self.label))

    def __str__(self):
        return '{}.{}'.format(self.node, self.label)

    def __repr__(self):
        return '<NodeAttribute \'{}\'>'.format(str(self))

    def for_cypher(self):
        return '{}.{}'.format(self.node.alias, key_for_cypher(self.label))

    def for_json(self):
        return [[x for x in self.node.for_json()] + [self.label], self.output_label]

    def for_filter(self):
        return self.for_cypher()

    def for_column(self):
        return self.for_cypher()

    def value_type(self):
        a_type = self.node.node_type
        if a_type == 'Speaker':
            for name, t in self.node.hierarchy.speaker_properties:
                if name == self.label:
                    if t == type(None) or t is None:
                        return None
                    return t
        elif a_type == 'Discourse':
            for name, t in self.node.hierarchy.discourse_properties:
                if name == self.label:
                    if t == type(None) or t is None:
                        return None
                    return t

        elif self.node.hierarchy.has_token_property(a_type, self.label):
            for name, t in self.node.hierarchy.token_properties[a_type]:
                if name == self.label:
                    if t == type(None) or t is None:
                        return None
                    return t
        elif self.node.hierarchy.has_type_property(a_type, self.label):
            for name, t in self.node.hierarchy.type_properties[a_type]:
                if name == self.label:
                    if t == type(None) or t is None:
                        return None
                    return t
        elif self.node.hierarchy.has_subannotation_property(a_type, self.label):
            for name, t in self.node.hierarchy.subannotation_properties[a_type]:
                if name == self.label:
                    if t == type(None) or t is None:
                        return None
                    return t
        raise ValueError('Property type "{}" not found for "{}".'.format(self.label, a_type))

    def coerce_value(self, value):
        if value is None:
            return value
        t = self.value_type()
        if t is None:
            return None
        if isinstance(value, list):
            return [t(x) for x in value]
        return t(value)

    @property
    def alias(self):
        """ Removes '`' from annotation, concatenates annotation alias and label"""
        return '{}_{}'.format(self.node.alias.replace('`', ''), self.label)

    @property
    def alias_for_cypher(self):
        return '`{}_{}`'.format(self.node.alias.replace('`', ''), self.label)

    def aliased_for_cypher(self):
        """
        creates cypher string to use in db

        Returns
        -------
        string
            string for db
        """
        return '{} AS {}'.format(self.for_cypher(), self.alias_for_cypher)

    def for_return(self):
        return self.for_cypher()

    def aliased_for_output(self):
        """
        creates cypher string for output

        Returns
        -------
        string
            string for output
        """
        return '{} AS {}'.format(self.for_return(), self.output_alias_for_cypher)

    @property
    def output_alias(self):
        """
        returns output_label if there is one
        return alias otherwise
        """
        if self.output_label is not None:
            return self.output_label
        return self.alias

    @property
    def output_alias_for_cypher(self):
        """
        returns output_label if t
here is one
        return alias otherwise
        """
        if self.output_label is not None:
            return self.output_label
        return self.alias_for_cypher

    @property
    def with_alias(self):
        """
        returns 
type_alias if there is one
        alias otherwise
        """
        return self.node.alias

    def column_name(self, label):
        """
        sets a column name to label
        """
        self.output_label = label
        return self

    def __eq__(self, other):
        if self.label == 'subset':
            return SubsetClauseElement(self, other)
        if other is None:
            return NullClauseElement(self, other)
        return EqualClauseElement(self, other)

    def __ne__(self, other):
        if self.label == 'subset':
            return NotSubsetClauseElement(self, other)
        if other is None:
            return NotNullClauseElement(self, other)
        return NotEqualClauseElement(self, other)

    def __gt__(self, other):
        return GtClauseElement(self, other)

    def __ge__(self, other):
        return GteClauseElement(self, other)

    def __lt__(self, other):
        return LtClauseElement(self, other)

    def __le__(self, other):
        return LteClauseElement(self, other)

    def in_(self, other):
        """
        Checks if the parameter other has a 'cypher' element
        executes the query if it does and appends the relevant results
        or appends parameter other

        Parameters
        ----------
        other : list
            attribute will be checked against elements in this list
        Returns
        -------
        string
            clause for asserting membership in a filter

        """
        if hasattr(other, 'cypher'):
            results = other.all()
            t = []
            for x in results:
                t.append(getattr(x, self.label))
        else:
            t = other
        return InClauseElement(self, t)

    def not_in_(self, other):
        """
        Checks if the parameter other has a 'cypher' element
        executes the query if it does and appends the relevant results
        or appends parameter other

        Parameters
        ----------
        other : list
            attribute will be checked against elements in this list
        Returns
        -------
        string
            clause for asserting non-membership in a filter
        """
        if hasattr(other, 'cypher'):
            results = other.all()
            t = []
            for x in results:
                t.append(getattr(x, self.label))
        else:
            t = other
        return NotInClauseElement(self, t)

    def regex(self, pattern):
        """ Returns a clause for filtering based on regular expressions."""
        return RegexClauseElement(self, pattern)

    @property
    def nodes(self):
        return self.node.nodes

    cache_alias = alias


class CollectionAttribute(NodeAttribute):
    collapsing = True
    acoustic = False
    filter_template = '{alias}.{property}'
    return_template = 'extract(n in {alias}|n.{property})'

    def __repr__(self):
        return '<CollectionAttribute \'{}\'>'.format(str(self))

    def value_type(self):
        n = self.node.collected_node
        a_type = n.node_type
        if a_type == 'Speaker':
            for name, t in self.node.hierarchy.speaker_properties:
                if name == self.label:
                    if t == type(None) or t is None:
                        return None
                    return t
        elif a_type == 'Discourse':
            for name, t in self.node.hierarchy.discourse_properties:
                if name == self.label:
                    if t == type(None) or t is None:
                        return None
                    return t

        elif self.node.hierarchy.has_token_property(a_type, self.label):
            for name, t in self.n