import datetime
import decimal
import functools
import json as stdlib_json

try:
    # we keep simplejson for having dump functionality still
    # orjson doesn't support it
    import simplejson as sjson
except ImportError:
    sjson = stdlib_json

try:
    import orjson
    import orjson as json
except ImportError:
    json = stdlib_json


from rhodecode.lib.datelib import is_aware
from rhodecode.lib.str_utils import safe_str

try:
    import rhodecode.translation
except ImportError:
    rhodecode = None

__all__ = ['json']


def _obj_dump(obj):
    """
    Custom function for dumping objects to JSON, if obj has __json__ attribute
    or method defined it will be used for serialization

    :param obj:
    """

    if isinstance(obj, set):
        return list(obj)
    # See "Date Time String Format" in the ECMA-262 specification.
    # some code borrowed from django 1.4
    elif isinstance(obj, datetime.datetime):
        r = obj.isoformat()
        if isinstance(obj.microsecond, int):
            r = r[:23] + r[26:]
        if r.endswith('+00:00'):
            r = r[:-6] + 'Z'
        return r
    elif isinstance(obj, datetime.date):
        return obj.isoformat()
    elif isinstance(obj, decimal.Decimal):
        return str(obj)
    elif isinstance(obj, datetime.time):
        if is_aware(obj):
            raise TypeError("Time-zone aware times are not JSON serializable")
        r = obj.isoformat()
        if isinstance(obj.microsecond, int):
            r = r[:12]
        return r
    elif hasattr(obj, '__json__'):
        if callable(obj.__json__):
            return obj.__json__()
        else:
            return obj.__json__
    elif isinstance(obj, complex):
        return [obj.real, obj.imag]
    elif rhodecode and isinstance(obj, rhodecode.translation._LazyString):
        return obj.eval()
    else:
        raise TypeError(repr(obj) + " is not JSON serializable")


sjson.dumps = functools.partial(sjson.dumps, default=_obj_dump)
sjson.dump = functools.partial(sjson.dump, default=_obj_dump)

json.dumps = functools.partial(json.dumps, default=_obj_dump, option=orjson.OPT_NON_STR_KEYS)
json.dump = functools.partial(sjson.dump, default=_obj_dump)


def formatted_json(*args, **kwargs):
    # alias for formatted json
    opts = orjson.OPT_NON_STR_KEYS | orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS
    return functools.partial(json.dumps, option=opts)(*args, **kwargs)


def formatted_str_json(*args, **kwargs):
    opts = orjson.OPT_NON_STR_KEYS | orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS
    closure = functools.partial(json.dumps, option=opts)
    return safe_str(closure(*args, **kwargs))


def str_json(*args, **kwargs):
    closure = functools.partial(json.dumps)
    return safe_str(closure(*args, **kwargs))