package de.ugoe.cs.eventbench;

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.security.InvalidParameterException;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

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.eventbench.data.Event;
import de.ugoe.cs.eventbench.data.WindowTree;
import de.ugoe.cs.eventbench.data.WindowsMessage;
import de.ugoe.cs.eventbench.messagehandler.HandlerCreate;
import de.ugoe.cs.eventbench.messagehandler.HandlerDestroy;
import de.ugoe.cs.eventbench.messagehandler.HandlerSetText;
import de.ugoe.cs.eventbench.messagehandler.MessageHandler;
import de.ugoe.cs.eventbench.windowsdefs.MessageDefs;
import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.Console;

public class LogParser extends DefaultHandler {
	
	private MessageHandler currentHandler;
	
	private WindowsMessage currentMessage;
	
	private SequenceSplitter sequenceSplitter;
	
	private List<List<Event<WindowsMessage>>> sequences;
	
	private SortedMap<Integer, Integer> typeCounter;
	
	private boolean countMessageOccurences;
	
	public LogParser() {
		this(false);
	}
	
	public LogParser(boolean countMessageOccurences) {
		sequenceSplitter = new SequenceSplitter();
		sequences = new LinkedList<List<Event<WindowsMessage>>>();
		currentHandler = null;
		this.countMessageOccurences = countMessageOccurences;
		if( countMessageOccurences) {
			typeCounter = new TreeMap<Integer, Integer>();
		}
		
	}
	
	public List<List<Event<WindowsMessage>>> getSequences() {
		return sequences;
	}
	
	@Override
	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
		if( qName.equals("session") ) {
			Console.traceln("start of session");
			sequenceSplitter = new SequenceSplitter();
		}
		else if( qName.equals("msg") ) {
			String msgType = atts.getValue("type");
			int msgInt = -1;
			try {
				msgInt = Integer.parseInt(msgType);
				
				if( countMessageOccurences ) {
					Integer currentCount = typeCounter.get(msgInt);
					if( currentCount==null ) {
						typeCounter.put(msgInt, 1);
					} else {
						typeCounter.put(msgInt, currentCount+1);
					}
				}
				
				if( msgInt==MessageDefs.WM_CREATE ) {
					currentHandler = new HandlerCreate();
					currentHandler.onStartElement();
				}
				else if( msgInt==MessageDefs.WM_DESTROY ) {
					currentHandler = new HandlerDestroy();
					currentHandler.onStartElement();
				}
				else if( msgInt==MessageDefs.WM_SETTEXT ) {
					currentHandler = new HandlerSetText();
					currentHandler.onStartElement();
				} else {
					currentMessage = new WindowsMessage(msgInt);
				}
			} catch(NumberFormatException e) {
				Console.printerrln("Invalid message type: type not a number");
				e.printStackTrace();
			}
		}
		else if( qName.equals("param") ) {
			if( currentHandler!=null ) {
				currentHandler.onParameter(atts.getValue("name"), atts.getValue("value"));
			} else {
				currentMessage.addParameter(atts.getValue("name"), atts.getValue("value"));
			}
		}
	}
	
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if( qName.equals("msg") ) {
			if( currentHandler!=null ) {
				currentHandler.onEndElement();
				currentHandler = null;
			} else {
				try {
					currentMessage.setTarget(WindowTree.getInstance());
					sequenceSplitter.addMessage(currentMessage);
				} catch (InvalidParameterException e) {
					Console.traceln(e.getMessage() + " WindowsMessage " + currentMessage + " ignored.");
				}				
			}
		}
		else if(qName.equals("session")) {
			sequenceSplitter.endSession();
			sequences.add(sequenceSplitter.getSequence());
			Console.traceln("end of session");
		}
	}
	
	public void parseFile(String filename) {
		if( filename==null ) {
			throw new InvalidParameterException("filename 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(filename), "UTF-16"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		if( inputSource!=null ) {
	        inputSource.setSystemId("file://" + new File(filename).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() +".");
	        	e.printStackTrace();
			} catch (SAXException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if( countMessageOccurences ) {
			Console.println("Message statistics:");
			Console.println(typeCounter.toString().replace(" ", StringTools.ENDLINE).replaceAll("[\\{\\}]",""));
		}
	}
}
