/**
 * Copyright (c) 2008-2022, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved.
 * This work is provided under a BSD open-source license, see project license.html and license.txt
 *
 * @author brutzman@nps.edu
 */
package OpenDis7Examples;

import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
import edu.nps.moves.dis7.enumerations.ForceID;
import edu.nps.moves.dis7.pdus.EntityStatePdu;
import edu.nps.moves.dis7.pdus.Pdu;
import edu.nps.moves.dis7.pdus.Vector3Double;
import edu.nps.moves.dis7.utilities.DisChannel;
import edu.nps.moves.dis7.utilities.DisTime;
import edu.nps.moves.dis7.utilities.stream.X3dCreateInterpolators;
import edu.nps.moves.dis7.utilities.stream.X3dCreateLineSet;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The purpose of this program is to provide an easily modifiable example
 * simulation for networked entity tracks and presentation, including
 * DIS-capable entities doing tasks and reporting them to the network.
 * Default settings include PDU recording turned on by default.
 * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleTrackInterpolationLog.txt" target="_blank">ExampleTrackInterpolationLog.txt</a>
 * @see <a href="https://calhoun.nps.edu/handle/10945/65436" target="_blank">REPEATABLE UNIT TESTING OF DISTRIBUTED INTERACTIVE SIMULATION (DIS) PROTOCOL BEHAVIOR STREAMS USING WEB STANDARDS</a> by Tobias Brennenstuhl, Masters Thesis, Naval Postgraduate School (NPS), June 2020
 * @see <a href="https://gitlab.nps.edu/Savage/SavageTheses/-/tree/master/BrennenstuhlTobias" target="_blank">https://gitlab.nps.edu/Savage/SavageTheses/-/tree/master/BrennenstuhlTobias</a>
 */
public class ExampleTrackInterpolation extends ExampleSimulationProgram
{    
    /** Default constructor */
    public ExampleTrackInterpolation()
    {
        // default constructor
    }
    // -------------------- Begin Variables for X3D autogenerated code
    private X3dCreateInterpolators x3dInterpolators = new X3dCreateInterpolators();
    private X3dCreateLineSet       x3dLineSet       = new X3dCreateLineSet();
    private byte[]                 globalByteBufferForX3dInterpolators = null;
    // -------------------- End Variables for X3D autogenerated code
    
    ArrayList<Pdu> pduSentList = new ArrayList<>();
    
