package tmcsim.cadsimulator;

import java.io.File;
import java.io.FileInputStream;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.util.Calendar;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.JWindow;
import javax.swing.UIManager;
import javax.xml.parsers.DocumentBuilderFactory;
import tmcsim.cadsimulator.db.CMSDiversionDB;
import tmcsim.cadsimulator.managers.ATMSManager;
import tmcsim.cadsimulator.managers.IncidentManager;
import tmcsim.cadsimulator.managers.MediaManager;
import tmcsim.cadsimulator.managers.ParamicsSimulationManager;
import tmcsim.cadsimulator.managers.SimulationClockManager;
import tmcsim.cadsimulator.managers.TrafficModelManager;
import tmcsim.cadsimulator.viewer.model.CADSimulatorState;
import tmcsim.common.SimulationException;
import tmcsim.interfaces.CADViewer;

/**
 * CADSimulator is main class for the CAD Simulator application. At construction
 * the Coordinator, CoordinatorViewer, and all CAD Simulator Managers are
 * initialized and data relationships are established. Simulation control is
 * managed through the Coordinator and Managers. The CADSimulator contains the
 * instances of all Manager Objects that are used to control the Simulation flow
 * of data.<br>
 * <br>
 *
 * The CADSimulator is initialized with a properties file containing the
 * following data items:<br>
 * <code>
 * -----------------------------------------------------------------------------<br>
 * CADClientPort The port number to use for remote CAD Client connections.<br>
 * CoordinatorRMIPort The port number to use for binding the Coordinator.<br>
 * CMSDiversionXML The filepath for the xml file containing initialization data
 * for the Diversion "database." AudioFileLocation The root directory path where
 * audio files are referenced from.<br>
 * ParamicsProperties The filepath for the properties file to initialize the
 * ParamicsControlManager.<br>
 * ATMSProperties The filepath for the properties file to initialize the
 * ATMSManager.<br>
 * MediaProperties The filepath for the properties file to initialize the
 * MediaManager.<br>
 * ErrorFile The filename of the error file used for logging errors.<br>
 * ----------------------------------------------------------------------------<br>
 * Example File:<br>
 * CADClientPort = 4444<br>
 * CoordinatorRMIPort = 4445<br>
 * CMSDiversionXML = ../data/cmsdiversions.xml<br>
 * AudioFileLocation = ../audio/<br>
 * ParamicsProperties = ../config/paramics.properties<br>
 * ATMSProperties = ../config/atms.properties<br>
 * MediaProperties = ../config/media.properties<br>
 * ErrorFile = cad_sim_error.xml<br>
 * </code>
 *
 * @author Matthew Cechini (mcechini@calpoly.edu) jdalbey
 * @version $Date: 2009/04/17 16:27:46 $ $Revision: 1.5 $
 */
public class CADServer
{

    private static String CONFIG_FILE_NAME = "cad_simulator_config.properties";
    /**
     * Error logger.
     */
    private static Logger cadSimLogger = Logger.getLogger("tmcsim.cadsimulator");

    /**
     * Enumeration containing properties name values. See CADSimulator class
     * description for more information.
     *
     * @author Matthew
     * @see CADServer
     */
    private static enum CAD_PROPERTIES
    {

        /**
         * RMI port to accept CAD Client connections.
         */
        CLIENT_PORT("CADClientPort"),
        /**
         * RMI port to bind the Coordinator to for RMI communication.
         */
        COOR_RMI_PORT("CoordinatorRMIPort"),
        CAD_RMI_PORT("CADRmiPort"),
        /**
         * Filepath for xml file containing diversion data.
         */
        CMS_XML_FILE("CMSDiversionXML"),
        /**
         * Filepath for xml file containing dvd control data.
         */
        DVD_XML_FILE("DVDPlayerXML"),
        /**
         * Filepath for xml file containing still image control data.
         */
        IMAGE_XML_FILE("StillImagesXML"),
        /**
         * Root directory path where audio files are referenced from.
         */
        AUDIO_LOCATION("AudioFileLocation"),
        /**
         * Filepath for the properties file to initialize the media manager.
         */
        MEDIA_PROP_FILE("MediaProperties"),
        /**
         * Filepath for the properties file to initialize the paramics control
         * manager.
         */
        PARAMICS_PROP_FILE("ParamicsProperties"),
        /**
         * Filepath for the properties file to initialize the atms manager.
         */
        ATMS_PROP_FILE("ATMSProperties"),
        /**
         * Filepath for the properties file to initialize the traffic manager.
         */
        TRAFFICMGR_PROP_FILE("TrafficMgrProperties"),
        /**
         * Class name of desired user interface.
         */
        USER_INTERFACE("UserInterface"),
        /**
         *  Filepath to which is written the simulation time.
         */
        ELAPSED_TIME_FILE("ElapsedTimeFile");
        
        public String name;

