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


@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
