#!/usr/bin/env python

"""Compare gamhelio results with spacecraft data.

Compare heliospheric model results from gamhelio with data measured by
spacecraft.

Note that the terms "ephemeris" and "trajectory" are used interchangeably.

Authors
-------
Eric Winter (eric.winter@jhuapl.edu)
Mike Wiltberger
"""


# Standard modules
import argparse
from argparse import RawTextHelpFormatter
import os
import importlib.resources as pkg_resources

# Third-party modules
import numpy as np
import spacepy.datamodel as dm

# Kaipy modules
import kaipy.kaiH5 as kaiH5
import kaipy.kaiViz as kv
import kaipy.kaiTools as kaiTools
import kaipy.satcomp.scutils as scutils
from kaipy import satcomp

# Program constants.

# Program description string.
description = """Extract satellite trajectory and observations for various
heliospheric spacecraft from CDAWeb. Produce comparisons between the
observations and corresponding gamhelio model results."""

# Default path to sctrack.x
default_cmd = os.path.join(
    os.environ["KAIJUHOME"], "build", "bin", "sctrack.x"
)

# Default time interval for ephemeris data returned from CDAWeb (seconds).
default_deltaT = 3600.00  # 1 hour

# Default run ID string.
default_runid = "wsa"

# Defaut number of segments to process.
default_numSeg = 1

# Default path to model results directory.
default_path = os.getcwd()

# Path to file of heliospheric spacecraft metadata.
helio_sc_metadata_path = pkg_resources.files(satcomp).joinpath("sc_helio.json") 


def create_command_line_parser():
    """Create the command-line argument parser.

    Create the command-line parser.

    Parameters
    ----------
    None

    Returns
    -------
    parser : argparse.ArgumentParser
        Parser for command-line arguments.
    """
    parser = argparse.ArgumentParser(
        description=description, formatter_class=RawTextHelpFormatter
    )
    parser.add_argument(
        "-c", "--cmd", type=str, metavar="command", default=default_cmd,
        help="Full path to sctrack.x command (default: %(default)s)."
    )
    parser.add_argument(
        "-d", "--debug", action="store_true", default=False,
        help="Print debugging output (default: %(default)s)."
    )
    parser.add_argument(
        "--deltaT", type=float, metavar="deltaT", default=default_deltaT,
        help="Time interval (seconds) for ephemeris points from CDAWeb " +
             "(default: %(default)s)."
    )
    parser.add_argument(
        "-i", "--id", type=str, metavar="runid", default=default_runid,
        help="ID string of the run (default: %(default)s)"
    )
    parser.add_argument(
        "-k", "--keep", action="store_true", default=False,
        help="Keep intermediate files (default: %(default)s).")
    parser.add_argument(
        "-n", "--numSeg", type=int, metavar="number_segments",default=default_numSeg,
        help="Number of segments to simultaneously process (default: %(default)s).")
    parser.add_argument(
        "-p", "--path", type=str, metavar="path", default=default_path,
        help="Path to directory containing gamhelio results (default: %(default)s)"
    )
    parser.add_argument(
        "-s", "--satId", type=str, metavar="satellite_id", default=None,
        help="Name of Satellite to compare"
    )
    parser.add_argument(
        "-v", "--verbose", action="store_true", default=False,
        help="Print verbose output (default: %(default)s)."
    )
    return parser