        private CAD_PROPERTIES(String nam)
        {
            name = nam;
        }
    };
    /** NOTE: Protected fields are accessed by Coordinator */
    /**
     * CADSimulatorViewer instance.
     */
    protected static CADViewer theViewer;
    //protected static CADSimulatorViewer theViewer;
    //protected static CADConsoleViewer theConsole;
    protected static CADSimulatorState theModel;
    /**
     * Coordinator instance.
     */
    protected static Coordinator theCoordinator;
    /**
     * SoundPlayer instance.
     */
    protected static SoundPlayer theSoundPlayer = null;
    /**
     * SimulationControlManager instance.
     */
    protected static SimulationClockManager theSimulationCntrlMgr = null;
    /**
     * ParamicsSimulationManager instance.
     */
    protected static ParamicsSimulationManager theParamicsSimMgr = null;
    /**
     * IncidentManager instance.
     */
    protected static IncidentManager theIncidentMgr = null;
    /**
     * MediaManager instance.
     */
    protected static MediaManager theMediaMgr = null;
    /**
     * ATMSManager instance.
     */
    protected static ATMSManager theATMSMgr = null;
    /**
     * Traffic Model Manager instance
     */
    protected static TrafficModelManager theTrafficMgr = null;
    
    /**
     * Properties file for the CADSimulator.
     */
    private Properties cadServerProperties;

    /**
     * Constructor. Load the Properties file and initialize all CAD Simulator
     * Managers and establish Manager data relationships. A
     * CADSimulatorSocketHandler is instantiated and started to being listening
     * for remote CAD connections. The CMSDiversionDB is initialized with the
     * XML data(incomplete design).
     *
     * @param propertiesFile Filename of CAD Simulator properties file.
     * @throws SimulationException if there is an error in initializing the CAD
     * Simulator.
     */
    public CADServer(String propertiesFile) throws SimulationException
    {

        try
        {
            cadServerProperties = new Properties();
            cadServerProperties.load(new FileInputStream(propertiesFile));
            cadSimLogger.logp(Level.INFO, "CADSimulator", "Constructor",
                    "Properties loaded from " + propertiesFile);
        } catch (Exception e)
        {
            cadSimLogger.logp(Level.SEVERE, "CADSimulator", "Constructor",
                    "Exception in reading properties file.", e);
            JOptionPane.showMessageDialog(new JWindow(), e.getMessage() +
                    "\nUser.Dir=" + System.getProperty("user.dir"),
                    "Fatal Error - Exiting", JOptionPane.ERROR_MESSAGE);
                        
            throw new SimulationException(SimulationException.PROPERTY_READ_ERROR, e);
        }

        //Create the Coordinator and register it for RMI communicator.  Start the
        //CAD Simulator Socket Handler to begin to accept connections from CAD Clients.
        try
        {
            String userInterfaceName =
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.USER_INTERFACE.name);
            if (userInterfaceName == null)
            {
                cadSimLogger.logp(Level.SEVERE, "CADSimulator", "Constructor",
                        propertiesFile + " missing property for user interface.");
                throw new SimulationException(SimulationException.PROPERTY_MISSING_ERROR);
            }
            try
            {
                Class uiClass = Class.forName(userInterfaceName);
                theViewer = (CADViewer) uiClass.newInstance();
            } catch (Exception exc)
            {
                cadSimLogger.logp(Level.SEVERE, "CADSimulator", "Constructor",
                        "Unable to instantiate user interface: " + userInterfaceName
                        + " " + exc);
                throw new SimulationException(SimulationException.INSTANTIATION_FAILURE);
            }
            theModel = new CADSimulatorState();
            theModel.addObserver(theViewer);
            /** Load the simulation time filename from properties */
            String simTimeFilename =
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.ELAPSED_TIME_FILE.name);
            if (simTimeFilename == null)
            {
                cadSimLogger.logp(Level.SEVERE, "CADSimulator", "Constructor",
                        propertiesFile + " missing property for " + CAD_PROPERTIES.ELAPSED_TIME_FILE.name);
                throw new SimulationException(SimulationException.PROPERTY_MISSING_ERROR);
            }
            theCoordinator = new Coordinator(theModel, simTimeFilename);

