/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.drools.eclipse.debug;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.StreamException;
import org.drools.core.audit.WorkingMemoryLog;
import org.drools.core.audit.event.ActivationLogEvent;
import org.drools.core.audit.event.LogEvent;
import org.drools.core.audit.event.ObjectLogEvent;
import org.drools.core.audit.event.RuleBaseLogEvent;
import org.drools.core.audit.event.RuleFlowGroupLogEvent;
import org.drools.core.audit.event.RuleFlowLogEvent;
import org.drools.core.audit.event.RuleFlowNodeLogEvent;
import org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.DroolsPluginImages;
import org.drools.eclipse.debug.actions.DeleteLogAction;
import org.drools.eclipse.debug.actions.FileAuditDropAdapter;
import org.drools.eclipse.debug.actions.OpenLogAction;
import org.drools.eclipse.debug.actions.RefreshLogAction;
import org.drools.eclipse.debug.actions.ShowEventCauseAction;
import org.eclipse.debug.ui.AbstractDebugView;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PartInitException;

import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

public class AuditView extends AbstractDebugView {

    private static final String LOG_FILE_NAME = "LogFileName";
    private static final String CAUSE_EVENT_COLOR = "CauseEventColor";

    private String logFileName;
    private IAction deleteAction;
    private IAction refreshAction;
    private boolean drools4 = false;

