"""Adapters: one callable per JSON-recovery library, all with the same contract.

Contract (MALFORMED-300 grading spec):
  callable(text) -> the recovered Python value
                 -> return None, or raise, to REFUSE.
No case in the corpus has a top-level expected value of null, so None is an
unambiguous refusal signal.

Rules this file holds itself to, so the comparison is fair:
  * every library is called through its OWN documented entry point, with its
    OWN documented "be lenient" switch where one exists;
  * nothing is pre-cleaned before it reaches the library -- each one sees the
    exact same raw string;
  * the only post-processing is normalisation of library-specific container
    and sentinel types into plain dict/list/None, so that value comparison is
    about VALUES and not about which class the library wrapped them in;
  * a per-case 10s alarm stops one pathological input from hanging a run; a
    timeout is recorded as a refusal, which is the kindest reading of it.

CC0-1.0. Reproduce with: python3 run_leaderboard.py
"""

import json
import signal

CASE_TIMEOUT_SEC = 10


class _Timeout(Exception):
    pass


def _alarm(signum, frame):
    raise _Timeout("case exceeded %ds" % CASE_TIMEOUT_SEC)


def _guard(fn):
    def wrapped(text):
        signal.signal(signal.SIGALRM, _alarm)
        signal.alarm(CASE_TIMEOUT_SEC)
        try:
            return _plain(fn(text))
        finally:
            signal.alarm(0)
    wrapped.__name__ = fn.__name__
    return wrapped


def _plain(v):
    """Library-specific containers/sentinels -> plain json types."""
    if v is None or isinstance(v, (bool, int, float, str)):
        return v
    if isinstance(v, dict):
        return {str(k): _plain(x) for k, x in v.items()}
    if isinstance(v, (list, tuple)):
        return [_plain(x) for x in v]
    name = type(v).__name__
    if name in ("undefined", "_undefined", "nan", "JSONundefined"):
        return None
    try:
        return json.loads(json.dumps(v))
    except Exception:
        return str(v)


# --- the control ----------------------------------------------------------

@_guard
def stdlib_json(text):
    return json.loads(text)


# --- third-party libraries ------------------------------------------------

@_guard
def json_repair_(text):
    import json_repair
    # documented entry point for "give me the object back"
    return json_repair.repair_json(text, return_objects=True)


@_guard
def dirtyjson_(text):
    import dirtyjson
    return dirtyjson.loads(text)


@_guard
def demjson3_(text):
    import demjson3
    # strict=False is demjson3's documented lenient mode
    return demjson3.decode(text, strict=False)


@_guard
def json5_(text):
    import json5
    return json5.loads(text)


@_guard
def pyjson5_(text):
    import pyjson5
    return pyjson5.decode(text)


@_guard
def hjson_(text):
    import hjson
    return hjson.loads(text)


@_guard
def commentjson_(text):
    import commentjson
    # documented purpose: JSON with comments
    return commentjson.loads(text)


@_guard
def simplejson_(text):
    import simplejson
    # not a repair library: the widely-installed drop-in replacement for json,
    # scored so the table shows what "just swap the parser" actually buys
    return simplejson.loads(text)


@_guard
def partial_json_parser_(text):
    from partial_json_parser import loads
    # documented purpose: parse incomplete JSON as it streams in
    return loads(text)


# --- Toolkit Labs' own tool, scored by the same rules ---------------------

@_guard
def jsonshim_(text):
    import jsonshim
    return jsonshim.loads(text)


REGISTRY = {
    "json.loads (stdlib control)": ("stdlib_json", None),
    "json-repair": ("json_repair_", "json_repair"),
    "dirtyjson": ("dirtyjson_", "dirtyjson"),
    "demjson3": ("demjson3_", "demjson3"),
    "json5": ("json5_", "json5"),
    "pyjson5": ("pyjson5_", "pyjson5"),
    "hjson": ("hjson_", "hjson"),
    "commentjson": ("commentjson_", "commentjson"),
    "simplejson": ("simplejson_", "simplejson"),
    "partial-json-parser": ("partial_json_parser_", "partial_json_parser"),
    "jsonshim (Toolkit Labs)": ("jsonshim_", None),
}
