from .._gae import ndb
from ..adapters.google import GoogleDatastore
from ..helpers.gae import NDBDecimalProperty
from .base import NoSQLDialect
from . import dialects, sqltype_for


@dialects.register_for(GoogleDatastore)
class GoogleDatastoreDialect(NoSQLDialect):
    FILTER_OPTIONS = {
        '=': lambda a, b: a == b,
        '>': lambda a, b: a > b,
        '<': lambda a, b: a < b,
        '<=': lambda a, b: a <= b,
        '>=': lambda a, b: a >= b,
        '!=': lambda a, b: a != b,
        'in': lambda a, b: a.IN(b)
    }

    @sqltype_for('string')
    def type_string(self):
        return (lambda **kwargs: ndb.StringProperty(**kwargs))

    @sqltype_for('boolean')
    def type_boolean(self):
        return ndb.BooleanProperty

    @sqltype_for('text')
    def type_text(self):
        return ndb.TextProperty

    @sqltype_for('json')
    def type_json(self):
        return self.types['text']

    @sqltype_for('password')
    def type_password(self):
        return ndb.StringProperty

    @sqltype_for('blob')
    def type_blob(self):
        return ndb.BlobProperty

    @sqltype_for('upload')
    def type_upload(self):
        return self.types['password']

    @sqltype_for('integer')
    def type_integer(self):
        return ndb.IntegerProperty

    @sqltype_for('bigint')
    def type_bigint(self):
        return self.types['integer']

    @sqltype_for('float')
    def type_float(self):
        return ndb.FloatProperty

    @sqltype_for('double')
    def type_double(self):
        return self.types['float']

    @sqltype_for('decimal')
    def type_decimal(self):
        return NDBDecimalProperty

    @sqltype_for('date')
    def type_date(self):
        return ndb.DateProperty

    @sqltype_for('time')
    def type_time(self):
        return ndb.TimeProperty

    @sqltype_for('datetime')
    def type_datetime(self):
        return ndb.DateTimeProperty

    @sqltype_for('id')
    def type_id(self):
        return None

    @sqltype_for('reference')
    def type_reference(self):
        return ndb.IntegerProperty

    @sqltype_for('list:integer')
    def type_list_integer(self):
        return (lambda **kwargs: ndb.IntegerProperty(
            repeated=True, default=None, **kwargs))

    @sqltype_for('list:string')
    def type_list_string(self):
        return (lambda **kwargs: ndb.StringProperty(
            repeated=True, default=None, **kwargs))

    @sqltype_for('list:reference')
    def type_list_reference(self):
        return (lambda **kwargs: ndb.IntegerProperty(
            repeated=True, default=None, **kwargs))

    def _and(self, first, second, query_env={}):
        first = self.expand(first, query_env=query_env)
        second = self.expand(second, query_env=query_env)
        # none means lack of query (true)
        if first == None:
            return second
        return ndb.AND(first, second)

    def _or(self, first, second, query_env={}):
        first = self.expand(first, query_env=query_env)
        second = self.expand(second, query_env=query_env)
        # none means lack of query (true)
        if first == None or second == None:
            return None
        return ndb.OR(first, second)

    def __gaef(self, first, op, second):
        name = first.name if first.name != 'id' else 'key'
        if name == 'key' and op in ('>', '!=') and second in (0, '0', None):
            return None
        field = getattr(first.table._tableobj, name)
        value = self.adapter.represent(second, first.type, first._tablename)
        return self.FILTER_OPTIONS[op](field, value)

    def eq(self, first, second=None, query_env={}):
        return self.__gaef(first, '=', second)

    def ne(self, first, second=None, query_env={}):
        return self.__gaef(first, '!=', second)

    def lt(self, first, second=None, query_env={}):
        return self.__gaef(first, '<', second)

    def lte(self, first, second=None, query_env={}):
        return self.__gaef(first, '<=', second)

    def gt(self, first, second=None, query_env={}):
        return self.__gaef(first, '>', second)

    def gte(self, first, second=None, query_env={}):
        return self.__gaef(first, '>=', second)

    def invert(self, first, query_env={}):
        return '-%s' % first.name

    def comma(self, first, second, query_env={}):
        return '%s,%s' % (first, second)

    def belongs(self, first, second, query_env={}):
        if not isinstance(second, (list, tuple, set)):
            raise SyntaxError("Not supported")
        if not isinstance(second, list):
            second = list(second)
        if len(second) == 0:
            # return a filter which will return a null set
            f = self.eq(first, 0)
            f.filter_all = True
            return f
        return self.__gaef(first, 'in', second)

    def contains(self, first, second, case_sensitive=True, query_env={}):
        # silently ignoring: GAE can only do case sensitive matches!
        if not first.type.startswith('list:'):
            raise SyntaxError("Not supported")
        return self.__gaef(first, '=', second)

    def _not(self, val, query_env={}):
        op, f, s = val.op, val.first, val.second
        if op in [self._or, self._and]:
            not_op = self._and if op == self._or else self._or
            rv = not_op(self._not(f), self._not(s))
        elif op == self.eq:
            rv = self.__gaef(f, '!=', s)
        elif op == self.ne:
            rv = self.__gaef(f, '=', s)
        elif op == self.lt:
            rv = self.__gaef(f, '>=', s)
        elif op == self.lte:
            rv = self.__gaef(f, '>', s)
        elif op == self.gt:
            rv = self.__gaef(f, '<=', s)
        elif op == self.gte:
            rv = self.__gaef(f, '<', s)
        else:
            # TODO the IN operator must be split into a sequence of
            # (field!=value) AND (field!=value) AND ...
            raise NotImplementedError
        return rv
