//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.plugin.html;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
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;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementTree;
import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.autoquest.plugin.html.eventcore.HTMLEventTypeFactory;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElementSpec;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * This class provides the functionality to parse XML log files generated by the HTMLMonitor of
 * autoquest. The result of parsing a file is a collection of event sequences.
 * </p>
 * 
 * @author Fabian Glaser
 * @version 1.0
 * 
 */
public class NewHTMLLogParser extends DefaultHandler {
    /**
     * <p>
     * Constructor. Creates a new HTMLLogParser.
     * </p>
     */
    public NewHTMLLogParser() {
        sequences = new LinkedList<List<Event>>();
    }

    /**
     * <p>
     * Collection of event sequences that is contained in the parsed log file.
     * </p>
     */
    private Collection<List<Event>> sequences;
    
    /**
     * <p>
     * Internal handle to the parsed GUI structure, stored in a GUIElementTree
     * </p>
     */
    private GUIElementTree<String> currentGUIElementTree;
    
    /**
     * <p>
     * Path of the GUI element currently being parsed.
     * </p>
     */
    private String currentGUIElementPath;
    
    /**
     * <p>
     * Path of the parent of the GUI element currently being parsed.
     * </p>
     */
    private String currentParentPath;
    
    /**
     * <p>
     * Source of the GUI element currently being parsed.
     * </p>
     */
    private String currentEventSource;
    
    /**
     * <p>
     * Timestamp of the event currently being parsed.
     * </p>
     */
    private Long currentEventTimestamp;
    
    /**
     * <p>
     * Internal handle to the parameters of the event currently being parsed.
     * </p>
     */
    private Map<String, String> currentEventParameters;
    
    /**
     * <p>
     * Internal handle to the sequence currently being parsed.
     * </p>
     */
    private List<Event> currentSequence;
    
    /**
     * <p>
     * Internal handle to type of the event currently being parsed.
     * </p>
     */
    private String currentEventType;
    
    /**
     * <p>
     * Class of the GUI element currently being parsed.
     * </p>
     */
    private String currentGUIElementClass;
    
    /**
     * <p>
     * Index of the GUI element currently being parsed.
     * </p>
     */
    private String currentGUIElementIndex;
    
    /**
     * Internal handle to the specification of the GUI element currently being parsed.
     */
    private HTMLGUIElementSpec currentGUIElementSpec;
    
    /**
     * <p>
     * internal handle to the GUI element of the previous event to be potentially reused for the
     * current
     * </p>
     */
    private IGUIElement lastGUIElement;
    