            startRegistry(Integer.parseInt(
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.COOR_RMI_PORT.name).trim()));
            startRegistry(Integer.parseInt(
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.CAD_RMI_PORT.name).trim()));

            theSimulationCntrlMgr = new SimulationClockManager(theCoordinator);

            theATMSMgr = new ATMSManager(
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.ATMS_PROP_FILE.name));
            
            theTrafficMgr = new TrafficModelManager(
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.TRAFFICMGR_PROP_FILE.name),
                    theCoordinator);
            theTrafficMgr.addObserver(theViewer);
            theTrafficMgr.run();

            theMediaMgr = new MediaManager(
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.MEDIA_PROP_FILE.name),
                    theATMSMgr, theModel);

            theParamicsSimMgr = new ParamicsSimulationManager(
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.PARAMICS_PROP_FILE.name),
                    theCoordinator, theMediaMgr);

            theSoundPlayer = new SoundPlayer(
                    cadServerProperties.getProperty(
                    CAD_PROPERTIES.AUDIO_LOCATION.name));
            theSoundPlayer.start();

            theIncidentMgr = new IncidentManager(theCoordinator, theSoundPlayer);


            //Begin accepting Client connections
            CADSimulatorSocketHandler tmsh = new CADSimulatorSocketHandler(
                    Integer.parseInt(cadServerProperties.getProperty(
                    CAD_PROPERTIES.CLIENT_PORT.name).trim()));
            tmsh.start();
        } catch (RemoteException e)
        {
            cadSimLogger.logp(Level.SEVERE, "CADSimulator", "Constructor",
                    "Exception in starting Coordinator.", e);

            throw new SimulationException(SimulationException.BINDING, e);
        }

        //Load CMS Diversion Information from the XML file
        try
        {
            if (cadServerProperties.getProperty(
                    CAD_PROPERTIES.CMS_XML_FILE.name) != null)
            {
                CMSDiversionDB.getInstance().loadFromXML(
                        DocumentBuilderFactory.newInstance().newDocumentBuilder()
                        .parse(new File(cadServerProperties.getProperty(
                        CAD_PROPERTIES.CMS_XML_FILE.name))));
            }
        } catch (Exception e)
        {
            cadSimLogger.logp(Level.SEVERE, "CADSimulator", "Constructor",
                    "Exception in parsing CMSDiversion xml file.", e);

            JOptionPane.showMessageDialog(new JWindow(), "Unable to open "
                    + cadServerProperties.getProperty(CAD_PROPERTIES.CMS_XML_FILE.name),
                    "Initialization Error", JOptionPane.WARNING_MESSAGE);
        }

        theViewer.setVisible(true);

    }

    /**
     * Binds the Coordinator to an RMI port so that the SimulationManager can
     * communicate with it, and so that the Coordinator can perform RMI callback
     * method calls. The port numbers and RMI designators are parsed from the
     * properties file file.
     *
     * @param theCoor A reference to the Coordinator object.
     * @throws SimulationException if there are errors in binding the RMI to a
     * port and name.
     */
    private void startRegistry(Integer regPort) throws SimulationException
    {

        try
        {
//            if (LocateRegistry.getRegistry(regPort) == null)
//            {
            LocateRegistry.createRegistry(regPort);
            String registryURL = "rmi://localhost:" + regPort + "/coordinator";
            Naming.rebind(registryURL, theCoordinator);
//            }
        } catch (Exception e)
        {
            throw new SimulationException(SimulationException.BINDING, e);
        }
    }

    /**
     * Method returns a String representation of the current time. String format
     * is HHMM
     *
     * @return String representation of the current time.
     */
    public static String getCADTime()
    {
        String time = new String();

        Calendar rightNow = Calendar.getInstance();

        if (rightNow.get(Calendar.HOUR_OF_DAY) < 10)
        {
            time += "0";
        }

        time += (String.valueOf(rightNow.get(Calendar.HOUR_OF_DAY)));

        if (rightNow.get(Calendar.MINUTE) < 10)
        {
            time += "0";
        }

        time += (String.valueOf(rightNow.get(Calendar.MINUTE)));

        return time;
    }

    /**
     * Returns a string representation of the current date. String format is:
     * MMDDYY
     *
     * @return String format of the date.
     */
    public static String getCADDate()
    {
        String date = new String();

        Calendar rightNow = Calendar.getInstance();

        //Months are zero referenced
        if (rightNow.get(Calendar.MONTH) + 1 < 10)
        {
            date += "0";
        }

        date += (String.valueOf(rightNow.get(Calendar.MONTH) + 1));

        if (rightNow.get(Calendar.DAY_OF_MONTH) < 10)
        {
            date += "0";
        }

        date += (String.valueOf(rightNow.get(Calendar.DAY_OF_MONTH)));

        if (rightNow.get(Calendar.YEAR) % 1000 < 10)
        {
            date += "0";
        }

        date += (String.valueOf(rightNow.get(Calendar.YEAR) % 1000));

        return date;

    }

    /**
     * Main class. Instantiate a CAD Simulator with the properties file
     * specified on the command line or the default properties file
     *
     * @param args Command line arguments.
     */
    public static void main(String[] args)
    {
        if (System.getProperty("CONFIG_DIR") == null)
        {
            System.setProperty("CONFIG_DIR", "config");
        }
        if (System.getProperty("PROP_FILE") != null)
        {
            CONFIG_FILE_NAME = System.getProperty("PROP_FILE");
        }
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            String propFile = System.getProperty("CONFIG_DIR")
                    + System.getProperty("file.separator")
                    + CONFIG_FILE_NAME;
            new CADServer(propFile);
        } catch (Exception e)
        {
            cadSimLogger.logp(Level.SEVERE, "CADSimulator", "Main",
                    "Error initializing application.", e);

            JOptionPane.showMessageDialog(new JWindow(), e.getMessage(),
                    "Error - Program Exiting", JOptionPane.ERROR_MESSAGE);

            System.exit(-1);
        }

    }
}