def main():
    """Begin main program."""

    # Set up the command-line parser.
    parser = create_command_line_parser()

    # Parse the command-line arguments.
    args = parser.parse_args()
    cmd_sctrack = args.cmd
    debug = args.debug
    cdaweb_data_interval = args.deltaT
    gh_run_id = args.id
    keep = args.keep
    num_segments = args.numSeg
    gh_result_directory = args.path
    sc_to_compare = args.satId
    verbose = args.verbose
    if debug:
        print("args = %s" % args)

    # Read the list of available spacecraft from the YAML configuration file.
    if verbose:
        print("Reading heliosphere spacecraft metadata from %s." %
              helio_sc_metadata_path)
    sc_metadata = scutils.getScIds(helio_sc_metadata_path, doPrint=verbose)
    if debug:
        print("sc_metadata = %s" % sc_metadata)

    # Compute the path to the gamhelio output files to examine.
    if verbose:
        print("Looking for gamhelio results for run %s in %s." %
              (gh_run_id, gh_result_directory))
    (gh_result_path, is_MPI, Ri, Rj, Rk) = kaiTools.getRunInfo(
        gh_result_directory, gh_run_id
    )
    if debug:
        print("gh_result_path = %s" % gh_result_path)
        print("is_MPI = %s" % is_MPI)
        print("(Ri, Rj, Rk) = (%s, %s, %s)" % (Ri, Rj, Rk))

    # Determine the number of timesteps in the gamhelio output files, and get
    # a list of the timestep indices.
    if verbose:
        print("Counting timesteps in %s." % gh_result_path)
    (gh_n_timesteps, gh_timestep_indices) = kaiH5.cntSteps(gh_result_path)
    if verbose:
        print("  Found %s timesteps." % gh_n_timesteps)
    if debug:
        print("gh_n_timesteps = %s" % gh_n_timesteps)
        print("gh_timestep_indices = %s" % gh_timestep_indices)

	# Pull the timestep information from the gamhelio output files. This
    # fetches the "MJD" attribute from each of the top-level groups called
    # "Step#[\d]+". These MJD values are floats, and are assumed to be in
    # increasing order.
    if verbose:
        print("Reading timestep times from %s." % gh_result_path)
    gh_timestep_MJDs = kaiH5.getTs(
        gh_result_path, gh_timestep_indices, aID="MJD"
    )
    if debug:
        print("gh_timestep_MJDs = %s" % gh_timestep_MJDs)

    # Get the MJDc value for use in computing the gamhelio frame. This value
    # was specified in the WSA file of initial conditions.
    if verbose:
        print("Reading MJDc value to use for constructing gamhelio coordinate frame.")
    gh_MJDc = scutils.read_MJDc(gh_result_path)
    if verbose:
        print("  Found MJDc = %s" % gh_MJDc)
    if debug:
        print("gh_MJDc = %s" % gh_MJDc)

    # Now get the "time" attribute from each step. For gamhelio, these elapsed
    # times are in seconds since the start of the simulation, and are assumed
    # to be in increasing order
    if verbose:
        print("Reading timestep elapsed seconds from %s." % gh_result_path)
    gh_timestep_elapsed_seconds = kaiH5.getTs(
        gh_result_path, gh_timestep_indices, aID="time"
    )
    if debug:
        print("gh_timestep_elapsed_seconds = %s" %
              gh_timestep_elapsed_seconds)

    # Convert the MJD values to Universal Time datetime objects.
    if verbose:
        print("Converting timestep MJDs to UTC datetimes.")
    gh_timestep_UT = kaiTools.MJD2UT(gh_timestep_MJDs)
    if debug:
        print("gh_timestep_UT = %s" % gh_timestep_UT)

    # Use the first (positive) elapsed time as the initial time.
    # N.B. THIS SKIPS THE FIRST SIMULATION STEP SINCE IT TYPICALLY HAS
    # gh_timestep_elapsed_seconds[0] = 0.
    # Use the last time as the last MJD.
    if verbose:
        print("Identifying first timestep with elapsed seconds > 0.")
    gh_first_step_used = np.argwhere(gh_timestep_elapsed_seconds > 0.0)[0][0]
    if verbose:
        print("  Found first non-zero elapsed seconds at timestep %s." %
              gh_first_step_used)
    gh_t0 = gh_timestep_UT[gh_first_step_used]
    gh_t1 = gh_timestep_UT[-1]
    if verbose:
        print("Using %s as the starting datetime for data comparison." % gh_t0)
        print("Using %s as the ending datetime for data comparison." % gh_t1)
    if debug:
        print("gh_t0 = %s" % gh_t0)
        print("gh_t1 = %s" % gh_t1)

    # Construct the string versions of the first and last times.
    datestr_start = gh_t0.strftime("%Y-%m-%dT%H:%M:%SZ")
    datestr_end = gh_t1.strftime("%Y-%m-%dT%H:%M:%SZ")
    if debug:
        print("datestr_start = %s" % datestr_start)
        print("datestr_end = %s" % datestr_end)

    # Save the (float) MJD of the first used step.
    gh_first_MJD = gh_timestep_MJDs[gh_first_step_used]
    if verbose:
        print("Using %s as the starting MJD for data comparison." % gh_first_MJD)
    if debug:
        print("gh_first_MJD = %s" % gh_first_MJD)

    # Save the elapsed simulation time (seconds) of the first used step.
    gh_first_elapsed_seconds = gh_timestep_elapsed_seconds[gh_first_step_used]
    if verbose:
        print("Using %s as the starting elapsed seconds for data comparison." %
              gh_first_elapsed_seconds)
    if debug:
        print("gh_first_elapsed_seconds = %s" % gh_first_elapsed_seconds)

    # Determine the list of IDs of spacecraft to fetch data from. If no
    # spacecraft were specified on the command line, use all spacecraft
    # listed in the heliosphere spacecraft metadata file.
    if sc_to_compare:
        sc_to_compare = sc_to_compare.split(",")
    else:
        sc_to_compare = list(sc_metadata.keys())
    if verbose:
        print("Comparing gamhelio results to data measured by:")
        for sc_id in sc_to_compare:
            print("  %s" % sc_id)
    if debug:
        print("sc_to_compare = %s" % sc_to_compare)

    # Fetch the ephemeris and observed data for each spacecraft in the list.
    for sc_id in sc_to_compare:

        # Fetch the ephemeris and observed data for the current spacecraft.
        if verbose:
            print("Fetching ephemeris and instrument data for %s from CDAWeb." % sc_id)
        sc_data = scutils.get_helio_cdaweb_data(
            sc_id, sc_metadata[sc_id],
            datestr_start, datestr_end, cdaweb_data_interval,
            verbose=verbose, debug=debug
        )
        if debug:
            print("sc_data = %s" % sc_data)

        # If no data was found for the spacecraft, go to the next.
        if sc_data is None:
            print("No data found for %s." % sc_id)
            continue

        # At this point, the data object contains only the raw spacecraft
        # ephemeris, and the raw spacecraft-measured data, as returned from
        # CDAWeb.

        # Ingest the CDAWeb data. This means convert the data as originally
        # retrieved from CDAWeb to the units and coordinate systems used by
        # gamhelio so that comparisons may be made. This will create new
        # variables in the data object, with names analogous to the
        # corresponding gamhelio variables.
        if verbose:
            print("Converting CDAWeb data for %s into gamhelio format." % sc_id)
        scutils.ingest_helio_cdaweb_data(
            sc_id, sc_data, sc_metadata[sc_id], gh_MJDc,
            verbose=verbose, debug=debug
        )

        # Use the spacecraft trajectory to interpolate simulated observations
        # from the gamhelio output.
        if verbose:
            print("Interpolating gamhelio results along %s trajectory." % sc_id)
        scutils.interpolate_gamhelio_results_to_trajectory(
            sc_data, sc_metadata[sc_id], sc_id,
            gh_first_MJD, gh_first_elapsed_seconds,
            gh_result_directory, gh_run_id,
            cmd_sctrack, num_segments, keep, gh_MJDc
        )

        # Save the important measured and simulated data as a CDF file for
        # comparison.
        cdf_path = os.path.join(gh_result_directory, sc_id + ".comp.cdf")
        if verbose:
            print("Saving comparison data to %s." % cdf_path)
        if os.path.exists(cdf_path):
            if verbose:
                print("Deleting existing CDF comparison file %s" % cdf_path)
            os.system("rm %s" % cdf_path)
        if verbose:
            print("Creating CDF file %s with %s and GAMERA data" % (cdf_path, sc_id))
        dm.toCDF(cdf_path, sc_data)

        # Compute the errors in the simulated data relative to the measured
        # data and save in a file.
        error_file_path = os.path.join(gh_result_directory, sc_id + "-error.txt")
        if verbose:
            print("Computing gamhelio-%s errors and saving to %s." %
                  (sc_id, error_file_path))
        scutils.write_helio_error_report(error_file_path, sc_id, sc_data)

        # Make a comparison plot of the measured and simulated results.
        plot_file_path = os.path.join(gh_result_directory, sc_id + ".png")
        if verbose:
            print("Saving gamhelio-%s comparison plots in %s." %
                  (sc_id, plot_file_path))
        kv.helioCompPlot_new(plot_file_path, sc_id, sc_data)

        # Plot the spacecraft trajectory.
        # plot_file_path = os.path.join(gh_result_directory, sc_id + "-traj.png")
        # if verbose:
        #     print("Plotting %s trajectory in spacecraft frame to %s." % (sc_id, plot_file_path))
        # kv.helioTrajPlot(plot_file_path, sc_id, sc_data)

if __name__ == "__main__":
    main()