import java.io.File;
import java.util.logging.Logger;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.ode.ODEIntegrator;
import org.orekit.data.DataContext;
import org.orekit.data.DirectoryCrawler;
import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
import org.orekit.forces.gravity.potential.GravityFieldFactory;
import org.orekit.forces.gravity.potential.ICGEMFormatReader;
import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
import org.orekit.forces.gravity.potential.PotentialCoefficientsReader;
import org.orekit.frames.Frame;
import org.orekit.frames.FramesFactory;
import org.orekit.frames.Predefined;
import org.orekit.orbits.CartesianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.conversion.DormandPrince54IntegratorBuilder;
import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
import org.orekit.propagation.conversion.ODEIntegratorBuilder;
import org.orekit.propagation.numerical.NumericalPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScalesFactory;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;

/** 
 * tests the different ways of setting up numerical propagation
 * @author Sam Crawford
*/
public class TestOrekitPropagators {
    private static final String DEFAULT_OREKIT_DATA_DIR = ".\\orekit-data";
    private static final Logger logger = Logger.getLogger(TestOrekitPropagators.class.getName());

    private TestOrekitPropagators() {
        // no op
    }

    public static void main(String[] args) {
        setupData();

        setupPropagatorDirectly();

        setupPropagatorViaBuilder();
    }

    private static void setupData() {
        File orekitData = new File(DEFAULT_OREKIT_DATA_DIR);
        DataContext.getDefault().getDataProvidersManager().clearProviders();
        DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(orekitData));
    }

    private static void setupPropagatorViaBuilder() {
        final Orbit orbit = getOrbit();
        final SpacecraftState state = new SpacecraftState(orbit);
        final ODEIntegratorBuilder odeBuilder = new DormandPrince54IntegratorBuilder(0.001, 100, 0.01);

        final NumericalPropagatorBuilder propBuilder = new NumericalPropagatorBuilder(orbit, odeBuilder,
                PositionAngleType.TRUE, 1.0);

        propBuilder.addForceModel(getGravityField());

        final NumericalPropagator propagator = propBuilder.buildPropagator();
        propagator.setInitialState(state);

        logGravitationalParams(propagator, "Propagator Set up via Builder:");
    }

    private static void setupPropagatorDirectly() {
        final Orbit orbit = getOrbit();
        final SpacecraftState state = new SpacecraftState(orbit);
        final ODEIntegratorBuilder odeBuilder = new DormandPrince54IntegratorBuilder(0.001, 100, 0.01);
        final ODEIntegrator integrator = odeBuilder.buildIntegrator(orbit, orbit.getType());
        final NumericalPropagator propagator = new NumericalPropagator(integrator);
        propagator.setOrbitType(orbit.getType());
        propagator.setPositionAngleType(PositionAngleType.TRUE);

        propagator.addForceModel(getGravityField());
        propagator.setInitialState(state);

        logGravitationalParams(propagator, "Propagator Set up Directly:");
    }

    private static Orbit getOrbit() {
        final AbsoluteDate absDate = new AbsoluteDate(2024, 11, 01, 0, 0, 0, TimeScalesFactory.getUTC());
        final Vector3D pos = new Vector3D(5848171.099922442, 4492788.7370601995, -141509.93903166187);
        final Vector3D vel = new Vector3D(-566.1311349148098, 952.7534231654076, 7268.659139821718);
        final TimeStampedPVCoordinates pvCoords = new TimeStampedPVCoordinates(absDate, new PVCoordinates(pos, vel));
        // this mu value is pulled from the JPL Celestial Body Definition, different
        // than the mu provided by our EGM2008.gfc potential file
        return new CartesianOrbit(pvCoords, FramesFactory.getGCRF(), 398600435507022.75);
    }

    private static HolmesFeatherstoneAttractionModel getGravityField() {
        final PotentialCoefficientsReader reader = new ICGEMFormatReader("eigen-6s.gfc", true);
        GravityFieldFactory.addPotentialCoefficientsReader(reader);
        final Frame frame = FramesFactory.getFrame(Predefined.ITRF_CIO_CONV_2010_ACCURATE_EOP);
        final NormalizedSphericalHarmonicsProvider provider = GravityFieldFactory.getNormalizedProvider(21, 21);
        return new HolmesFeatherstoneAttractionModel(frame, provider);
    }

    private static void logGravitationalParams(final NumericalPropagator propagator, final String propSetup) {
        final String formatString = """

                        %s
                        SpacecraftState GM: %.6f
                        Gravity Field GM: %.6f
                        Newtonian Attraction GM: %.6f
                """;
        logger.info(() -> String.format(formatString,
                propSetup,
                propagator.getInitialState().getOrbit().getMu(),
                propagator.getAllForceModels().get(0).getParametersAllValues()[0], // this is guaranteed the gravity
                                                                                   // field model
                propagator.getAllForceModels().get(1).getParametersAllValues()[0])); // this is guaranteed the netwonian
                                                                                     // model
    }
}
