package paramsim.paramicssimulator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * A class which will read a FileReader stream, parsing it as an XML document,
 * and return a IncidentStatus object which contains the parsed information.
 * 
 * @author Greg Eddington (geddingt@calpoly.edu)
 */
public class ParamicsIncidentReader 
{	
	/** Error Log **/
	private static Logger paramLogger = Logger.getLogger("paramsim.paramicssimulator");
	
	/** A SAX Handler that is used to parse received Incident Status Node. **/
	protected IncidentStatusHandler ish = null;
	
	/** The current networkId **/
	private int networkId = 1;
	
	/** The Paramics Simulation Info for the most recent incident read **/
	protected ParamicsSimulationInfo psi = null;
	
	/** Value (seconds since 1/1/1970) of input file's last modification time. */
	protected long lastModified = 0;
	
	/**
	 * Constructor.
	 */
	public ParamicsIncidentReader() 
	{		
		ish = new IncidentStatusHandler();
	}
	
	/**
	 * This method parses the received XML node with the local CameraStatusHandler.
	 * All updated camera information is sent to the ParamicsSimulationManager.
	 * 
	 * If a successful read of the file occurs, the file is written with an empty string
	 * and the date of the last modification is stored.
	 *
	 * @param filename The file to check for message in.
	 * @return A ParamicsSimulationInfo object containing the file's information
	 * 		   A null pointer on:
	 * 		     - The file does not exist
	 * 		     - No new modification
	 * 		     - An error opening file 		  
	 *           - An empty file
	 *           - An error parsing the file
	 *           
	 *         The caller is able to call this function to check if there are any new messages
	 *         in the file.  If there are, a PSI object is returned.  If there aren't any new
	 *         messages, a null pointer is returned.
	 */
	public ParamicsSimulationInfo parse(String filename) 
	{	
		ParamicsSimulationInfo psi = null;
		
		File f = new File(filename);
		BufferedReader r = null;
		
		// File does not exist: No information
		if (!(f.exists()))
		{
			return null;
		}
		
		// File not modified: No new information
		if (f.lastModified() <= lastModified)
		{
			return null;
		}
		
		try 
		{
			// Try to open the file
			r = new BufferedReader(new FileReader(f));
			
			// Read the file to the end
			StringBuilder xml = new StringBuilder("");
			String line = r.readLine();
			while (line != null)
			{
				xml.append(line + "\n");
				line = r.readLine();
			}
			
			// If the file has contents in it, read them and returned a PSI describing the contents
			if(xml.length() > 0) 
			{
				// Parse the file
				SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(new StringReader(xml.toString())), ish);
				psi = new ParamicsSimulationInfo(networkId, xml.toString());
			}
			else if (xml.length() == 0)
			{
				lastModified = modifyFile(f);
				r.close();
			}
		}
		catch (FileNotFoundException fnfe)
		{
			paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "parse", 
					filename + " dissapeared before reading.", fnfe);
			psi = null;
		}
		catch (IOException ioe)
		{
			paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "parse", 
					"Error while reading file " + filename + ".", ioe);
			psi = null;
		}
		catch (SAXException saxe)
		{
			paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "parse", 
					"Error while parsing file " + filename + ".", saxe);
			psi = null;
		}
		catch (ParserConfigurationException pce)
		{
			paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "parse", 
					"Error while configuring parser.", pce);
		}
		
		// If a incident was read
		if (psi != null)
		{
			lastModified = modifyFile(f);
			try
			{
				r.close();
			}
			catch (IOException e)
			{
				paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "parse", 
						"Error while closing file" + filename + ".", e);
			}			
		}
		
		// Return no object if there was an error while reading, or the psi created from
		// parsing the file if the operation was successful.
		return psi;
	}
	
	private long modifyFile(File f)
	{
		FileWriter w = null;
		
		// Write to the file to show that it was read.
		try
		{
			w = new FileWriter(f);
			w.write("");
			w.close();
		}
		catch (IOException e)
		{
			paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "parse", 
					"Error while writing to file " + f.getName() + " to show that file was read.", e);
		}
		
		// Save the last modified time
		return f.lastModified();
	}

    /**
     * Internal SAX Handler used to parse the Incident Status Document read by
     * the remote Status Reader.  The schema for this document is: <br/>
     *
	 * <CAD_DATA><br>
	 *    <Basic><br>
	 *       <Comm_Interval/><br/>
	 *       <Network_ID/><br/>
	 *       <Simulation/><br/>
	 *       <Incident/><br/>
	 *    </Basic>
	 *
	 *    <Simulation_Data>
	 *       <Simulation_speed/><br/>
	 *       <CAD_clock><br/>
	 *          <hour/><br/>
	 *          <minute/><br/>
	 *          <second/><br/>
	 *          <Location><br/>
	 *       </CAD_clock><br/>
	 *    </Simulation_Data>
	 *    
	 *    <CAD_Incidents>
	 *       <Incident><br/>
	 *          <Identifier/><br/>
	 *          <Status/><br/>
	 *          <Location><br/>
	 *              <Route/><br/>
	 *              <Direction/><br/>
	 *              <Location_type/><br/>
	 *              <Postmile/><br/>
	 *          </Location><br/>
	 *          <Incident_type/><br/>
	 *          <Lanes><br/>
	 *             <Lane_number/><br/>
	 *             ...
	 *          </Lanes><br/>
	 *       </Incident><br/>
	 *       ...
	 *    </CAD_Incidents>   
	 *    
	 *    <Management>
	 *       <Diversion>
     *          <Diversion_path>
     *             <Identifier/>
     *             <Percentage/>
     *          <Diversion_path>
     *          ...
     *       </Diversion>
     *       ...
	 *    </Management>
	 *  
	 * </CAD_DATA>
     */	
	protected class IncidentStatusHandler extends DefaultHandler 
	{
		private final String NETWORK_ID   = "Network_ID";
		
		/** A buffer for reading characters **/
		private StringBuffer parsedValue  = new StringBuffer();

		public void startDocument() 
		{ 
		}	
		
		/** Appends characters to the xmlMessage and parsedValue buffers **/
		public void characters(char[] ch, int start, int length) 
		{
			parsedValue.append(new String(ch, start, length).trim());
		}
		
	    public void startElement (String uri, String localName, String qName, Attributes attributes)
			throws SAXException
		{
	    }
		
		public void endElement(String uri, String localName, String qName)  
		{
			if(qName.equals(NETWORK_ID)) { networkId = Integer.parseInt(parsedValue.toString()); }
			
			parsedValue.setLength(0);
		}	
		
		public void error(SAXParseException e) 
		{
			paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "error", 
					"Error in parsing received incident status.", e);
		}
		
		public void fatalError(SAXParseException e) 
		{
			paramLogger.logp(Level.SEVERE, "ParamicsIncidentReader", "fatalError", 
					"Fatal error in parsing received incident status.", e);
		}
		
		public void warning(SAXParseException e) 
		{
			paramLogger.logp(Level.WARNING, "ParamicsIncidentReader", "warning", 
					"Warning in parsing received incident status.", e);
		}		
	}		
}