from json import JSONEncoder, loads
from time import mktime
from datetime import date, datetime
from google.appengine.ext import ndb
from google.appengine.ext.ndb import query


def to_epoch(value):
    """
    This is a view method to return the data in milli-seconds.

        :param value: Instance of `datetime.datetime`.
        :returns: `float` as the number of seconds since unix epoch.
    """
    return mktime(value.utctimetuple()) * 1000


def from_epoch(value):
    """
        :param value:
            Instance of `float` as the number of seconds since unix epoch.
        :returns:
            Instance of `datetime.datetime`.
    """
    return datetime.utcfromtimestamp(value / 1000)


def entity_to_dict(self, includes=None, excludes=None):
    """Encodes an `ndb.Model` to a `dict`. By default, only `ndb.Property`
    attributes are included in the result.

        :param include:
            List of strings keys of class attributes. Can be the name of the
            either a method or property.
        :param exclude:
            List of string keys to omit from the return value.
        :returns: Instance of `dict`.
        :raises: `ValueError` if any key in the `include` param doesn't exist.
    """
    value = ndb.Model.to_dict(self)
    # set the `id` of the entity's key by default..
    if self.key:
        value['key'] = self.key.urlsafe()
        value['id'] = self.key.id()
    if includes:
        for inc in includes:
            attr = getattr(self, inc, None)
            if attr is None:
                cls = self.__class__
                logging.warn('entity_to_dict cannot encode `%s`. Property is \
not defined on `%s.%s`.', inc, cls.__module__, cls.__name__)
                continue
            if callable(attr):
                value[inc] = attr()
            else:
                value[inc] = attr
    if excludes:
        # exclude items from the result dict, by popping the keys
        # from the dict..
        [value.pop(exc) for exc in excludes
            if exc in value]
    return value


def entity_from_dict(cls, value):
    """
        :param cls: `ndb.Model` subclass.
        :param value:
    """
    def _decode(_result, _value):
        """
        Deserializes `dict` values to `ndb.Property` values.
        """
        for key, val in _value.iteritems():
            prop = cls._properties.get(key)
            # logging.error('prop: %s', dir(prop))
            if prop is None:
                logging.warn('entity_from_dict cannot decode: `%s`. Property is \
not defined on: `%s.%s`.', key, cls.__module__, cls.__name__)
                continue
            if isinstance(prop, (ndb.DateTimeProperty, ndb.DateProperty,
                ndb.TimeProperty)):
                if prop._repeated:
                    val = [from_epoch(v) for v in val]
                else:
                    val = from_epoch(val)
            if isinstance(prop, ndb.BlobKeyProperty):
                if prop._repeated:
                    val = [ndb.BlobKey(urlsafe=v) for v in val]
                else:
                    val = ndb.BlobKey(urlsafe=val)
            if isinstance(prop, ndb.KeyProperty):
                if prop._repeated:
                    val = [ndb.Key(urlsafe=v) for v in val]
                else:
                    val = ndb.Key(urlsafe=val)
            if isinstance(prop, ndb.BlobProperty):
                pass
            _result[key] = val
        return _result
    return cls(**_decode({}, value))


def entity_from_json(cls, value):
    """
    Deserializes a json str to an instance of an `ndb.Model` subclass.

        :param cls: `ndb.Model` subclass.
        :param value:
    """
    value = loads(value, strict=False)
    if isinstance(value, list):
        result = [entity_from_dict(cls, v) for v in value]
    else:
        result = entity_from_dict(cls, value)
    return result