    /**
     * <p>
     * Parses a log file written by the HTMLMonitor and creates a collection of event sequences.
     * </p>
     * 
     * @param filename
     *          name and path of the log file
     */
    public void parseFile(String filename) {
        if (filename == null){
            throw new IllegalArgumentException("filename must not be null");
        }
        
        parseFile(new File(filename));
    }
    /**
     * <p>
     * Parses a log file written by the HTMLMonitor and creates a collection of event sequences.
     * </p>
     * @param file
     *          file to be parsed
     */
    public void parseFile(File file) {
        if (file == null){
            throw new IllegalArgumentException("file must not be null");
        }
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setValidating(true);
        SAXParser saxParser = null;
        InputSource inputSource = null;
        try{
            saxParser = spf.newSAXParser();
            inputSource =
                    new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
        }
        catch (UnsupportedEncodingException e){
            Console.printerr("Error parsing file " + file.getName());
            Console.logException(e);
            return;
        }
        catch (ParserConfigurationException e){
            Console.printerr("Error parsing file " + file.getName());
            Console.logException(e);
            return;
        }
        catch (SAXException e){
            Console.printerr("Error parsing file " + file.getName());
            Console.logException(e);
        }
        catch (FileNotFoundException e){
            Console.printerr("Error parsing file " + file.getName());
            Console.logException(e);
        }
        if (inputSource != null){
           inputSource.setSystemId("file://" + file.getAbsolutePath());
           try{
               if (saxParser == null){
                   throw new RuntimeException("SaxParser creation failed");
               }
               saxParser.parse(inputSource, this);
           }
           catch (SAXParseException e){
               Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
                                  ", column " + e.getColumnNumber() + ".");
               Console.logException(e);
           }
           catch (SAXException e){
               Console.printerr("Error parsing file " + file.getName());
               Console.logException(e);
               return;
           }
           catch (IOException e){
               Console.printerr("Error parsing file " + file.getName());
               Console.logException(e);
               return;
           }
        }  
    }

    /* (non-Javadoc)
     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts)
        throws SAXException
    {
        if (qName.equals("session")) {
             currentSequence = new LinkedList<Event>();
             if (currentGUIElementTree == null)
                     currentGUIElementTree = new GUIElementTree<String>();
        }
        else if (qName.equals("component")) {
            currentGUIElementPath = atts.getValue("path");
        }
        else if (qName.equals("event")) {
            currentEventType = atts.getValue("type");
            currentEventParameters = new HashMap<String, String>();
        }
        else if (qName.equals("param")){
            String paramName = atts.getValue("name");
            if (currentGUIElementPath != null){
                if ("parent".equals(paramName)){
                    currentParentPath = atts.getValue("value");
                }
                if ("class".equals(paramName)){
                    currentGUIElementClass = atts.getValue("value");
                }
                if ("index".equals(paramName)){
                    currentGUIElementIndex = atts.getValue("value");
                }
            }
            else if (currentEventType != null){
                if ("target".equals(paramName)){
                    currentEventSource = atts.getValue("value");
                }
                if ("timestamp".equals(paramName)){
                    currentEventTimestamp = Long.parseLong(atts.getValue("value"));
                }
                currentEventParameters.put(paramName, atts.getValue("value"));
            }
            else{
                throw new SAXException("param tag found where it should not be.");
            }
        }
        else{
            throw new SAXException("unknown tag found: " + qName);
        }

    }

    /* (non-Javadoc)
     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (qName.equals("session")) {
            if (currentSequence != null && !currentSequence.isEmpty()){
                sequences.add(currentSequence);
            }
            currentSequence = null;
        }
        else if (qName.equals("component") && currentGUIElementPath != null) {
            HTMLGUIElementSpec guiElementSpec = new HTMLGUIElementSpec(currentGUIElementClass);
            currentGUIElementTree.add(currentGUIElementPath, currentParentPath, guiElementSpec);
            
            currentParentPath = null;
            currentGUIElementPath = null;
        }
        else if (qName.equals("event")) {
            IGUIElement currentGUIElement;
            currentGUIElement = currentGUIElementTree.find(currentEventSource);
            
            Event event = new Event(HTMLEventTypeFactory.
                                    getInstance().getEventType(currentEventType, currentEventParameters),
                                    (currentGUIElement == null ? lastGUIElement : currentGUIElement));
            
            event.setTimestamp(currentEventTimestamp);
            HTMLGUIElement currentEventTarget = (HTMLGUIElement) event.getTarget();
            currentEventTarget.markUsed();
            currentSequence.add(event);
            
            currentEventSource = null;
            currentEventTimestamp = -1l;
            currentEventParameters = null;
            currentEventType = null;
            
            if (currentGUIElement != null){
                lastGUIElement = currentGUIElement;
            }
            
            currentGUIElement = null;
        }
    }
   
    /**
     * <p>
     * Returns a collection of event sequences that was obtained from parsing log files.
     * </p>
     * @return
     */
    public Collection<List<Event>> getSequences(){
        return sequences;
    }
    
    /**
     * <p>
     * Returns the GUI model that is obtained from parsing log files.
     * </p>
     * 
     * @return GUIModel
     */
    public GUIModel getGuiModel(){
        return currentGUIElementTree.getGUIModel();
    }

}
