import java.util.*;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
/**
 * A class that extends DefaultHandler classes of Java Xml SAX API.
 * This class is used to get Xml data from the Xml parser and 
 * return them in defined formats (Hastable, Segmentation instances...).
 *
 * @author NIST-ITL-IAD-Retrieval Group
 * This software was produced by NIST, an agency of the
 * U.S. government, and by statute is not subject to copyright in the
 * United States. Recipients of this software assume all
 * responsibilities associated with its operation, modification and
 * maintenance.
 *
 */
public class xmlContentHandler extends DefaultHandler {

    /**
     *
     * NOTES (by Arjen P. De Vries - CWI.nl):
     *
     * This class reads the XML files into a Hashtable of TreeSets (Segmentation).
     * Upon finding a videoSearchResult or videoTopic tag, it associates in 
     * HashTable systemResults key <systemId,topicNo> with a new Segmentation
     * object, maintained in a global variable currentSeg.
     * Foreach Item or knownItem tag, this segmentation is then extended with 
     * the interval defined by the tag. When handling reference files, currentSeg
     * is a TreeSet created with an IntervalComparator, so the intervals are
     * ordered by source and on start time (and length if necessary) within
     * each source. 
     *
     */
	static SystemItem globalI1 = null;
    static private Writer  out;
    private int maxAllowedItems = 0;
    /**
     * The Hashtable used to store the system results,
     * the key is the system ID + the topicNumber, and value is a segmentation instance.
     *
     * @see #getSystemResults()
     */
	private Hashtable systemResults = new Hashtable();
	/**
     * Segmentation instance used to get the current Segmentation the parser is working on.
     * The items of this segmentation will be added when the parser calls startsElement() method
     * and the Xml element "<item ...>" and its attributes are recognized.
     *
     */
	private SysSegmentation currentSeg = null;	
	private RefSegmentation refSeg = null;
    /**
     * Returns the reference known-items parsed from one Xml file per topic in the reference.
     * Actually currentSeg is returned cause it contains either the current parsed reference 
     * segmentation or a segmentation of a system.
     */
	public RefSegmentation getRefSegmentation(){
		return refSeg;	
	}
	/**
     * Returns the systemResults parsed and contained in an Hashtable.     
     * 
     */	
	public Hashtable getSystemResults(){
		return systemResults;
	}
    /**
     * Constructs an instance of this class with the given Writer as the output
     * writer used to get information during parsing...
     * 
     */
	public xmlContentHandler(Writer out, int maxAllowedItems){ 
		this.out = out; 
		this.maxAllowedItems = maxAllowedItems;
	}
    /**
     * SAX DocumentHandler method.
     * 
     */
    public void setDocumentLocator(Locator l){
        // Save this to resolve relative URIs or to give diagnostics.
        try {
          if(l.getSystemId().indexOf("vt") == -1){
          	out.write("    "+l.getSystemId() );
          	out.flush();
          }
        } catch (IOException e) { }
    }
    /**
     * SAX DocumentHandler method.
     * 
     */
    public void startDocument() throws SAXException{ }
    /**
     * SAX DocumentHandler method.
     * 
     */
    public void endDocument() throws SAXException {
        try { out.flush(); } 
        catch (IOException e) { throw new SAXException("I/O error", e); }
    }
	
