"""Write selected RINEX navigation file observations

Description:
------------


"""
# Standard library imports
from collections import namedtuple

# External library imports
import numpy as np

# Midgard imports
from midgard.dev import plugins
from midgard.writers._writers import get_existing_fields, get_field, get_header
from midgard.math.unit import Unit

# Where imports
import where
from where.lib import config
from where.lib import util

WriterField = namedtuple(
    "WriterField", ["name", "field", "attrs", "dtype", "format", "width", "header", "unit", "description"]
)
WriterField.__new__.__defaults__ = (None,) * len(WriterField._fields)
WriterField.__doc__ = """A convenience class for defining a output field for the writer

    Args:
        name  (str):             Unique field name
        field (str):             Dataset field name
        attrs (Tuple[str]):      Field attributes
        dtype (Numpy dtype):     Type of field
        format (str):            Format string
        width (int):             Width of header information
        header (str):            Header information
        unit (str):              Unit of field
        description (str):       Description of field
    """

FIELDS = (
    WriterField(
        "date",
        "date",
        (),
        object,
        "%21s",
        19,
        "DATE",
        "YYYY/MM/DD hh:mm:ss",
        "Date in format year, month, day and hour, minute and second",
    ),
    WriterField("mjd", "time", ("gps", "mjd"), float, "%14.6f", 14, "MJD", "", "Modified Julian Day"),
    WriterField("gpsweek", "time", ("gps", "gps_ws", "week"), int, "%5d", 5, "WEEK", "", "GPS week"),
    WriterField(
        "gpssec", "time", ("gps", "gps_ws", "seconds"), float, "%11.3f", 11, "GPSSEC", "second", "GPS seconds"
    ),
    WriterField("satellite", "satellite", (), object, "%5s", 5, "SAT", "", "Satellite number"),
    WriterField(
        "iode",
        "iode",
        (),
        float,
        "%6d",
        6,
        "IODE",
        "",
        f"Ephemeris issue of data indicates changes to the broadcast ephemeris:\n"
        f"""
{'': >38}- GPS:     Ephemeris issue of data (IODE), which is set equal to IODC
{'': >38}- Galileo: Issue of Data of the NAV batch (IODnav)
{'': >38}- QZSS:    Ephemeris issue of data (IODE)
{'': >38}- BeiDou:  Age of Data Ephemeris (AODE)
""",
    ),
    WriterField(
        "trans_time_gpsweek",
        "trans_time_gpsweek",
        (),
        object,
        "%15s",
        15,
        "TRANS_TIME",
        "wwwwd:ssssss",
        "Transmission time (receiver reception time)",
    ),
    WriterField("toe_gpsweek", "toe_gpsweek", (), object, "%15s", 15, "TOE", "wwwwd:ssssss", "Time of ephemeris"),
    WriterField(
        "diff_trans_toe",
        "diff_trans_toe",
        (),
        float,
        "%8d",
        8,
        "TM-TOE",
        "second",
        "Difference between transmission time and time of ephemeris",
    ),
    WriterField("sv_accuracy", "sv_accuracy", (), float, "%8d", 8, "ACC", "", "Satellite vehicle accuracy"),
    WriterField("sv_health", "sv_health", (), float, "%8d", 8, "HLTH", "", "Satellite health status"),
    WriterField("dvs_e1", "dvs_e1", (), float, "%8d", 8, "DVS_E1", "", "Data validity status for E1 signal"),
    WriterField("dvs_e5a", "dvs_e5a", (), float, "%8d", 8, "DVS_E5a", "", "Data validity status for E5a signal"),
    WriterField("dvs_e5b", "dvs_e5b", (), float, "%8d", 8, "DVS_E5b", "", "Data validity status for E5b signal"),
    WriterField("shs_e1", "shs_e1", (), float, "%8d", 8, "SHS_E1", "", "Signal health status for E1 signal"),
    WriterField("shs_e5a", "shs_e5a", (), float, "%8d", 8, "SHS_E5a", "", "Signal health status for E5a signal"),
    WriterField("shs_e5b", "shs_e5b", (), float, "%8d", 8, "SHS_E5b", "", "Signal health status for E5b signal"),
)


@plugins.register
def rinex_nav_writer(dset: "Dataset") -> None:
    """Write selected RINEX navigation file observations

    Args:
        dset:   A dataset containing the data.
    """
    file_path = config.files.path(f"output_rinex_nav", file_vars={**dset.vars, **dset.analysis})

    # Add additional fields used by the writer
    dset.add_text("date", val=[d.strftime("%Y/%m/%d %H:%M:%S") for d in dset.time.datetime])
    dset.add_text(
        "time_gpsweek",
        val=[f"{t.gps.gps_ws.week:04.0f}{t.gps.gps_ws.day:1.0f}:{t.gps.gps_ws.seconds:06.0f}" for t in dset.time],
        write_level="detail",
    )
    dset.add_text(
        "trans_time_gpsweek",
        val=[
            f"{t.gps.gps_ws.week:04.0f}{t.gps.gps_ws.day:1.0f}:{t.gps.gps_ws.seconds:06.0f}"
            for t in dset.transmission_time
        ],
        write_level="detail",
    )
    dset.add_text(
        "toe_gpsweek",
        val=[f"{t.gps.gps_ws.week:04.0f}{t.gps.gps_ws.day:1.0f}:{t.gps.gps_ws.seconds:06.0f}" for t in dset.toe],
        write_level="detail",
    )

    dset.add_float(
        "diff_trans_toe", 
        val=(dset.transmission_time.mjd - dset.toe.mjd) * Unit.day2second, 
        write_level="detail",
    )

    # Select fields available in Dataset (e.g. DVS and SHS fields are only given for Galileo)
    fields = get_existing_fields(dset, FIELDS)

    # List epochs ordered by satellites
    idx = np.concatenate([np.where(dset.filter(satellite=s))[0] for s in dset.unique("satellite")])

    # Put together fields in an array as specified by the fields-tuple
    output_list = list(zip(*(get_field(dset, f.field, f.attrs, f.unit) for f in fields)))
    output_array = np.array(output_list, dtype=[(f.name, f.dtype) for f in fields])[idx]

    # Write to disk
    header = get_header(
        fields,
        pgm_version=f"where {where.__version__}",
        run_by=util.get_user_info()["inst_abbreviation"] if "inst_abbreviation" in util.get_user_info() else "",
        summary="RINEX navigation file analysis results",
    )
    np.savetxt(
        file_path,
        output_array,
        fmt=tuple(f.format for f in fields),
        header=header,
        delimiter="",
        encoding="utf8",
    )
