""" Lists of significant events and related information needed to load
    trajectories from SPICE kernels and measurements from MAG data files.
"""

import logging
import numpy as np
import spiceypy as spice
from PlanetProfile.GetConfig import Params
from PlanetProfile.TrajecAnalysis.SpiceFuncs import LoadKernels, BodyDist_km, BiTrajec, spiceSCname
from MoonMag.field_xyz import eval_Bi_Schmidt

# Assign logger
log = logging.getLogger('PlanetProfile')

scPlanets = {
    'Voyager 1': ['Jupiter', 'Saturn'],
    'Voyager 2': ['Jupiter', 'Saturn', 'Uranus', 'Neptune'],
    'Galileo': 'Jupiter',
    'Cassini': 'Saturn',
    'Juno': 'Jupiter',
    'Clipper': 'Jupiter'
}
scNames = list(scPlanets.keys())
[LoadKernels(Params, parent, scName) for scName, parent in scPlanets.items()]

class FlybyCAStruct:
    def __init__(self, scName):
        self.scName = scName
        self.spiceName = spiceSCname[self.scName]

        self.rCA_km = None  # Dict of body: flyby ID: closest approach radii in km
        # Dict of body: flyby ID: closest approach UTC timestamp in SPICE-readable format
        # NOTE: These times are evaluated from the archived SPICE kernels for trajectory
        # reconstruction as of 2022-07-02.
        if self.scName == 'Voyager 1':
            self.tCA_UTC = {
                'Jupiter': {'J': '1979-03-05T12:04:35.405'},
                'Saturn':  {'S': '1980-11-12T23:45:42.725'}
            }
        elif self.scName == 'Voyager 2':
            self.tCA_UTC = {
                'Jupiter': {'J': '1979-07-09T22:29:01.964'},
                'Saturn':  {'S': '1981-08-26T03:24:04.760'},
                'Uranus':  {'U': '1986-01-24T17:58:51.346'},
                'Neptune': {'N': '1989-08-25T03:55:40.076'}
            }
        elif self.scName == 'Galileo':
            self.tCA_UTC = {
                'Io': {
                    'I0': '1995-12-07T17:45:58.461',
                    'I24': '1999-10-11T04:33:02.580',
                    'I27': '2000-02-22T13:46:41.423',
                    'I31': '2001-08-06T04:59:20.516',
                    'I32': '2001-10-16T01:23:20.598'
                },
                'Europa': {
                    'E4': '1996-12-19T06:52:57.758',
                    'E11': '1997-11-06T20:31:44.224',
                    'E12': '1997-12-16T12:03:19.869',
                    'E14': '1998-03-29T13:21:05.161',
                    'E15': '1998-05-31T21:12:56.608',
                    'E19': '1999-02-01T02:19:50.002',
                    'E26': '2000-01-03T17:59:42.595'
                },
                'Ganymede': {
                    'G1': '1996-06-27T06:29:06.687',
                    'G2': '1996-09-06T18:59:33.836',
                    'G7': '1997-04-05T07:09:58.113',
                    'G8': '1997-05-07T15:56:09.556',
                    'G28': '2000-05-20T10:10:09.662',
                    'G29': '2000-12-28T08:25:26.659'
                },
                'Callisto': {
                    'C3': '1996-11-04T13:34:27.726',
                    'C9': '1997-06-25T13:47:49.949',
                    'C10': '1997-09-17T00:18:54.790',
                    'C30': '2001-05-25T11:23:57.770'
                }
            }
        elif self.scName == 'Cassini':
            self.tCA_UTC = {
                'Enceladus': {
                    'E0': '2005-02-17T03:30:28.745',
                    'E1': '2005-03-09T09:08:02.313',
                    'E2': '2005-07-14T19:55:20.924',
                    'E3': '2008-03-12T19:06:11.827',
                    'E4': '2008-08-11T21:06:18.646',
                    'E5': '2008-10-09T19:06:39.789',
                    'E6': '2008-10-31T17:14:51.514',
                    'E7': '2009-11-02T07:41:57.693',
                    'E8': '2009-11-21T02:09:56.440',
                    'E9': '2010-04-28T00:10:16.852',
                    'E10': '2010-05-18T06:04:39.708',
                    'E11': '2010-08-13T22:30:51.619',
                    'E12': '2010-11-30T11:53:59.407',
                    'E13': '2010-12-21T01:08:26.768',
                    'E14': '2011-10-01T13:52:25.736',
                    'E15': '2011-10-19T09:22:11.790',
                    'E16': '2011-11-06T04:58:52.984',
                    'E17': '2012-03-27T18:30:09.015',
                    'E18': '2012-04-14T14:01:37.801',
                    'E19': '2012-05-02T09:31:28.806',
                    'E20': '2015-10-14T10:41:29.053',
                    'E21': '2015-10-28T15:22:41.596',
                    'E22': '2015-12-19T17:49:16.159'
                }
            }
        elif self.scName == 'Juno':
            self.tCA_UTC = {
                'Ganymede': {
                    'G34': '2021-06-07T16:56:08.733'
                }
            }
        elif self.scName == 'Clipper':
            self.tCA_UTC = {
                'Europa': {
                    'E1': '2030-06-07T16:56:08.733'
                }
            }

        self.etCA = {
            body: {flybyID: spice.str2et(tCA) for flybyID, tCA in self.tCA_UTC[body].items() if tCA is not None}
        for body in self.tCA_UTC.keys()}

    def GetrCA(self):
        self.rCA_km = {body: {flybyID: rCA for flybyID, rCA in zip(self.tCA_UTC[body].keys(),
                                BodyDist_km(self.spiceName, body, list(self.etCA[body].values()))[-1])}
                                for body in self.tCA_UTC.keys()}

    def PrinttCA(self, flybyID=None):
        # Get the actual time of closest approach according to trajectory
        # reconstruction and print.
        print(f' - {self.scName.upper()} - ')
        if flybyID is None:
            for body in self.tCA_UTC.keys():
                print(f'{body}:')
                for ID, tCA_UTC in self.tCA_UTC[body].items():
                    tCA = GetActualCA(self.spiceName, tCA_UTC, body)
                    print(f'{ID}: {tCA}')
        else:
            body = next(bname for bname in self.tCA_UTC.keys() if bname[0] == flybyID[0])
            tCA = GetActualCA(self.spiceName, self.tCA_UTC[body][flybyID], body)
            print(f'{flybyID}: {tCA}')
                    

