package tmcsim.cadsimulator.managers;

import tmcsim.highwaymodel.GoogleMapAnimator;
import tmcsim.highwaymodel.Highways;
import tmcsim.highwaymodel.LoopDetector;
import tmcsim.highwaymodel.TrafficEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.rmi.RemoteException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Properties;
import java.util.Scanner;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.JWindow;
import javax.swing.Timer;
import java.util.TimerTask;
import javax.swing.UIManager;
import tmcsim.cadsimulator.Coordinator;
import tmcsim.cadsimulator.viewer.model.CADSimulatorState;
import tmcsim.common.SimulationException;

/**
 * Traffic Model Manager is a model and controller for the Traffic Model
 * used in the simulation.  It represents all the highways and traffic
 * events that occur.
 * @author jdalbey
 * @version 2.0
 */
public class TrafficModelManager extends Observable
{
    private final static int ONE_SECOND = 1000;
    private static final int FEPSIM_INTERVAL = 30000;
    private final static SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");

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

    /**
     * Enumeration containing property names for Properties parsing.
     *
     * @author Matthew Cechini
     */
    private static enum PROPERTIES
    {
        HIGHWAYS_MAP_FILE("Highways_Map_File"),
        FEPSIM_IP_ADDR("FEPSim_IP_addr"),
        EVENTS_FILE("Events_File"),
        OUTPUT_DEST("Output_Destination"),
        HIGHWAY_STATUS_FILE("Highway_Status_File");
        
        public String name;

        private PROPERTIES(String n)
        {
            name = n;
        }
    };


    /**
     * Properties Object.
     */
    private Properties props = null;
    
    /** 
     * The Coordinator object from which we obtain the simulation clock.
     */ 
    private Coordinator theCoordinator;

    /**
     * Highways in traffic network
     */
    private Highways highways;

    /**
     * LinkedList of batch events
     */
    private LinkedList<TrafficEvent> eventQueue;
    /**
     * Map of incidents to events
     */
    private Map<String, List<TrafficEvent>> incidents;

    /**
     * Current simulation clock time
     */
    private String currentClock = "";
    
    /**
     * Path for writing highway data to Json file.
     */
    private String jsonPath;
    
