"""
This module parses the files generated by the ``dummy`` and ``biologic`` devices
within ``tomato-0.2``. As the ``dummy`` device has been mainly used for testing,
the below discusses the output of a ``biologic`` device.

Usage
`````
Available since ``yadg-4.0``.

.. autopydantic_model:: dgbowl_schemas.yadg.dataschema_5_1.filetype.Tomato_json

Schema
``````
.. code-block:: yaml

    datatree.DataTree:
      coords:
        uts:            !!float     # Unix timestamp
      data_vars:
        Ewe:            (uts)       # Potential of the working electrode
        Ece:            (uts)       # Potential of the counter electrode, if present
        I:              (uts)       # Instantaneous current
        technique:      (uts)       # Technique name
        loop number:    (uts)       # Loop number  (over techniques)
        cycle number:   (uts)       # Cycle number (within technique)
        index:          (uts)       # Technique index

Metadata
````````
No metadata is extracted.

Notes on file structure
```````````````````````
The files generated by the ``dummy`` driver do not contain the ``technique``, and all
values present in the json files are simply copied over assuming an uncertainty of 0.0.

For the ``biologic`` driver, each tomato data file contains the following four sections:

- ``technique`` section, describing the current technique,
- ``previous`` section, containing status information of the previous file,
- ``current`` section, containing status information of the current file,
- ``data`` section, containing the timesteps.

The reason why both ``previous`` and ``current`` are requires is that the device
status is recorded at the time of data polling, which means the values in ``current``
might be invalid (after the run has finished) or not in sync with the ``data`` (if
a technique change happened). However, ``previous`` may not be present in the first
data file of an experiment.

Uncertainties
`````````````
To determine the measurement errors, the values from BioLogic manual are used: for
measured voltages (:math:`E_{\\text{we}}` and :math:`E_{\\text{ce}}`) this corresponds
to a constant uncertainty of 0.004% of the applied E-range with a maximum of 75 uV,
while for currents (:math:`I`) this is a constant uncertainty of 0.0015% of the applied
I-range with a maximum of 0.76 uA.

.. codeauthor::
    Peter Kraus

"""

import json
import logging
import xarray as xr
from datatree import DataTree

from yadg import dgutils

logger = logging.getLogger(__name__)

I_ranges = {
    "1 A": 1e0,
    "100 mA": 1e-1,
    "10 mA": 1e-2,
    "1 mA": 1e-3,
    "100 uA": 1e-4,
    "10 uA": 1e-5,
    "1 uA": 1e-6,
    "100 pA": 1e-7,
}


def biologic_tomato_json(fn: str, jsdata: dict) -> DataTree:
    technique = jsdata["technique"]
    previous = jsdata.get("previous", None)
    current = jsdata["current"]

    if "uts" in technique:
        uts = technique["uts"]
        fulldate = True
    else:
        uts = 0
        fulldate = False

    uts += technique["start_time"]

    if previous is None:
        meta = current
    elif current["status"] == "STOP":
        meta = previous
    elif previous["elapsed_time"] > technique["start_time"]:
        meta = previous
    else:
        meta = current

    I_range = I_ranges[meta["I_range"]]
    E_range = meta["E_range"]["max"] - meta["E_range"]["min"]

    data_vars = {
        "loop number": [],
        "technique": [],
        "index": [],
        "time": [],
        "Ewe": [],
        "Ewe_std_err": [],
        "Ece": [],
        "Ece_std_err": [],
        "I": [],
        "I_std_err": [],
        "cycle": [],
    }

    for point in jsdata["data"]:
        for k, v in point.items():
            if k == "time":
                data_vars[k].append(uts + v)
            elif k in {"Ewe", "Ece"}:
                data_vars[k].append(v)
                data_vars[f"{k}_std_err"].append(max(E_range * 0.0015 / 100, 75e-6))
            elif k in {"I"}:
                data_vars[k].append(v)
                data_vars[f"{k}_std_err"].append(max(I_range * 0.004 / 100, 760e-12))
            elif k in {"cycle"}:
                data_vars[k].append(v)
            else:
                logger.cricital(f"parameter {k}: {v} not understood.")
        data_vars["loop number"].append(technique["loop_number"])
        data_vars["technique"].append(technique["name"])
        data_vars["index"].append(technique["index"])

    data_vars["cycle number"] = data_vars.pop("cycle")
    uts = data_vars.pop("time")

    data_vars = {k: v for k, v in data_vars.items() if len(v) > 0}

    for k in data_vars:
        if k in {"Ewe", "Ece", "I"}:
            data_vars[k] = (
                ["uts"],
                data_vars[k],
                {
                    "units": "A" if k == "I" else "V",
                    "ancillary_variables": f"{k}_std_err",
                },
            )
        elif k.endswith("_std_err"):
            data_vars[k] = (
                ["uts"],
                data_vars[k],
                {
                    "units": "A" if k == "I" else "V",
                    "standard_name": f"{k.replace('_std_err', '')} standard_error",
                },
            )
        else:
            data_vars[k] = (["uts"], data_vars[k])

    ds = xr.Dataset(data_vars, coords=dict(uts=uts))
    if not fulldate:
        ds.attrs["fulldate"] = False
    return DataTree(ds)


def dummy_tomato_json(fn: str, jsdata: dict) -> DataTree:
    data_vals = {}
    meta_vals = {}
    for vi, vals in enumerate(jsdata["data"]):
        vals["uts"] = vals.pop("time")
        devs = {}
        for k, v in vals.items():
            if k not in {"time", "address", "channel"}:
                devs[k] = 0.0
        dgutils.append_dicts(vals, devs, data_vals, meta_vals, fn, vi)
    return DataTree(dgutils.dicts_to_dataset(data_vals, meta_vals, fulldate=False))


def extract(
    *,
    fn: str,
    **kwargs: dict,
) -> DataTree:
    with open(fn, "r") as inf:
        jsdata = json.load(inf)

    if "technique" in jsdata:
        return biologic_tomato_json(fn, jsdata)
    else:
        return dummy_tomato_json(fn, jsdata)