def GetActualCA(spiceSCname, t_UTC, bodyname, range_min=5, res_s=0.001):
    # Look within +/- range_min of t_UTC to within res_s precision
    # to get the UTC time string of the closest approach.

    spiceBody = bodyname.upper()
    etApprox = spice.str2et(t_UTC)
    ets = np.arange(etApprox - range_min * 60, etApprox + range_min * 60, res_s)
    pos, _ = spice.spkpos(spiceSCname, ets, f'IAU_{spiceBody}', 'NONE', spiceBody)
    r_km = np.sqrt(pos[:,0]**2 + pos[:,1]**2 + pos[:,2]**2)
    etCA = ets[np.argmin(r_km)]
    tCA_UTC = spice.et2utc(etCA, 'ISOC', 3)

    return tCA_UTC


FlybyCA = {scName: FlybyCAStruct(scName) for scName in scNames}
[FlybyCA[sc].GetrCA() for sc in scNames]

def Ws(et, planet=None, epoch='J2000'):
    W_deg = {}
    if epoch == 'J1950':
        J1950 = mp.mpf(spice.str2et('1950 Jan 01 00:00:00.000 TDB'))
        d = (et - J1950)/86400
        N = 357.85 + 52.316*d/36525
        W_deg['J'] = np.round(float(mp.fmod( 80.6 + 870.536*d, 360)),3)
        W_deg['S'] = np.round(float(mp.fmod(360.0 + 810.7939024*d, 360)),3)
        W_deg['U'] = np.round(float(mp.fmod(360.0 - 501.1600928*d, 360)),3)
        W_deg['N'] = np.round(float(mp.fmod(360.0 + 536.3128492*d - 0.48*mp.sin(mp.radians(N)), 360)),3)
    else:
        d = et/86400
        N = 357.85 + 52.316*d/36525
        W_deg['J'] = np.round(float(mp.fmod(284.95  + 870.5360000*d, 360)),3)
        W_deg['S'] = np.round(float(mp.fmod( 38.90  + 810.7939024*d, 360)),3)
        W_deg['U'] = np.round(float(mp.fmod(203.81  - 501.1600928*d, 360)),3)
        W_deg['N'] = np.round(float(mp.fmod(253.18  + 536.3128492*d - 0.48*mp.sin(mp.radians(N)), 360)),3)
    if planet is None or planet.lower() == 'all':
        return W_deg
    else:
        return W_deg[planet[0].upper()]
    