	/**
     * SAX DocumentHandler method used to recognized Xml tags and get the information in the required Hashtable
     * or any data structure for later use.
     * 
     */
    public void startElement(String namespaceURI,
                             String lName, // local name
                             String qName, // qualified name
                             Attributes attrs) throws SAXException {
        String eName = lName; // element name
        if ("".equals(eName)) eName = qName; // namespaceAware = false

		if(eName.equals("videoTopic")){
			String num = "";
			if (attrs != null) {
	        	for (int i = 0; i < attrs.getLength(); i++) {
	
	                String aName = attrs.getLocalName(i); // Attr name 
	                if ("".equals(aName)) aName = attrs.getQName(i);
	                if(aName.equals("num")) num = attrs.getValue(i);
	            }
			}
			refSeg = new RefSegmentation(IntervalComparator.getInstance(), num);
		}
		else if(eName.equals("knownItem")){
			try{
				String src = "";
				String start = "";
				String stop = "";
				if (attrs != null) {
	            	for (int i = 0; i < attrs.getLength(); i++) {
	
		                String aName = attrs.getLocalName(i); // Attr name 
		                if ("".equals(aName)) aName = attrs.getQName(i);
		                if(aName.equals("src")) src = attrs.getValue(i).toLowerCase();
		                if(aName.equals("start")) start = attrs.getValue(i);
		                if(aName.equals("stop")) stop = attrs.getValue(i);
		            }
				}
				KnownItem i = new KnownItem(start, stop, src);
				refSeg.add(i);								
			} catch (Exception e){
				System.out.println("Abnormal exiting - KnownItem definition error");
				System.out.println(e.getMessage());
				System.exit(0);	
			}
	    }
	    else if(eName.equals("item")){
			try{
				String src = "";
				String start = "";
				String stop = "";
				String seqNum = "";				
				if (attrs != null) {
	            	for (int i = 0; i < attrs.getLength(); i++) {
	
		                String aName = attrs.getLocalName(i); // Attr name 
		                if ("".equals(aName)) aName = attrs.getQName(i);
		                if(aName.equals("src")) src = attrs.getValue(i).toLowerCase();
		                if(aName.equals("start")) start = attrs.getValue(i);
		                if(aName.equals("stop")) stop = attrs.getValue(i);
		                if(aName.equals("seqNum")) seqNum = attrs.getValue(i);
		            }
				}
				SystemItem i = new SystemItem(start, stop, src, seqNum);
				currentSeg.add(i);
				if(currentSeg.getItemCount() > this.maxAllowedItems){
					System.out.println(System.getProperty("line.separator")+
										"Abnormal exiting - maximum allowed items exceeded");
					System.out.println("For the following (system-topic): ("+
										((SysSegmentation)currentSeg).getSysId()+"-"+
										((SysSegmentation)currentSeg).getTopicNumber()+")");
					System.exit(0);	
				}					
			} catch (Exception e){
				System.out.println("Abnormal exiting - SystemItem definition error");
				System.out.println(e.getMessage());
				System.exit(0);		
			}
	    }
		else if(eName.equals("videoSearchResult")){

			String sysId = "";
			String sType = "";
			String pType = "";
			String topicNumber = "";
			
			if (attrs != null) {
            	for (int i = 0; i < attrs.getLength(); i++) {

	                String aName = attrs.getLocalName(i); // Attr name 
	                if ("".equals(aName)) aName = attrs.getQName(i);
		            if(aName.equals("sysId")) sysId = attrs.getValue(i);
		            if(aName.equals("sType")) sType = attrs.getValue(i);
		            if(aName.equals("pType")) pType = attrs.getValue(i);
		            if(aName.equals("tNum")) topicNumber = normalizeTopicNumber(attrs.getValue(i));
	            }
			}			
			currentSeg = new SysSegmentation(RankComparator.getInstance(), sysId, topicNumber, pType, sType);
			systemResults.put(sysId+topicNumber, currentSeg);
		}
    }
	/**
     * Private method used to normalize a topic number to the required format of 3 digit like 001 for topic #1.
     * For example some time the topic number could be "1", this method permits to return "001" for further convenience.
     */
	private String normalizeTopicNumber(String topicNumber){
		
		String normalized = topicNumber;
		if(normalized.length() == 1)
			normalized = "00"+normalized;
		else if(normalized.length() == 2)
			normalized = "0"+normalized;
		else{
			try{
				Integer.parseInt(topicNumber);
			}
			catch(NumberFormatException ex){
				System.out.println("normalizing problem, topic#"+topicNumber);
				return topicNumber;
			}
		}
		return normalized;
	}
	/**
     * SAX DocumentHandler method.
     * 
     */
    public void endElement(String namespaceURI,
                           String sName, // simple name
                           String qName  // qualified name
                          ) throws SAXException {
    }
    /**
     * SAX DocumentHandler method.
     * 
     */
    public void characters(char buf[], int offset, int len) throws SAXException {
        emit("CHARS:   ");
        String s = new String(buf, offset, len);
        if (!s.trim().equals("")) emit(s);
    }
    /**
     * SAX DocumentHandler method.
     * 
     */
    public void ignorableWhitespace(char buf[], int offset, int len) throws SAXException { }
    /**
     * SAX DocumentHandler method.
     * 
     */
    public void processingInstruction(String target, String data) throws SAXException {
        emit("PROCESS: ");
        emit("<?"+target+" "+data+"?>");
    }
    /**
     * SAX ErrorHandler method. Treats validation errors as fatal
     * 
     */
    public void error(SAXParseException e) throws SAXParseException {
        throw e;
    }
    /**
     * SAX ErrorHandler method. Dumps warnings.
     * 
     */
    public void warning(SAXParseException err) throws SAXParseException {
        System.out.println("** Warning"
            + ", line " + err.getLineNumber()
            + ", uri " + err.getSystemId());
        System.out.println("   " + err.getMessage());
    }
    //===========================================================
    // Utility Methods ...
    //===========================================================
    /**
     * Wrap I/O exceptions in SAX exceptions, to suit handler signature 
     * requirements.
     * 
     */
    private void emit(String s) throws SAXException {
        try {
            out.write(s);
            out.flush();
        } catch (IOException e) {
            throw new SAXException("I/O error", e);
        }
    }
    /**
     * Start a new line and indent the next line appropriately.
     * 
     */
    private void nl() throws SAXException {
        String lineEnd =  System.getProperty("line.separator");
        try {
            out.write(lineEnd);
            //for (int i=0; i < indentLevel; i++) out.write(indentString);
        } catch (IOException e) {
            throw new SAXException("I/O error", e);
        }
    }
}
