//   Copyright 2015 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.genericevents;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @author Patrick Harms
 */
public class GenericEventLogSplitter extends AbstractDefaultLogParser {

    /** */
    private Attributes sessionAttributes = null;
    
    /** */
    private Map<String, Map<String, String>> targets = new HashMap<>();
    
    /** */
    private List<Event> events = new LinkedList<>();
    
    /**
     * 
     */
    public void splitLogFile(File file, File destinationFolder, long timediff)
        throws SAXException, IOException
    {
        super.parseFile(file);
        
        List<List<Event>> splittedEvents = new LinkedList<>();
        LinkedList<Event> currentList = new LinkedList<>();
        splittedEvents.add(currentList);
        
        for (Event event : events) {
            if ((currentList.size() > 0) &&
                ((event.getTimestamp() - currentList.getLast().getTimestamp()) > timediff))
            {
                currentList = new LinkedList<>();
                splittedEvents.add(currentList);
            }
            
            currentList.add(event);
        }
        
        if (splittedEvents.size() == 1) {
            // simply copy the file
            FileUtils.copyFileToDirectory(file, destinationFolder);
        }
        else {
            Console.println("splitting " + file.getAbsolutePath());
            
            int index = 0;
            for (List<Event> session : splittedEvents) {
                PrintWriter out = null;
                try {
                    String fileName = file.getName().substring(0, file.getName().lastIndexOf('.')) +
                        "_" + index++ + file.getName().substring(file.getName().lastIndexOf('.'));
                    File destFile = new File(destinationFolder, fileName);
                    Console.println("writing " + destFile.getAbsolutePath());
                    
                    FileOutputStream fis = new FileOutputStream(destFile);
                    out = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
                    out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                    out.print("<session");
                    
                    for (int i = 0; i < sessionAttributes.getLength(); i++) {
                        out.print(' ');
                        out.print(sessionAttributes.getLocalName(i));
                        out.print("=\"");
                        out.print(StringTools.xmlEntityReplacement(sessionAttributes.getValue(i)));
                        out.print("\"");
                    }
                    
                    out.println(">");
                    
                    Set<String> loggedTargets = new HashSet<>();
                    for (Event event : session) {
                        String targetId = event.getParameter("targetId");
                        if (!loggedTargets.contains(targetId)) {
                            logTarget(targetId, out);
                            loggedTargets.add(targetId);
                        }
                        logEvent(event, out);
                    }
                    
                    
                    out.println("</session>");
                }
                finally {
                    if (out != null) {
                        out.close();
                    }
                }
            }
        }
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param event
     * @param out
     */
    private void logEvent(Event event, PrintWriter out) {
        out.print("<event type=\"");
        out.print(event.getType());
        out.println("\">");
        
        for (Map.Entry<String, String> parameter : event.getParameters().entrySet()) {
            logParam(parameter.getKey(), parameter.getValue(), out);
        }
        
        out.println("</event>");
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param targetId
     * @param out
     */
    private void logTarget(String targetId, PrintWriter out) {
        Map<String, String> parameters = targets.get(targetId);
        
        if (parameters.containsKey("parent")) {
            logTarget(parameters.get("parent"), out);
        }
        
        out.print("<target id=\"");
        out.print(targetId);
        out.println("\">");
    
        for (Map.Entry<String, String> param : targets.get(targetId).entrySet()) {
            logParam(param.getKey(), param.getValue(), out);
        }
    
        out.println("</target>");
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param key
     * @param value
     * @param out
     */
    private void logParam(String key, String value, PrintWriter out) {
        out.print(" <param name=\"");
        out.print(key);
        out.print("\" value=\"");
        out.print(StringTools.xmlEntityReplacement(value));
        out.println("\"/>");
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.plugin.genericevents.AbstractDefaultLogParser#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
    {
        super.startElement(uri, localName, qName, atts);
        
        if (qName.equals("session")) {
            sessionAttributes = atts;
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.plugin.genericevents.AbstractDefaultLogParser#handleTarget(java.lang.String, java.util.Map)
     */
    @Override
    protected boolean handleTarget(String id, Map<String, String> parameters) throws SAXException {
        targets.put(id, parameters);
        return true;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.plugin.genericevents.AbstractDefaultLogParser#handleEvent(java.lang.String, java.util.Map)
     */
    @Override
    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
        Event event = new Event(type, parameters);
        
        ListIterator<Event> iterator = events.listIterator();
        boolean added = false;
        
        while (iterator.hasNext()) {
            Event candidate = iterator.next();
            if (candidate.getTimestamp() > event.getTimestamp()) {
                iterator.previous();
                iterator.add(event);
                added = true;
                break;
            }
        }
        
        if (!added) {
            events.add(event);
        }
        
        return true;
    }

    
    /**
     * <p>
     * TODO comment
     * </p>
     * 
     * @author Patrick Harms
     */
    public class Event {

        private String type;
        private Map<String, String> parameters;

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param type
         * @param parameters
         */
        public Event(String type, Map<String, String> parameters) {
            this.type = type;
            this.parameters = parameters;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @return
         */
        public long getTimestamp() {
            return Long.parseLong(parameters.get("timestamp"));
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @return
         */
        public Map<String, String> getParameters() {
            return parameters;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @return
         */
        public String getType() {
            return type;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param string
         * @return
         */
        public String getParameter(String key) {
            return parameters.get(key);
        }

    }
    
}