    /**
     * Constructor. Loads the Properties file and initializes the
     * highway network model. 
     *
     * @param propertiesFile Target file path of properties file.
     * @param theCoordinator Reference to the simulation Coordinator from whom we get simulation
     * elapsed time.
     */
    public TrafficModelManager(String propertiesFile, final Coordinator theCoordinator) 
            throws SimulationException 
    {
        try 
        {
            props = loadProperties(propertiesFile);
            jsonPath = props.getProperty(PROPERTIES.HIGHWAY_STATUS_FILE.name);
            //logger.logp(Level.INFO, "Traffic Manager", "Constructor", 
            //        "Highway network json output: " + jsonPath);
            // Initialize the highway model
            incidents = new HashMap<String, List<TrafficEvent>>();
            highways = new Highways(
                    props.getProperty(PROPERTIES.HIGHWAYS_MAP_FILE.name)); 
            this.theCoordinator = theCoordinator;
        }
        catch (Exception e) 
        {
            logger.logp(Level.SEVERE, "Traffic Manager", "Constructor",
                    "Exception in parsing properties file.", e);
        }
    }
    /**
     * Load the traffic events and start processing the event queue.
     * Usage: addObserver must be called before calling run.
     */
    public void run()
    {
        loadEvents();

        java.util.Timer t = new java.util.Timer();

        // Create a timer that fetches the simulation time every second.
        t.scheduleAtFixedRate(
            new TimerTask()
            {
                // Every second, see if an event should be launched
                public void run()
                {
                    String currentATMStime = "";
                    Date simClock = new Date();
                    // Obtain the simulation time from the CAD server
                    try
                    {
                        long simtime = theCoordinator.getCurrentSimulationTime();
                        currentClock = theCoordinator.formatTimeInSeconds(simtime);
                        // For Debugging, show the ATMS time
    //                    long ATMStime = theCoorInt.getATMStime();       
    //                    Date atmsdate = new Date(ATMStime);
    //                    currentATMStime = formatter.format(atmsdate);
                        try
                        {
                            simClock = formatter.parse(currentClock);
                        }
                        catch (ParseException ex)
                        {
                            Logger.getLogger(TrafficModelManager.class.getName()).log(Level.SEVERE, null, ex);
                            System.out.println("Invalid simulation clock time found");
                            System.exit(-1);
                        }
                    }
                    catch (RemoteException ex)
                    {
                        System.out.println("Remote Exception reading sim or ATMS clock time");
                        Logger.getLogger(TrafficModelManager.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    // If we have any events left to process
                    if (!eventQueue.isEmpty())
                    {
                        // Get the time to launch the next event
                        TrafficEvent nextEvent = eventQueue.peek();
                        Date eventTime = nextEvent.eventDate;
                        // Check the queue of events to see if the first
                        // item should be launched.  IF so, 
                        // issue that command and remove it from queue.
                        if (eventTime.before(simClock) || eventTime.equals(simClock))
                        {
                            System.out.println("LAUNCHING EVENT: " + nextEvent.toString());
                            // apply colorization to highways
                            highways.applyColorToHighwayStretch(nextEvent.routeNumber, nextEvent.dir,
                                    nextEvent.postmile, nextEvent.range, nextEvent.color);
                            // Remove this event from the queue, we're done with it.
                            eventQueue.remove();
                            setChanged();
                            // Send updated list to view
                            // notifyObservers(getEventQueue());
                            // Notify view it should scroll to next event
                            notifyObservers(new Integer(0));
                        }
                        setChanged();
                        notifyObservers(currentClock);
                    }
                }
            },
            0, 
            ONE_SECOND
            );

        // Also write to json for google map display, every ten seconds.
        WriteToJsonTask wtJson = new WriteToJsonTask();
        wtJson.start();
        System.out.println("Traffic Model Mgr init complete.");
    }
    /** Accessor to event queue
     * 
     * @return defensive copy of current queue of events
     */
    public LinkedList<TrafficEvent> getEventQueue()
    {
        return new LinkedList<TrafficEvent>(eventQueue);
    }
    public Vector<String> getIncidents()
    {
        return new Vector<String>(incidents.keySet());
    }
    public String getClockTime()
    {
        return currentClock;
    }
    public void loadEvents()
    {
        // Read the text file of events and put in a queue
        FileInputStream fis = null;
        try
        {
            fis = new FileInputStream(props.getProperty(PROPERTIES.EVENTS_FILE.name));
        } catch (FileNotFoundException ex)
        {
            Logger.getLogger(TrafficModelManager.class.getName()).log(Level.SEVERE, null, 
                    "Missing Traffic Events file " + props.getProperty(PROPERTIES.EVENTS_FILE.name));
            System.exit(-1);
        }
        // Read all lines from the file of events
        Scanner fileScanner = new Scanner(fis);        
        eventQueue = readBatchFile(fileScanner);
        // Extract the incidents and create a map
        incidents = createIncidentMap(eventQueue);
        setChanged();
        notifyObservers("-:--");
        setChanged();
        notifyObservers(getIncidents());
        setChanged();
        notifyObservers(getEventQueue());
    }
    /**
     * This method verifies that the needed configuration properties are not
     * null. 
     *
     * @param propertiesFile File path (absolute or relative) to the properties
     * file containing configuration data.
     * @return The Properties loaded
     * @throws SimulationException if there is an exception in verifying the
     * properties file
     */
    public static Properties loadProperties(String propertiesFile)
            throws SimulationException
    {
        Properties props;
        // Load the properties file.
        try
        {
            props = new Properties();
            props.load(new FileInputStream(propertiesFile));

        } catch (Exception e)
        {
            throw new SimulationException(SimulationException.INITIALIZE_ERROR);
        }

        // Ensure that the properties file does not have null values for the
        // required information.
        if (props.getProperty(PROPERTIES.HIGHWAYS_MAP_FILE.name) == null
                || props.getProperty(PROPERTIES.FEPSIM_IP_ADDR.name) == null
                || props.getProperty(PROPERTIES.EVENTS_FILE.name) == null
                || props.getProperty(PROPERTIES.OUTPUT_DEST.name) == null)
        {
            System.out.println("Missing property value in "+propertiesFile);
            throw new SimulationException(SimulationException.INITIALIZE_ERROR);
        }

        return props;
    }
    /**
     * Read a file of traffic events.  
     * @param filename the name of the events file
     * @return the chronologically ordered list of events
     */
    public static LinkedList<TrafficEvent> readBatchFile(Scanner scan)
    {
        LinkedList<TrafficEvent> eventList = new LinkedList<TrafficEvent>();
        while (scan.hasNext())
        {
            // Read a line and add it to the event queue
            String line = scan.nextLine().trim();
            // Ignore blank lines and comments
            if (line.length() > 0 && line.charAt(0) != '#')
            {
                TrafficEvent evt;
                try
                {
                    evt = new TrafficEvent(line);
                    eventList.add(evt);
                }
                catch (ParseException ex)
                {
                    Logger.getLogger(TrafficModelManager.class.getName()).log(Level.SEVERE, null, ex);
                    System.out.println("Wrong format data in batch event file: " + line + " \nskipping.");
                    System.out.println("Skipping badly formatted event.");
                }
            }
        }
        System.out.println("Events file read, " + eventList.size() + " events queued.");
        // Put the events in chronological order
        Collections.sort(eventList);
        return eventList;
    }

    static Map<String, List<TrafficEvent>> createIncidentMap(LinkedList<TrafficEvent> eventList)
    {
        Map<String, List<TrafficEvent>> incidents = new HashMap<String, List<TrafficEvent>>();
        for (TrafficEvent evt: eventList)
        {
            // Add the line to the list for the corresponding incident
            List evtList;
            if (incidents.containsKey(evt.incident))
            {
                evtList = incidents.get(evt.incident);
            }
            else
            {
                evtList = new ArrayList<String>();
            }
            evtList.add(evt);
            // and put it back in the map
            incidents.put(evt.incident, evtList);
        }        
        return incidents;
    }
    
    /**
     * Clear an incident. For each event associated with an incident, turn the
     * dots in its range Green and remove it from the event queue.
     *
     * @param incidentNumber incident to be cleared.
     */
    public void clearIncident(String incidentNumber)
    {
        boolean ok = incidents.containsKey(incidentNumber);
        if (!ok)
        {
            System.out.println("Sorry, that incident number isn't found.");
            return;
        }
        System.out.println("Clearing incident " + incidentNumber);
        List<TrafficEvent> events = incidents.get(incidentNumber);
        // Process each event associated with this incident 
        for (TrafficEvent event : events)
        {
            System.out.println("Event: " + event + " cleared.");
            eventQueue.remove(event);

            // apply colorization to highways, forcing to green, indicating cleared
            highways.applyColorToHighwayStretch(event.routeNumber, event.dir,
                    event.postmile, event.range, LoopDetector.DOTCOLOR.GREEN);

        }
        incidents.remove(incidentNumber);
        // Now refresh the view with the updated queue of events
        setChanged();
        notifyObservers(getEventQueue());
        setChanged();
        notifyObservers(getIncidents());
    }

    /**
     * Format a time in seconds as HH:MM:SS
     *
     * @param seconds
     * @return HH:MM:SS formatted string
     */
//    public static String formatTimeInSeconds(final long seconds)
//    {
//        final long hr = TimeUnit.SECONDS.toHours(seconds);
//        final long min = TimeUnit.SECONDS.toMinutes(seconds - TimeUnit.HOURS.toSeconds(hr));
//        final long sec = TimeUnit.SECONDS.toSeconds(seconds - TimeUnit.HOURS.toSeconds(hr) - TimeUnit.MINUTES.toSeconds(min));
//        return String.format("%02d:%02d:%02d", hr, min, sec);
//    }

    /** Writes the highway model to a GeoJson file for reading
     *  by Google Maps.
     */
    class WriteToJsonTask
    {
        void start()
        {
            System.out.println("WriteToJson Task starting.");
            java.util.Timer tasktimer = new java.util.Timer();
            tasktimer.scheduleAtFixedRate(
                new TimerTask()
                {
                    public void run()
                    {
                        String geojson = highways.toJson();
                        PrintWriter out;
                        try
                        {
                            // currently writes to local file
                            out = new PrintWriter(jsonPath);
                            out.print(geojson);
                            out.close();
                        }
                        catch (FileNotFoundException ex)
                        {
                            Logger.getLogger(TrafficModelManager.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }    
                },
                0,   // run first occurrence now
                10000); // run every ten seconds
        }
    }
    
    
}