    /**
     * This runSimulationLoops() method is a programmer-modifiable method for
     * defining and running a new simulation of interest. Welcome! Other parts
     * of this program handle bookkeeping and plumbing tasks so that you can
     * focus on your model entities and activities. Expandable support includes
     * DIS EntityStatePdu, FirePdu and CommentPdu all available for modification
     * and sending in a simulation loop. Continuous improvement efforts seek to
     * make this program as easy and straightforward as possible for DIS
     * simulationists to use and adapt. All of the other methods are setup,
     * teardown and configuration that you may find interesting, even helpful,
     * but don't really have to worry about.
     */
    @SuppressWarnings("SleepWhileInLoop") // yes we do that
    @Override // indicates that this method supercedes corresponding superclass method
    public void runSimulationLoops() 
    {
        try
        {
            final int SIMULATION_MAX_LOOP_COUNT = 50; // be deliberate out there!  also avoid infinite loops.
            int simulationLoopCount = 0;        // variable, initialized at 0
            boolean simulationComplete = false;     // sentinel variable as termination condition, are we done yet?

            // TODO reset Clock Time to today's date and timestamp to zero, providing consistent outputs for each simulation run
            DisTime.setEpochLvcNow();
            simulationTimeSeconds = simulationTimeInitial - getSimulationTimeStepDuration(); // pre-initialization for first loop
        
            initializeSimulationEntities();
            
            // use declared PDU objects and set their values.
            entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
            // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
            
            disChannel.getPduRecorder().setVerbose(false);
            disChannel.getPduRecorder().hasVerboseOutput(); // debug check

            EntityStatePdu espdu_1 = pduFactory.makeEntityStatePdu();
            espdu_1.setEntityID(entityID_1);
            espdu_1.setForceId(ForceID.FRIENDLY);
            espdu_1.setEntityType(new _001Poseidon()); // note import statement above
            espdu_1.setMarking("track path");
//          espdu_1.clearMarking();     // test
//          espdu_1.getMarkingString(); // trace
//          espdu_1.setEntityLocation(new Vector3Double().setX(0).setY(0).setZ(0)); // long form
            espdu_1.setEntityLocation(0, 0, 0); // utility method
            
            float speedEntity_1 = 1.0f; // meters/second
            EntityStatePdu.Direction directionEntity_1 = EntityStatePdu.Direction.NORTH;
            
            PduTrack pduTrack_1 = new PduTrack();
            pduTrack_1.setDescriptor("testing 123");
            pduTrack_1.setDefaultWaypointInterval(1.0f); // overrides timestamps
            pduTrack_1.setAddLineBreaksWithinKeyValues(true);
            pduTrack_1.setAuthor("Don Brutzman");
            pduTrack_1.setX3dModelName("ExampleTrackInterpolation.x3d");
            pduTrack_1.setX3dModelIdentifier("https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleTrackInterpolation.x3d");
            pduTrack_1.addPdu(espdu_1); // initial location
            
            // OK send initial PDUs prior to loop
            if (disChannel.getPduRecorder().hasVerboseOutput())
                System.out.println("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
            disChannel.sendSinglePdu(espdu_1);
//            sendCommentPdu(timeStepDurationCommentType, narrativeMessage1, narrativeMessage2, narrativeMessage3);
            pduSentList.add(espdu_1);
            reportPdu(simulationLoopCount, espdu_1.getEntityLocation(), directionEntity_1);

            // TODO simulation management PDUs for startup, planning to design special class support
            // ===================================================================================================
            // loop the simulation while allowed, programmer can set additional conditions to break out and finish
            while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT) // are we done yet?
            {
                simulationLoopCount++;      // good practice: increment loop counter as first action in that loop
                simulationTimeSeconds += getSimulationTimeStepDuration(); // good practice: update clock along with loop index

                // =============================================================================================
                // * your own simulation code starts here! *****************************************************
                // =============================================================================================
                //  are there any other variables to modify at the beginning of your loop?
                // compute a track, update an ESPDU, whatever it is that your model is doing...
                
                // Pick direction, change each 10 seconds, traverse a box.  No physics.  Walk a box!
                if (simulationLoopCount <= 10)
                    directionEntity_1 = EntityStatePdu.Direction.NORTH;
                else if (simulationLoopCount <= 20)
                    directionEntity_1 = EntityStatePdu.Direction.EAST;
                else if (simulationLoopCount <= 30)
                    directionEntity_1 = EntityStatePdu.Direction.SOUTH;
                else // if (simulationLoopCount <= 40)
                    directionEntity_1 = EntityStatePdu.Direction.WEST;
                
                // use utility method to simply update velocity vector using speed value and direction
                espdu_1.setEntityLinearVelocity(speedEntity_1, directionEntity_1);
                
                // Where is my entity?  Insert changes in position; this sample only changes X position.
                espdu_1.advanceEntityLocation(getSimulationTimeStepDuration());

                // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
                narrativeMessage1 = "MV3500 TrackSimulationProgram";
                narrativeMessage2 = "runSimulation() loop " + simulationLoopCount + " at time " + simulationTimeSeconds;
                narrativeMessage3 = ""; // intentionally blank for testing

                // your loop termination condition goes here
                if (simulationLoopCount > 40) // for example
                {
                    simulationComplete = true;
                }
                // =============================================================================================
                // * your own simulation code is finished here! ************************************************
                // =============================================================================================

                // staying synchronized with timestep: wait duration for elapsed time in this loop
                // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes
                if (false) // real-time operation or simulation speedup
                {
                    Thread.sleep((long) (getSimulationTimeStepDuration() * 1000)); // seconds * (1000 msec/sec) = milliseconds
                    System.out.println(disChannel.getTRACE_PREFIX() + "Pausing for " + getSimulationTimeStepDuration() + " seconds");
                }

                // OK now send the status PDUs for this loop, and then continue
                if (disChannel.getPduRecorder().hasVerboseOutput())
                    System.out.println("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
                disChannel.sendSinglePdu(espdu_1);
                disChannel.sendCommentPdu(DisChannel.COMMENTPDU_SIMULATION_TIMESTEP, narrativeMessage1, narrativeMessage2, narrativeMessage3);
                if (disChannel.getPduRecorder().hasVerboseOutput())
                    System.out.println(disChannel.getTRACE_PREFIX() + "PDUs successfully sent for this loop");
                pduSentList.add(espdu_1);
                pduTrack_1.addPdu(espdu_1);
                Vector3Double location = espdu_1.getEntityLocation();
                reportPdu(simulationLoopCount, location, directionEntity_1);
                
                // ===============================
                // current loop now finished, check whether to terminate if simulation complete, otherwise continue
                if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good
                {
                    System.out.println(disChannel.getTRACE_PREFIX() + "loop termination condition met, simulationComplete=" + simulationComplete); // ", final loopCount=" + loopCount +
                    break;
                }
            }   // end of simulation loop
            // ===================================================================================================
            System.out.println(disChannel.getTRACE_PREFIX() + "all PDUs successfully sent for this loop (pduSentList.size()=" + pduSentList.size() + " total)");
            
            // track analysis
            System.out.println(disChannel.getTRACE_PREFIX() + "pduTrack_1 initialLocation=" + pduTrack_1.getInitialLocation() + ", latestLocation=" + pduTrack_1.getLatestLocation());
            pduTrack_1.sortPdus()
                      .createRawWaypoints();
            
            System.out.println("pduTrack_1 getEspduCount()=" + pduTrack_1.getEspduCount());
            System.out.println("pduTrack_1 duration = " + pduTrack_1.getTotalDurationSeconds() + " seconds = " +
                                                          pduTrack_1.getTotalDurationTicks() + " ticks");
            System.out.println("=================================");
            System.out.println(pduTrack_1.createX3dModel());
            System.out.println("=================================");
            
            narrativeMessage2 = "runSimulation() completed successfully"; // all done
            disChannel.sendCommentPdu(DisChannel.COMMENTPDU_NARRATIVE, narrativeMessage1, narrativeMessage2, narrativeMessage3);
            if (disChannel.getPduRecorder().hasVerboseOutput())
                disChannel.printlnTRACE("final CommentPdu successfully sent for simulation");
            // TODO simulation management PDUs
        }
        catch (InterruptedException iex) // handle any exception that your code might choose to provoke!
        {
            Logger.getLogger(ExampleTrackInterpolation.class.getSimpleName()).log(Level.SEVERE, null, iex);
        }
    }
    
    /**
     * Report current PDU information to console
     * @param simulationLoopCount current loop index
     * @param location current location
     * @param directionEntity current direction
     * @return same object to permit progressive setters
     */
    public ExampleTrackInterpolation reportPdu(int simulationLoopCount, Vector3Double location, EntityStatePdu.Direction directionEntity)
    {
        System.out.println (String.format("%2d ", simulationLoopCount) + "Entity location=(" + 
                String.format("%4.1f", location.getX()) + ", " +
                String.format("%4.1f", location.getY()) + ", " + 
                String.format("%4.1f", location.getZ()) + ") "  +
                String.format("%-5s",   directionEntity.name())
//              + " " + espdu_1.getEntityLinearVelocity().toString()
        );
        return this;
    }

    /* Default constructors used unless otherwise defined/overridden.  */
    /**
     * Main method is first executed when a program instance is loaded.
     *
     * @see
     * <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html" target="_blank">Java
     * Tutorials: A Closer Look at the "Hello World!" Application</a>
     * @param args command-line arguments are an array of optional String
     * parameters that are passed from execution environment during invocation
     */
    public static void main(String[] args)
    {
        thisProgram = new ExampleTrackInterpolation(); // create instance of self within static main() method

        thisProgram.disChannel.printlnTRACE("main() started...");
        
        thisProgram.handleArguments (args); // process command-line invocation arguments
        
//        thisProgram.disChannel.getPduRecorder().setVerbose(false);
//        thisProgram.disChannel.setVerboseComments(false);
//        thisProgram.disChannel.getDisNetworkInterface().setVerbose(false);
        
        thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ...
        
        thisProgram.disChannel.tearDownNetworkInterface(); // make sure no processes are left lingering
        
        thisProgram.disChannel.printlnTRACE("complete."); // report successful completion
        
        System.exit(0); // ensure all threads and sockets released
    }

}