    protected Viewer createViewer(Composite parent) {
        final TreeViewer variablesViewer = new TreeViewer(parent);
        variablesViewer.setContentProvider(new AuditViewContentProvider());
        variablesViewer.setLabelProvider(new AuditLabelProvider());
        variablesViewer.setUseHashlookup(true);
        variablesViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                getViewer().refresh();
            }
        });
        
        int ops = DND.DROP_COPY | DND.DROP_MOVE;
        Transfer[] transfers = new Transfer[] { FileTransfer.getInstance()};
        variablesViewer.addDropSupport(ops, transfers, new FileAuditDropAdapter(variablesViewer,this));
        
        return variablesViewer;
    }
    
    public void setLogFile(String logFileName) {
        this.logFileName = logFileName;
        refresh();
        deleteAction.setEnabled(logFileName != null);
        refreshAction.setEnabled(logFileName != null);
    }
    
    @SuppressWarnings("unchecked")
    public void refresh() {
        drools4 = false;
        boolean reteoo = false;
        if (logFileName == null) {
            getViewer().setInput(null);
            return;
        }
        List<LogEvent> eventList = new ArrayList<LogEvent>();
        try {
            XStream xstream = new XStream();
            ObjectInputStream in = xstream.createObjectInputStream( new FileReader(logFileName) );
            try {
                while (true) {
                	Object object = in.readObject();
                    if (object instanceof WorkingMemoryLog) {
                    	WorkingMemoryLog log = (WorkingMemoryLog) object;
                    	eventList.addAll(log.getEvents());
                        // TODO check drools version based on the content of the xml file
                        // instead of using the actual runtime class.
                        reteoo |= isReteoo( log );
                    } else if (object instanceof LogEvent) {
                        eventList.add((LogEvent) object);
                    } else if (object instanceof List) {
                        drools4 = true;
                        eventList.addAll((List<LogEvent>) object);
                    } else {
                        throw new IllegalArgumentException("Unexpected element in log: " + object);
                    }
                }
            } catch (StreamException e) {
                if (!(e.getCause() instanceof EOFException)) {
                    throw e;
                }
            } catch (EOFException e) {
                // do nothing
            }
        } catch (FileNotFoundException e) {
            setLogFile(null);
        } catch (Throwable t) {
            DroolsEclipsePlugin.log(t);
        }
        if (reteoo) {
            getViewer().setInput(createEventList(eventList));
        } else if (drools4) {
            getViewer().setInput(createDrools4EventList(eventList));
        } else {
            getViewer().setInput(createPhreakEventList(eventList));
        }
        // TODO: this is necessary because otherwise, the show cause action
        // cannot find the cause event if it hasn't been shown yet
        ((TreeViewer) getViewer()).expandAll();
    }

    private boolean isReteoo(WorkingMemoryLog log) {
        // From Drools 7 phreak is the only available engine.
        // Tries to check via reflection if the WorkingMemoryLog class has a getEngine() method
        // (only available on drools 6) and use it if so.
        try {
            Method m = log.getClass().getMethod( "getEngine" );
            return ((String)m.invoke( log )).equalsIgnoreCase("RETEOO");
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            return false;
        }
    }
    
    protected List<Event> createPhreakEventList(List<LogEvent> logEvents) {
        Map<Long, Event> objectMap = new HashMap<Long, Event>();
        Map<String, Event> activationMap = new HashMap<String, Event>();
        Stack<Event> beforeEvents = new Stack<Event>();

        List<Event> events = new ArrayList<Event>();
        for (LogEvent logEvent : logEvents) {
            Event event = new Event(logEvent.getType());
            switch (logEvent.getType()) {
            	case LogEvent.INSERTED:
                    ObjectLogEvent objectEvent = (ObjectLogEvent) logEvent;
                    event.setString("Object inserted (" + objectEvent.getFactId() + "): " + objectEvent.getObjectToString());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    objectMap.put( objectEvent.getFactId(), event );
                    break;
                case LogEvent.UPDATED:
                    objectEvent = (ObjectLogEvent) logEvent;
                    event.setString("Object updated (" + objectEvent.getFactId() + "): " + objectEvent.getObjectToString());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    objectMap.put( objectEvent.getFactId(), event );
                    break;
                case LogEvent.RETRACTED:
                    objectEvent = (ObjectLogEvent) logEvent;
                    event.setString("Object removed (" + objectEvent.getFactId() + "): " + objectEvent.getObjectToString());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    objectMap.put( objectEvent.getFactId(), event );
                    break;
                case LogEvent.ACTIVATION_CREATED:
                    ActivationLogEvent activationEvent = (ActivationLogEvent) logEvent;
                    event.setString("Activation created: Rule " + activationEvent.getRule() + " " + activationEvent.getDeclarations());
                    Event mostRecentObjEvent = null;
                    for (Long fhId : getEventFactHandleIds(activationEvent)) {
                    	Event objEvent = objectMap.get(fhId);
                    	if (objEvent != null && (mostRecentObjEvent == null || mostRecentObjEvent.recency < objEvent.recency)) {
                    		mostRecentObjEvent = objEvent;
                    	}
                    }
                	if (mostRecentObjEvent != null) {
                		mostRecentObjEvent.addSubEvent(event);
                	}
                    activationMap.put(activationEvent.getActivationId(), event);
                    break;
                case LogEvent.ACTIVATION_CANCELLED:
                	activationEvent = (ActivationLogEvent) logEvent;
                    event.setString("Activation cancelled: Rule " + activationEvent.getRule() + " " + activationEvent.getDeclarations());
                    mostRecentObjEvent = null;
                    for (Long fhId : getEventFactHandleIds(activationEvent)) {
                    	Event objEvent = objectMap.get(fhId);
                    	if (objEvent != null && (mostRecentObjEvent == null || mostRecentObjEvent.recency < objEvent.recency)) {
                    		mostRecentObjEvent = objEvent;
                    	}
                    }
                	if (mostRecentObjEvent != null) {
                		mostRecentObjEvent.addSubEvent(event);
                	}
                    event.setCauseEvent(activationMap.get(activationEvent.getActivationId()));
                    break;
                case LogEvent.BEFORE_ACTIVATION_FIRE:
                	activationEvent = (ActivationLogEvent) logEvent;
                    event.setString("Activation executed: Rule " + activationEvent.getRule() + " " + activationEvent.getDeclarations());
                    events.add(event);
                    beforeEvents.push(event);
                    event.setCauseEvent(activationMap.get(activationEvent.getActivationId()));
                    break;
                case LogEvent.AFTER_ACTIVATION_FIRE:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_CREATED:
                    RuleFlowLogEvent ruleFlowEvent = (RuleFlowLogEvent) logEvent;
                    event.setString("Process started: " + ruleFlowEvent.getProcessName() + "[" + ruleFlowEvent.getProcessId() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_CREATED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_COMPLETED:
                	ruleFlowEvent = (RuleFlowLogEvent) logEvent;
                    event.setString("Process completed: " + ruleFlowEvent.getProcessName() + "[" + ruleFlowEvent.getProcessId() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_COMPLETED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_NODE_TRIGGERED:
                    RuleFlowNodeLogEvent ruleFlowNodeEvent = (RuleFlowNodeLogEvent) logEvent;
                    event.setString("Process node triggered: " + ruleFlowNodeEvent.getNodeName() + " in process " + ruleFlowNodeEvent.getProcessName() + "[" + ruleFlowNodeEvent.getProcessId() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_NODE_TRIGGERED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_GROUP_ACTIVATED:
                    RuleFlowGroupLogEvent ruleFlowGroupEvent = (RuleFlowGroupLogEvent) logEvent;
                    event.setString("RuleFlow Group activated: " + ruleFlowGroupEvent.getGroupName() + "[size=" + ruleFlowGroupEvent.getSize() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_GROUP_ACTIVATED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_GROUP_DEACTIVATED:
                	ruleFlowGroupEvent = (RuleFlowGroupLogEvent) logEvent;
                    event.setString("RuleFlow Group deactivated: " + ruleFlowGroupEvent.getGroupName() + "[size=" + ruleFlowGroupEvent.getSize() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_GROUP_DEACTIVATED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_PACKAGE_ADDED:
                    RuleBaseLogEvent ruleBaseEvent = (RuleBaseLogEvent) logEvent;
                    event.setString("Package added: " + ruleBaseEvent.getPackageName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_PACKAGE_ADDED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_PACKAGE_REMOVED:
                    ruleBaseEvent = (RuleBaseLogEvent) logEvent;
                    event.setString("Package removed: " + ruleBaseEvent.getPackageName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_PACKAGE_REMOVED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULE_ADDED:
                    ruleBaseEvent = (RuleBaseLogEvent) logEvent;
                    event.setString("Rule added: " + ruleBaseEvent.getRuleName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULE_ADDED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULE_REMOVED:
                    ruleBaseEvent = (RuleBaseLogEvent) logEvent;
                    event.setString("Rule removed: " + ruleBaseEvent.getRuleName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULE_REMOVED:
                    beforeEvents.pop();
                    break;
                default:
                    // do nothing
                    break;
            }
       	
        }
        return events;
    }
    
    private Long[] getEventFactHandleIds(ActivationLogEvent event) {
    	String[] ids = event.getFactHandleIds().split(",");
    	Long[] longIds = new Long[ids.length];
    	for (int i = 0; i < ids.length; i++) {
    		longIds[i] = Long.parseLong(ids[i]);
    	}
    	return longIds;
    }
    
    protected List<Event> createEventList(List<LogEvent> logEvents) {
        Iterator<LogEvent> iterator = logEvents.iterator();
        List<Event> events = new ArrayList<Event>();
        Stack<Event> beforeEvents = new Stack<Event>();
        List<Event> newActivations = new ArrayList<Event>();
        Map<String, Event> activationMap = new HashMap<String, Event>();
        Map<Long, Event> objectMap = new HashMap<Long, Event>();
        while (iterator.hasNext()) {
            LogEvent inEvent = iterator.next();
            Event event = new Event(inEvent.getType());
            switch (inEvent.getType()) {
                case LogEvent.INSERTED:
                    ObjectLogEvent inObjectEvent = (ObjectLogEvent) inEvent;
                    event.setString("Object inserted (" + inObjectEvent.getFactId() + "): " + inObjectEvent.getObjectToString());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    objectMap.put(new Long(((ObjectLogEvent) inEvent).getFactId()), event);
                    break;
                case LogEvent.UPDATED:
                    inObjectEvent = (ObjectLogEvent) inEvent;
                    event.setString("Object updated (" + inObjectEvent.getFactId() + "): " + inObjectEvent.getObjectToString());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    Event assertEvent = objectMap.get(new Long(((ObjectLogEvent) inEvent).getFactId()));
                    if (assertEvent != null) {
                        event.setCauseEvent(assertEvent);
                    }
                    break;
                case LogEvent.RETRACTED:
                    inObjectEvent = (ObjectLogEvent) inEvent;
                    event.setString("Object removed (" + inObjectEvent.getFactId() + "): " + inObjectEvent.getObjectToString());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    assertEvent = objectMap.get(new Long(((ObjectLogEvent) inEvent).getFactId()));
                    if (assertEvent != null) {
                        event.setCauseEvent(assertEvent);
                    }
                    break;
                case LogEvent.ACTIVATION_CREATED:
                    ActivationLogEvent inActivationEvent = (ActivationLogEvent) inEvent;
                    event.setString("Activation created: Rule " + inActivationEvent.getRule() + " " + inActivationEvent.getDeclarations());
                    newActivations.add(event);
                    activationMap.put(((ActivationLogEvent) inEvent).getActivationId(), event);
                    break;
                case LogEvent.ACTIVATION_CANCELLED:
                    inActivationEvent = (ActivationLogEvent) inEvent;
                    event.setString("Activation cancelled: Rule " + inActivationEvent.getRule() + " " + inActivationEvent.getDeclarations());
                    newActivations.add(event);
                    event.setCauseEvent(activationMap.get(((ActivationLogEvent) inEvent).getActivationId()));
                    break;
                case LogEvent.BEFORE_ACTIVATION_FIRE:
                    inActivationEvent = (ActivationLogEvent) inEvent;
                    event.setString("Activation executed: Rule " + inActivationEvent.getRule() + " " + inActivationEvent.getDeclarations());
                    events.add(event);
                    beforeEvents.push(event);
                    event.setCauseEvent(activationMap.get(((ActivationLogEvent) inEvent).getActivationId()));
                    break;
                case LogEvent.AFTER_ACTIVATION_FIRE:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_CREATED:
                    RuleFlowLogEvent inRuleFlowEvent = (RuleFlowLogEvent) inEvent;
                    event.setString("Process started: " + inRuleFlowEvent.getProcessName() + "[" + inRuleFlowEvent.getProcessId() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_CREATED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_COMPLETED:
                    inRuleFlowEvent = (RuleFlowLogEvent) inEvent;
                    event.setString("Process completed: " + inRuleFlowEvent.getProcessName() + "[" + inRuleFlowEvent.getProcessId() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_COMPLETED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_NODE_TRIGGERED:
                    RuleFlowNodeLogEvent inRuleFlowNodeEvent = (RuleFlowNodeLogEvent) inEvent;
                    event.setString("Process node triggered: " + inRuleFlowNodeEvent.getNodeName() + " in process " + inRuleFlowNodeEvent.getProcessName() + "[" + inRuleFlowNodeEvent.getProcessId() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_NODE_TRIGGERED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_GROUP_ACTIVATED:
                    RuleFlowGroupLogEvent inRuleFlowGroupEvent = (RuleFlowGroupLogEvent) inEvent;
                    event.setString("RuleFlow Group activated: " + inRuleFlowGroupEvent.getGroupName() + "[size=" + inRuleFlowGroupEvent.getSize() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_GROUP_ACTIVATED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULEFLOW_GROUP_DEACTIVATED:
                    inRuleFlowGroupEvent = (RuleFlowGroupLogEvent) inEvent;
                    event.setString("RuleFlow Group deactivated: " + inRuleFlowGroupEvent.getGroupName() + "[size=" + inRuleFlowGroupEvent.getSize() + "]");
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULEFLOW_GROUP_DEACTIVATED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_PACKAGE_ADDED:
                    RuleBaseLogEvent ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Package added: " + ruleBaseEvent.getPackageName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_PACKAGE_ADDED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_PACKAGE_REMOVED:
                    ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Package removed: " + ruleBaseEvent.getPackageName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_PACKAGE_REMOVED:
                    beforeEvents.pop();
                    break;
                case LogEvent.BEFORE_RULE_ADDED:
                    ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Rule added: " + ruleBaseEvent.getRuleName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULE_ADDED:
                    if (!beforeEvents.isEmpty()) {
                        Event beforeEvent = beforeEvents.pop();
                        beforeEvent.addSubEvents(newActivations);
                        newActivations.clear();
                    }
                    break;
                case LogEvent.BEFORE_RULE_REMOVED:
                    ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Rule removed: " + ruleBaseEvent.getRuleName());
                    if (!beforeEvents.isEmpty()) {
                        beforeEvents.peek().addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    beforeEvents.push(event);
                    break;
                case LogEvent.AFTER_RULE_REMOVED:
                    if (!beforeEvents.isEmpty()) {
                        Event beforeEvent = beforeEvents.pop();
                        beforeEvent.addSubEvents(newActivations);
                        newActivations.clear();
                    }
                    break;
                default:
                    // do nothing
                    break;
            }
        }
        return events;
    }
    
    protected List<Event> createDrools4EventList(List<LogEvent> logEvents) {
        Iterator<LogEvent> iterator = logEvents.iterator();
        List<Event> events = new ArrayList<Event>();
        Event currentBeforeActivationEvent = null;
        Event currentBeforePackageEvent = null;
        List<Event> newActivations = new ArrayList<Event>();
        Map<String, Event> activationMap = new HashMap<String, Event>();
        Map<Long, Event> objectMap = new HashMap<Long, Event>();
        while (iterator.hasNext()) {
            LogEvent inEvent = iterator.next();
            Event event = new Event(inEvent.getType());
            switch (inEvent.getType()) {
                case LogEvent.INSERTED:
                    ObjectLogEvent inObjectEvent = (ObjectLogEvent) inEvent;
                    event.setString("Object inserted (" + inObjectEvent.getFactId() + "): " + inObjectEvent.getObjectToString());
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    objectMap.put(new Long(((ObjectLogEvent) inEvent).getFactId()), event);
                    break;
                case LogEvent.UPDATED:
                    inObjectEvent = (ObjectLogEvent) inEvent;
                    event.setString("Object updated (" + inObjectEvent.getFactId() + "): " + inObjectEvent.getObjectToString());
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    Event assertEvent = objectMap.get(new Long(((ObjectLogEvent) inEvent).getFactId()));
                    if (assertEvent != null) {
                        event.setCauseEvent(assertEvent);
                    }
                    break;
                case LogEvent.RETRACTED:
                    inObjectEvent = (ObjectLogEvent) inEvent;
                    event.setString("Object removed (" + inObjectEvent.getFactId() + "): " + inObjectEvent.getObjectToString());
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    assertEvent = objectMap.get(new Long(((ObjectLogEvent) inEvent).getFactId()));
                    if (assertEvent != null) {
                        event.setCauseEvent(assertEvent);
                    }
                    break;
                case LogEvent.ACTIVATION_CREATED:
                    ActivationLogEvent inActivationEvent = (ActivationLogEvent) inEvent;
                    event.setString("Activation created: Rule " + inActivationEvent.getRule() + " " + inActivationEvent.getDeclarations());
                    newActivations.add(event);
                    activationMap.put(((ActivationLogEvent) inEvent).getActivationId(), event);
                    break;
                case LogEvent.ACTIVATION_CANCELLED:
                    inActivationEvent = (ActivationLogEvent) inEvent;
                    event.setString("Activation cancelled: Rule " + inActivationEvent.getRule() + " " + inActivationEvent.getDeclarations());
                    newActivations.add(event);
                    event.setCauseEvent(activationMap.get(((ActivationLogEvent) inEvent).getActivationId()));
                    break;
                case LogEvent.BEFORE_ACTIVATION_FIRE:
                    inActivationEvent = (ActivationLogEvent) inEvent;
                    event.setString("Activation executed: Rule " + inActivationEvent.getRule() + " " + inActivationEvent.getDeclarations());
                    events.add(event);
                    currentBeforeActivationEvent = event;
                    event.setCauseEvent(activationMap.get(((ActivationLogEvent) inEvent).getActivationId()));
                    break;
                case LogEvent.AFTER_ACTIVATION_FIRE:
                    currentBeforeActivationEvent = null;
                    break;
                case 8:
                    RuleFlowLogEvent inRuleFlowEvent = (RuleFlowLogEvent) inEvent;
                    event.setString("RuleFlow started: " + inRuleFlowEvent.getProcessName() + "[" + inRuleFlowEvent.getProcessId() + "]");
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    break;
                case 9:
                    inRuleFlowEvent = (RuleFlowLogEvent) inEvent;
                    event.setString("RuleFlow completed: " + inRuleFlowEvent.getProcessName() + "[" + inRuleFlowEvent.getProcessId() + "]");
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    break;
                case 10:
                    RuleFlowGroupLogEvent inRuleFlowGroupEvent = (RuleFlowGroupLogEvent) inEvent;
                    event.setString("RuleFlowGroup activated: " + inRuleFlowGroupEvent.getGroupName() + "[size=" + inRuleFlowGroupEvent.getSize() + "]");
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    break;
                case 11:
                    inRuleFlowGroupEvent = (RuleFlowGroupLogEvent) inEvent;
                    event.setString("RuleFlowGroup deactivated: " + inRuleFlowGroupEvent.getGroupName() + "[size=" + inRuleFlowGroupEvent.getSize() + "]");
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    break;
                case 12:
                    RuleBaseLogEvent ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Package added: " + ruleBaseEvent.getPackageName());
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    currentBeforePackageEvent = event;
                    break;
                case 13:
                    currentBeforePackageEvent = null;
                    break;
                case 14:
                    ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Package removed: " + ruleBaseEvent.getPackageName());
                    if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    currentBeforePackageEvent = event;
                    break;
                case 15:
                    currentBeforePackageEvent = null;
                    break;
                case 17:
                    ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Rule added: " + ruleBaseEvent.getRuleName());
                    if (currentBeforePackageEvent != null) {
                        currentBeforePackageEvent.addSubEvent(event);
                    } else if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    break;
                case 19:
                    ruleBaseEvent = (RuleBaseLogEvent) inEvent;
                    event.setString("Rule removed: " + ruleBaseEvent.getRuleName());
                    if (currentBeforePackageEvent != null) {
                        currentBeforePackageEvent.addSubEvent(event);
                    } else if (currentBeforeActivationEvent != null) {
                        currentBeforeActivationEvent.addSubEvent(event);
                    } else {
                        events.add(event);
                    }
                    event.addSubEvents(newActivations);
                    newActivations.clear();
                    break;
            }
        }
        return events;
    }

    public void deleteLog() {
        if (logFileName != null) {
            File file = new File(logFileName);
            try {
                file.delete();
                // TODO delete file cause this doesn't seem to work
                setLogFile(null);
                refresh();
            } catch (Throwable t) {
                t.printStackTrace();
                DroolsEclipsePlugin.log(t);
            }
        }
    }
    
    protected void becomesVisible() {
        refresh();
    }
    
    protected String getHelpContextId() {
        return null;
    }
    
    public Event getSelectedEvent() {
        ISelection selection = getViewer().getSelection();
        if (selection instanceof IStructuredSelection) {
            Object selected = ((IStructuredSelection) selection).getFirstElement();
            if (selected instanceof Event) {
                return (Event) selected;
            }
        }
        return null;
    }
    
    public void showEvent(Event event) {
        ((TreeViewer) getViewer()).reveal(event);
    }

    protected void fillContextMenu(IMenuManager menu) {
        Event selected = getSelectedEvent();
        if (selected != null) {
            Event causeEvent = selected.getCauseEvent();
            if (causeEvent != null) {
                menu.add(getAction("ShowEventCause"));
            }
        }
        menu.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    protected void createActions() {
        deleteAction = new DeleteLogAction(this);
        setAction("ClearLog", deleteAction);
        deleteAction.setEnabled(logFileName != null);
        refreshAction = new RefreshLogAction(this);
        setAction("RefreshLog", refreshAction);
        refreshAction.setEnabled(logFileName != null);
        IAction action = new OpenLogAction(this);
        setAction("OpenLog", action);
        action = new ShowEventCauseAction(this);
        setAction("ShowEventCause", action);
    }

    protected void configureToolBar(IToolBarManager tbm) {
        tbm.add(getAction("OpenLog"));
        tbm.add(getAction("RefreshLog"));
        tbm.add(getAction("ClearLog"));
    }
    
    public void saveState(IMemento memento) {
        memento.putString(LOG_FILE_NAME, logFileName);
    }
    
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        if (memento != null) {
            logFileName = memento.getString(LOG_FILE_NAME);
        }
    }
    
    public static class Event {

    	private static long counter = 0;
    	private final long recency = counter++;
    	
        private String toString;
        private int type;
        private List<Event> subEvents = new ArrayList<Event>();
        private Event causeEvent;

        public Event(int type) {
            this.type = type;
        }

        public void setString(String toString) {
            this.toString = toString;
        }

        public String toString() {
            return toString;
        }

        public int getType() {
            return type;
        }

        public void addSubEvent(Event subEvent) {
            subEvents.add(subEvent);
        }

        public void addSubEvents(Collection<Event> subEvents) {
            this.subEvents.addAll(subEvents);
        }

        public Object[] getSubEvents() {
            return subEvents.toArray();
        }

        public boolean hasSubEvents() {
            return !subEvents.isEmpty();
        }

        public void setCauseEvent(Event causeEvent) {
            this.causeEvent = causeEvent;
        }

        public Event getCauseEvent() {
            return causeEvent;
        }
    }

    public class AuditLabelProvider extends LabelProvider implements IColorProvider {

        public Color getForeground(Object element) {
            return null;
        }

        public Color getBackground(Object element) {
            Event selected = getSelectedEvent();
            if (selected != null) {
                if (element.equals(selected.getCauseEvent())) {
                    Color color = DroolsEclipsePlugin.getDefault().getColor(CAUSE_EVENT_COLOR);
                    if (color == null) {
                        color = new Color(getControl().getDisplay(), 0, 255, 0);
                        DroolsEclipsePlugin.getDefault().setColor(CAUSE_EVENT_COLOR, color);
                    }
                    return color;
                }
            }
            return null;
        }

        public Image getImage(Object element) {
            if (element instanceof Event) {
                int type = ((Event) element).getType();
                if (drools4) {
                    switch (type) {
                        case LogEvent.INSERTED: return DroolsPluginImages.getImage(DroolsPluginImages.INSERT);
                        case LogEvent.UPDATED: return DroolsPluginImages.getImage(DroolsPluginImages.UPDATE);
                        case LogEvent.RETRACTED: return DroolsPluginImages.getImage(DroolsPluginImages.RETRACT);
                        case LogEvent.ACTIVATION_CREATED: return DroolsPluginImages.getImage(DroolsPluginImages.CREATE_ACTIVATION);
                        case LogEvent.ACTIVATION_CANCELLED: return DroolsPluginImages.getImage(DroolsPluginImages.CANCEL_ACTIVATION);
                        case LogEvent.BEFORE_ACTIVATION_FIRE: return DroolsPluginImages.getImage(DroolsPluginImages.EXECUTE_ACTIVATION);
                        case 8: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW);
                        case 9: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW);
                        case LogEvent.BEFORE_RULEFLOW_NODE_TRIGGERED: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW_NODE_TRIGGERED);
                        case 10: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW_GROUP);
                        case 11: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW_GROUP);
                        case 12: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                        case 14: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                        case 17: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                        case 19: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                    }
                }
                switch (type) {
                    case LogEvent.INSERTED: return DroolsPluginImages.getImage(DroolsPluginImages.INSERT);
                    case LogEvent.UPDATED: return DroolsPluginImages.getImage(DroolsPluginImages.UPDATE);
                    case LogEvent.RETRACTED: return DroolsPluginImages.getImage(DroolsPluginImages.RETRACT);
                    case LogEvent.ACTIVATION_CREATED: return DroolsPluginImages.getImage(DroolsPluginImages.CREATE_ACTIVATION);
                    case LogEvent.ACTIVATION_CANCELLED: return DroolsPluginImages.getImage(DroolsPluginImages.CANCEL_ACTIVATION);
                    case LogEvent.BEFORE_ACTIVATION_FIRE: return DroolsPluginImages.getImage(DroolsPluginImages.EXECUTE_ACTIVATION);
                    case LogEvent.BEFORE_RULEFLOW_CREATED: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW);
                    case LogEvent.BEFORE_RULEFLOW_COMPLETED: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW);
                    case LogEvent.BEFORE_RULEFLOW_NODE_TRIGGERED: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW_NODE_TRIGGERED);
                    case LogEvent.BEFORE_RULEFLOW_GROUP_ACTIVATED: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW_GROUP);
                    case LogEvent.BEFORE_RULEFLOW_GROUP_DEACTIVATED: return DroolsPluginImages.getImage(DroolsPluginImages.RULEFLOW_GROUP);
                    case LogEvent.BEFORE_PACKAGE_ADDED: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                    case LogEvent.BEFORE_PACKAGE_REMOVED: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                    case LogEvent.BEFORE_RULE_ADDED: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                    case LogEvent.BEFORE_RULE_REMOVED: return DroolsPluginImages.getImage(DroolsPluginImages.DROOLS);
                }
                return null;
            }
            return null;
        }
    }
}
