import json as _json
from typing import Any as _Any
from typing import Callable as _Callable
from typing import List as _List

from tamm.utils.json._normalization import get_denormalizer as _get_denormalizer
from tamm.utils.json._normalization import get_normalizer as _get_normalizer
from tamm.utils.json._utils import CompositeDefault as _CompositeDefault
from tamm.utils.json._utils import CompositeObjectHooks as _CompositeObjectHooks
from tamm.utils.json._utils import DeviceJSONHook as _DeviceJSONHook
from tamm.utils.json._utils import DTypeJSONHook as _DTypeJSONHook
from tamm.utils.json._utils import global_default as _global_default
from tamm.utils.json._utils import global_object_hook as _global_object_hook

# tamm object lifecycle (from disk to to live object to disk):
#
#        |------------|  |-----------------------|
# JSON-> | normalizer |->| loads (de-serializer) |-----> tamm live object ----->
#        |------------|  |-----------------------|
#
#        |-------------------|  |--------------|
# -----> | dump (serializer) |->| denormalizer | -----> JSON
#        |-------------------|  |--------------|
#
# * normalizer  : To apply any JSON transformation (e.g., key replacement) prior to
#                 deserialization. Normalization can implement auto-update of
#                 JSON generated by older tamm versions.
#
# * denormalizer: To apply any JSON transformation after serialization. Denormalization
#                 can update JSON for compatibility with older tamm versions.
#


def loads(
    json_str: str,
    *,
    cls=None,
    object_hook=None,
    parse_float=None,
    parse_int=None,
    parse_constant=None,
    object_pairs_hook=None,
    **kw,
) -> _Any:
    """
    .. _Python standard json lib: https://docs.python.org/3/library/json.html
    .. _json.loads(): https://docs.python.org/3/library/json.html#json.loads

    A drop-in replacement of `json.loads()`_ that recognizes tamm objects

    Args:
        json_str: A json string representing tamm object or any supported object
        cls: See `Python standard json lib`_ for details
        object_hook: See `Python standard json lib`_ for details
        parse_float: See `Python standard json lib`_ for details
        parse_int: See `Python standard json lib`_ for details
        parse_constant: See `Python standard json lib`_ for details
        object_pairs_hook: See `Python standard json lib`_ for details

    Returns:
        A tamm object or any object that can be deserialized
    """
    json_str = _get_normalizer()(json_str)  # normalization for syntax compatibility
    _object_hooks: _List[_Callable[..., _Any]] = [
        _DeviceJSONHook().object_hook,
        _DTypeJSONHook().object_hook,
        _global_object_hook,
    ]
    if object_hook is not None:
        _object_hooks.append(object_hook)

    _composite_hook = _CompositeObjectHooks(*_object_hooks)
    return _json.loads(
        json_str,
        cls=cls,
        object_hook=_composite_hook,
        parse_float=parse_float,
        parse_int=parse_int,
        parse_constant=parse_constant,
        object_pairs_hook=object_pairs_hook,
        **kw,
    )


def load(fp, **kwargs):
    """
    A drop-in replacement for :func:`json.load` that recognizes |tamm| objects.

    Args:
        fp: A file-like object (with a :meth:`read` method) containing the JSON
            document.
        **kwargs: Keyword arguments for :func:`json.loads`.

    Returns:
        The deserialized object.
    """
    return loads(fp.read(), **kwargs)


def dumps(
    obj: _Any,
    *,
    skipkeys=False,
    ensure_ascii=True,
    check_circular=True,
    allow_nan=True,
    cls=None,
    indent=None,
    separators=None,
    default=None,
    sort_keys=False,
    **kw,
) -> str:
    """
    .. _json.dumps(): https://docs.python.org/3/library/json.html#json.dumps

    A drop-in replacement of `json.dumps()`_ that recognizes tamm objects

    Args:
        obj: tamm object or any object
        skipkeys: See `Python standard json lib`_ for details
        ensure_ascii: See `Python standard json lib`_ for details
        check_circular: See `Python standard json lib`_ for details
        allow_nan: See `Python standard json lib`_ for details
        cls: See `Python standard json lib`_ for details
        indent: See `Python standard json lib`_ for details
        separators: `Python standard json lib`_ for details
        default: `Python standard json lib`_ for details
        sort_keys: See `Python standard json lib`_ for details

    Returns:
        A JSON string literal representing a serialized object

    """
    _defaults = [_global_default, _DTypeJSONHook().default, _DeviceJSONHook().default]
    if default is not None:
        _defaults.append(default)

    _composite_hook = _CompositeDefault(*_defaults)
    kwargs = {
        "skipkeys": skipkeys,
        "ensure_ascii": ensure_ascii,
        "check_circular": check_circular,
        "allow_nan": allow_nan,
        "cls": cls,
        "indent": indent,
        "separators": separators,
        "sort_keys": sort_keys,
    }
    kwargs.update(kw)
    output_json = _json.dumps(
        obj,
        default=_composite_hook,
        **kwargs,
    )
    return _get_denormalizer()(
        output_json,
        **kwargs,
    )


def dump(obj, fp, **kwargs):
    """
    A drop-in replacement for :func:`json.dump` that recognizes |tamm| objects.

    Args:
        fp: A file-like object (with a :meth:`write` method).
        **kwargs: Keyword arguments for :func:`json.dump`.
    """
    fp.write(dumps(obj, **kwargs))
