package tmcsim.cadsimulator.managers;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import tmcsim.cadmodels.CMSInfo;
import tmcsim.cadsimulator.Coordinator;
import tmcsim.cadsimulator.paramicscontrol.ParamicsCameraStatusReader;
import tmcsim.cadsimulator.paramicscontrol.ParamicsCommunicator;
import tmcsim.cadsimulator.paramicscontrol.ParamicsIncidentWriter;
import tmcsim.cadsimulator.paramicscontrol.ParamicsStatusReader;
import tmcsim.common.CADEnums.PARAMICS_STATUS;
import tmcsim.common.SimulationException;
import tmcsim.common.XMLIncident;

/**
 * ParamicsSimulationManager is a CAD Simulator Manager used to handle all
 * communication between the CAD Simulator and the remote ParamicsCommunicator.
 * Upon construction all ParamicsReaders and ParamicsWriters are initialized
 * with data parsed from the Properties file. When a connection is established
 * the ParamicsStatusReader is registered to read Paramics Status updates. These
 * updates are received through the updateParamicsStatus() method, which
 * notifies the Coordinator of the new status. The loadParamicsNetwork() must be
 * called to register the ParamicsIncidentWriter which will write the
 * information to cause Paramics to begin loading a traffic network. When the
 * status becomes LOADED, the ParamicsCameraStatusReader is registered and
 * begins reading. All camera speed updates are received through the
 * updateCameraInfo() method. The startSimulation(), resetSimulation(), and
 * sendIncidentUpdate() methods are used to control the flow of Incident update
 * information to Paramics. The updateIncident() and updateDiversion() methods
 * are used to update the information that is sent to Paramics.
 *
 *
 * @see PARAMICS_STATUS
 * @author Matthew Cechini
 * @version
 */
public class ParamicsSimulationManager
{

    /**
     * Error logger.
     */
    private static Logger paramLogger = Logger.getLogger("tmcsim.cadsimulator.paramicscontrol");

    /**
     * Enumeration containing property names for Properties parsing.
     *
     * @author Matthew Cechini
     */
    private static enum PROPERTIES
    {

        PARAMICS_HOST("ParamicsCommHost"),
        PARAMICS_PORT("ParamicsCommPort"),
        INCIDENT_UPDATE_INT("IncidentUpdateInterval"),
        INCIDENT_UPDATE_FILE("IncidentUpdateFile"),
        PARAMICS_STATUS_INT("ParamicsStatusInterval"),
        PARAMICS_STATUS_FILE("ParamicsStatusFile"),
        CAMERA_STATUS_INT("CameraStatusInterval"),
        CAMERA_STATUS_FILE("CameraStatusFile");
        String name;

        private PROPERTIES(String n)
        {
            name = n;
        }
    };
    /**
     * ParamicsCommunicator Object used for communication.
     */
    private ParamicsCommunicator theCommunicator;
    /**
     * Reference to the Coordinator Object.
     */
    private Coordinator theCoordinator;
    /**
     * Reference to the MediaManager Object.
     */
    private MediaManager theMediaMgr;
    /**
     * ParamicsIncidentWriter used to send incident updates to Paramics.
     */
    private ParamicsIncidentWriter paramicsIncidentWriter;
    /**
     * ParamicsStatusReader used to read status information from Paramics.
     */
    private ParamicsStatusReader paramicsStatusReader;
    /**
     * ParamicsCameraStatusReader used to read speed information from Paramics.
     */
    private ParamicsCameraStatusReader paramicsCameraStatusReader;
    /**
     * Properties file.
     */
    private Properties paramicsProperties;

    /**
     * Constructor. Loads the Properties file and initializes all Paramics
     * Writers and Readers with the parsed data.
     *
     * @param propertiesFile Target file path of properties file.
     * @param coor Coordinator Object.
     * @param mediaMgr MediaManager Object.
     */
    public ParamicsSimulationManager(String propertiesFile, Coordinator coor, MediaManager mediaMgr)
    {

        try
        {
            paramicsProperties = new Properties();
            paramicsProperties.load(new FileInputStream(propertiesFile));

            theCoordinator = coor;
            theMediaMgr = mediaMgr;

            theCommunicator = new ParamicsCommunicator(this,
                    paramicsProperties.getProperty(PROPERTIES.PARAMICS_HOST.name),
                    Integer.parseInt(paramicsProperties.getProperty(
                    PROPERTIES.PARAMICS_PORT.name).trim()));

            paramicsIncidentWriter = new ParamicsIncidentWriter(Integer.parseInt(
                    paramicsProperties.getProperty(PROPERTIES.INCIDENT_UPDATE_INT.name).trim()));
            paramicsIncidentWriter.writerID = theCommunicator.nextID();
            paramicsIncidentWriter.targetFile = paramicsProperties.getProperty(
                    PROPERTIES.INCIDENT_UPDATE_FILE.name);

            paramicsStatusReader = new ParamicsStatusReader(this);
            paramicsStatusReader.readerID = theCommunicator.nextID();
            paramicsStatusReader.interval = paramicsProperties.getProperty(
                    PROPERTIES.PARAMICS_STATUS_INT.name).trim();
            paramicsStatusReader.targetFile = paramicsProperties.getProperty(
                    PROPERTIES.PARAMICS_STATUS_FILE.name);

            paramicsCameraStatusReader = new ParamicsCameraStatusReader(this);
            paramicsCameraStatusReader.readerID = theCommunicator.nextID();
            paramicsCameraStatusReader.interval = paramicsProperties.getProperty(
                    PROPERTIES.CAMERA_STATUS_INT.name).trim();
            paramicsCameraStatusReader.targetFile = paramicsProperties.getProperty(
                    PROPERTIES.CAMERA_STATUS_FILE.name);
        } catch (Exception e)
        {
            paramLogger.logp(Level.SEVERE, "ParamicsSimulationManager",
                    "Constructor", "Exception in parsing properties file.", e);
        }

    }