def Bxyz2Bsph(Bx, By, Bz, theta, phi):
    # Convert vector components aligned to cartesian axes
    # into vector components aligned to spherical coordinates.
    # Source: Arfken, Weber, Harris, Mathematical Methods for Physicists,
    # 7th ed, pg. 199 for the unit vectors.
    Br   =  np.sin(theta) * np.cos(phi) * Bx + np.sin(theta) * np.sin(phi) * By + np.cos(theta) * Bz
    Bth  =  np.cos(theta) * np.cos(phi) * Bx + np.cos(theta) * np.sin(phi) * By - np.sin(theta) * Bz
    Bphi =                 -np.sin(phi) * Bx +                 np.cos(phi) * By
    return Br, Bth, Bphi

import mpmath as mp
J1950_0 = mp.mpf(86400*(2433282.423357 - 2451545))
J1950 = mp.mpf(spice.str2et('1950 Jan 01 00:00:00.000 TDB'))
J1957 = mp.mpf(spice.str2et('1957-01-01T00:00:00.000'))
J1965 = mp.mpf(spice.str2et('1965 Jan 01 00:00:00.000 TDB'))
J1980 = mp.mpf(spice.str2et('1980-01-01T00:00:00.000'))
J2000 = mp.mpf(0)
etULS = mp.mpf(spice.str2et('1986-01-24T18:00:00.000'))
etNLS = mp.mpf(spice.str2et('1989-08-25T03:56:00.000'))
eqS1980 = np.round(float(mp.fmod(90 - 40.589 * J1980/86400/36525, 360)),2)
W1950_0, W1950, W1957, W1965, WULS, WNLS, W1980, W2000 \
    = (Ws(J1950_0), Ws(J1950), Ws(J1957), Ws(J1965), Ws(etULS), Ws(etNLS), Ws(J1980), Ws(J2000))

import os
parentName = 'Neptune'
t, r, lat, lon, BrSC, BthSC, BphiSC = np.loadtxt(os.path.join('SpacecraftMAGdata', 'Voyager 2', f'vg2_{parentName}_12s_sph.tab'),
                                                unpack=True, dtype='U23,f,f,f,f,f,f')
ets = spice.str2et(t)
theta = np.radians(90 - lat)
phi = np.radians(360 - lon)
x = r * np.sin(theta) * np.cos(phi)
y = r * np.sin(theta) * np.sin(phi)
z = r * np.cos(theta)
gnm = 1e5 * np.loadtxt('/Users/mjstyczi/PlanetMag/modelCoeffs/coeffsNeptuneO8g.csv', skiprows=2, delimiter=',')
hnm = 1e5 * np.loadtxt('/Users/mjstyczi/PlanetMag/modelCoeffs/coeffsNeptuneO8h.csv', skiprows=2, delimiter=',')

Bx, By, Bz = (np.zeros_like(BrSC) for _ in range(3))
for n in range(1,3+1):
    for m in range(n+1):
        dBx, dBy, dBz = eval_Bi_Schmidt(n, m, gnm[n-1,m], hnm[n-1,m], x, y, z, r)
        Bx = Bx + dBx
        By = By + dBy
        Bz = Bz + dBz

BrN3, BthN3, BphiN3 = Bxyz2Bsph(Bx, By, Bz, theta, phi)
with open('/Users/mjstyczi/PlanetMag/MAG/Voyager 2/VG2_comp.tab', 'w') as f:
    f.write(' '.join(['UTC event time'.ljust(24),
                      'R (R_N)'.ljust(24),
                      'theta (rad)'.ljust(24),
                      'phi (rad)'.ljust(24),
                      'BrSC (nT)'.ljust(24),
                      'BthSC (nT)'.ljust(24),
                      'BphiSC (nT)'.ljust(24),
                      'BrNmax=3 (nT)'.ljust(24),
                      'BthNmax=3 (nT)'.ljust(24),
                      'BphiNmax=3 (nT)']) + '\n')
    for i in range(np.size(BrN3)):
        f.write(' '.join([
            t[i],
            f'{r[i]:24.17e}',
            f'{theta[i]:24.17e}',
            f'{phi[i]:24.17e}',
            f'{BrSC[i]:24.17e}',
            f'{BthSC[i]:24.17e}',
            f'{BphiSC[i]:24.17e}',
            f'{BrN3[i]:24.17e}',
            f'{BthN3[i]:24.17e}',
            f'{BphiN3[i]:24.17e}\n '
        ]))
a=0