    /**
     * Returns whether a connection exists to the remote ParamicsCommunicator.
     *
     * @return true if a connection exists, false if not.
     */
    public boolean isConnected()
    {
        return theCommunicator.isConnected();
    }

    /**
     * Returns the integer Network ID that has been loaded into Paramics.
     *
     * @return Network ID.
     */
    public int getParamicsNetworkLoaded()
    {
        return paramicsStatusReader.getNetworkID();
    }

    /**
     * Returns the current status of the Paramics traffic modeler.
     *
     * @return Current PARAMICS_STATUS
     */
    public PARAMICS_STATUS getParamicsStatus()
    {
        return paramicsStatusReader.getStatus();
    }

    /**
     * Updates the ParamicsIncidentWriter with new Incident information.
     *
     * @param xmlInc Updated Incident information.
     */
    public void updateIncident(XMLIncident xmlInc)
    {
        paramicsIncidentWriter.updateIncident(xmlInc);
    }

    /**
     * Updates the ParamicsIncidentWriter with the new Diversion information.
     *
     * @param theDiversion Updated diversion information.
     */
    public void updateDiversion(CMSInfo theDiversion)
    {
        paramicsIncidentWriter.updateDiversion(theDiversion);
    }

    /**
     * Notifies the ParamicsIncidentWriter to start the simulation.
     */
    public void startSimulation()
    {
        paramicsIncidentWriter.startSimulation();
    }

    /**
     * Notifies the ParamicsIncidentWriter to reset the simulation.
     */
    public void resetSimulation()
    {
        paramicsIncidentWriter.resetSimulation();
    }

    /**
     * Notifies the ParamicsIncidentWriter to send an Incident update.
     *
     * @param currentSimTime Current simulation time (in seconds).
     */
    public void sendIncidentUpdate(long currentSimTime)
    {
        paramicsIncidentWriter.sendUpdate(currentSimTime);
    }

    /**
     * Establish a connection to the remote ParamicsCommunicator. Register the
     * ParamicsStatusReader and update the current status to CONNECTED. If an
     * exception occurs in connecting to the ParamicsCommunicator, update the
     * current status to UNREACHABLE.
     */
    public void connectToParamics()
    {
        try
        {
            theCommunicator.connect();
            theCommunicator.registerReader(paramicsStatusReader);

            updateParamicsStatus(PARAMICS_STATUS.CONNECTED);
        } catch (IOException ioe)
        {
            paramLogger.logp(Level.SEVERE, "Coordinator", "connectToParamics",
                    "Communication error in connecting to Paramics.", ioe);

            updateParamicsStatus(PARAMICS_STATUS.UNREACHABLE);
        }
    }

    /**
     * Close the connection to the remote ParamicsCommunicator. Unregister all
     * Readers and Wrtiers from the ParamicsCommunicator. Reset the status
     * information in the ParamicsStatusReader. Update the current status to
     * DISCONNECTED.
     */
    public void disconnectFromParamics()
    {

        theCommunicator.disconnect();
        theCommunicator.unregisterReader(paramicsStatusReader);
        theCommunicator.unregisterWriter(paramicsIncidentWriter);
        theCommunicator.unregisterReader(paramicsCameraStatusReader);

        paramicsStatusReader.resetStatusInfo();

        updateParamicsStatus(PARAMICS_STATUS.DISCONNECTED);
    }

    /**
     * Updates the current paramics status. If the new status is LOADED, then
     * notify the ParamicsIncidentWriter that the network has been loaded and
     * register the ParamicsCameraStatusReader with the ParamicsCommunicator.
     * Notify the Coordinator of all Paramics status updates.
     *
     * @param newStatus New Paramics status.
     */
    public void updateParamicsStatus(PARAMICS_STATUS newStatus)
    {

        //the network has finished loading
        if (newStatus == PARAMICS_STATUS.LOADED)
        {

            paramicsIncidentWriter.networkLoaded();

            theCommunicator.registerReader(paramicsCameraStatusReader);

        } else if (newStatus == PARAMICS_STATUS.DROPPED)
        {
            paramLogger.logp(Level.WARNING, "Coordinator", "updateParamicsStatus",
                    "Connection to Paramics has been dropped.");
        }

        theCoordinator.setParamicsStatus(newStatus);

    }

    /**
     * If a connection has been made, register the ParamicsIncidentWriter and
     * use it to notify Paramics of the Network ID to load.
     *
     * @param networkID Network ID to load.
     * @throws SimulationException if a connection has not been made to the
     * remote ParamicsCommunicator.
     */
    public void loadParamicsNetwork(int networkID) throws SimulationException
    {

        if (theCommunicator.isConnected())
        {
            theCommunicator.registerWriter(paramicsIncidentWriter);
            paramicsIncidentWriter.loadNetwork(networkID);
        } else
        {
            throw new SimulationException(SimulationException.PARAMICS_NOT_CONNECTED);
        }
    }

    /**
     * Receive updated camera speed information. Notify the MediaManager with
     * the updated data.
     *
     * @param cameraID CCTV camera ID
     * @param avgSpeed_NE Average speed of traffic flowing N or E
     * @param avgSpeed_SW Average speed of traffic flowing S or W
     */
    public void updateCameraInfo(Integer cameraID, float avgSpeed_NE, float avgSpeed_SW)
    {
        theMediaMgr.updateCameraInfo(cameraID, avgSpeed_NE, avgSpeed_SW);
    }
}
