/*
 * 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 org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.debug.actions.ShowLogicalStructureAction;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.debug.internal.ui.VariablesViewModelPresentation;
import org.eclipse.debug.internal.ui.contexts.DebugContextManager;
import org.eclipse.debug.internal.ui.views.variables.VariablesView;
import org.eclipse.debug.ui.AbstractDebugView;
import org.eclipse.debug.ui.IDebugModelPresentation;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.contexts.DebugContextEvent;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
import org.eclipse.debug.ui.contexts.IDebugContextService;
import org.eclipse.jdt.debug.core.IJavaFieldVariable;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
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.resource.JFaceResources;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;

/**
 * A generic Drools debug view.
 */
public abstract class DroolsDebugEventHandlerView extends AbstractDebugView implements IDebugContextListener, ISelectionListener {

    private VariablesViewModelPresentation modelPresentation;
    private boolean showLogical = true;
    private Object[] oldExpandedElements = new Object[0];

    public void dispose() {
        DebugContextManager.getDefault().removeDebugContextListener(this);
        getSite().getPage().removeSelectionListener(IDebugUIConstants.ID_VARIABLE_VIEW, this);
        super.dispose();
    }

    public boolean isShowLogicalStructure() {
        return showLogical;
    }

    public void setShowLogicalStructure(boolean showLogical) {
        this.showLogical = showLogical;
    }

    protected void setViewerInput(Object context) {
        Object input = null;

        // if a working memory has been explicitly selected as variable, use this
        if (context instanceof IVariable) {
            IVariable variable = (IVariable) context;
            try {
                IValue value = ((IVariable) context).getValue();
                if (value != null && value instanceof IJavaObject) {
                    if ("org.drools.core.common.AbstartWorkingMemory".equals(variable.getValue().getReferenceTypeName()) ||
                    	"org.drools.core.reteoo.ReteooStatefulSession".equals(variable.getValue().getReferenceTypeName()) ||
                    		// for backwards compatibility
                    		"org.drools.reteoo.ReteooStatefulSession".equals(variable.getValue().getReferenceTypeName())) {
                        input = value;
                    } else if ("org.drools.core.impl.StatefulKnowledgeSessionImpl".equals(variable.getValue().getReferenceTypeName())
                    		|| "org.drools.impl.StatefulKnowledgeSessionImpl".equals(variable.getValue().getReferenceTypeName())) {
                        IJavaFieldVariable sessionVar = ((IJavaObject) value).getField("session", false);
                        if (sessionVar != null) {
                            input = sessionVar.getValue();
                        } else {
                        	input = variable.getValue();
                        }
                    }
                }
            } catch (Throwable t) {
                DroolsEclipsePlugin.log(t);
            }
        }
        // else get selected thread and determine if any of the stack frames
        // is executing in a working memory, if so, use that one
        if (input == null) {
            IDebugContextService debugContextService = DebugContextManager.getDefault().getContextService(getSite().getWorkbenchWindow());
            if (debugContextService != null) {
                ISelection stackSelection = debugContextService.getActiveContext();
                if (stackSelection instanceof IStructuredSelection) {
                    Object selection = ((IStructuredSelection) stackSelection).getFirstElement();
                    if (selection instanceof IJavaStackFrame) {
                        try {
                            IJavaThread thread = (IJavaThread) ((IJavaStackFrame) selection).getThread();
                            IStackFrame[] frames = thread.getStackFrames();
                            for (int i = 0; i < frames.length; i++) {
                                IJavaObject stackObj = ((IJavaStackFrame) frames[i]).getThis();
                                if ((stackObj != null)
                                        && (stackObj.getJavaType() != null)
                                        && ("org.drools.core.common.AbstractWorkingMemory".equals(stackObj.getJavaType().getName())
                                        		|| "org.drools.core.reteoo.ReteooStatefulSession".equals(stackObj.getJavaType().getName())
                                        		|| "org.drools.reteoo.ReteooStatefulSession".equals(stackObj.getJavaType().getName())
                                                || "org.drools.core.impl.StatefulKnowledgeSessionImpl".equals(stackObj.getJavaType().getName()))) {
                                    input = stackObj;
                                    break;
                                }
                            }
                        } catch (Throwable t) {
                            DroolsEclipsePlugin.log(t);
                        }
                    }
                }
            }
        }

        Object current = getViewer().getInput();

        if (current == null && input == null) {
            return;
        }

        Object[] newExpandedElements = ((TreeViewer) getViewer()).getExpandedElements();
        if (newExpandedElements.length != 0) {
            oldExpandedElements = newExpandedElements;
        }
        getViewer().setInput(input);
        if (input != null) {
            ((TreeViewer) getViewer()).setExpandedElements(oldExpandedElements);
            ((TreeViewer) getViewer()).expandToLevel(getAutoExpandLevel());
        }
    }

    protected Viewer createViewer(Composite parent) {
        TreeViewer variablesViewer = new TreeViewer(parent);
        variablesViewer.setContentProvider(createContentProvider());
        variablesViewer.setLabelProvider(new VariablesViewLabelProvider(
            getModelPresentation()));
        variablesViewer.setUseHashlookup(true);
        DebugContextManager.getDefault().addDebugContextListener(this);
        getSite().getPage().addSelectionListener(IDebugUIConstants.ID_VARIABLE_VIEW, this);
        return variablesViewer;
    }
    
    protected int getAutoExpandLevel() {
        return 0;
    }
    
    protected abstract IContentProvider createContentProvider();

    protected String getHelpContextId() {
        return null;
    }

    protected void becomesHidden() {
        setViewerInput(null);
        super.becomesHidden();
    }

    protected void becomesVisible() {
        super.becomesVisible();
        ISelection selection = getSite().getPage().getSelection(
            IDebugUIConstants.ID_VARIABLE_VIEW);
        if (selection instanceof IStructuredSelection) {
            setViewerInput(((IStructuredSelection) selection).getFirstElement());
        } else {
            setViewerInput(null);
        }
    }

    protected void createActions() {
        IAction action = new ShowLogicalStructureAction(this);
        setAction("ShowLogicalStructure", action);
    }

    protected void configureToolBar(IToolBarManager tbm) {
        tbm.add(getAction("ShowLogicalStructure"));
    }

    protected void fillContextMenu(IMenuManager menu) {
        menu.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    public void contextActivated(ISelection selection, IWorkbenchPart part) {
        if (!isAvailable() || !isVisible()) {
            return;
        }

        if (selection instanceof IStructuredSelection) {
            setViewerInput(((IStructuredSelection)selection).getFirstElement());
        }
        showViewer();
    }

    public void debugContextChanged(DebugContextEvent event) {
        //selectionChanged(null, event.getContext());
    }

    public void selectionChanged(IWorkbenchPart part, ISelection selection) {
        if (!isAvailable()) {
            return;
        }
        // In Eclipse 3.5.x, an additional selection changed event with selection null is
        // generated when clicking on a process instance in the process instance view
        // replacing this null by the actual selected element in those cases
        if (part instanceof VariablesView) {
            selection = ((VariablesView) part).getCurrentSelection();
        }
        if (selection == null) {
            setViewerInput(null);
        } else if (selection instanceof IStructuredSelection) {
            setViewerInput(((IStructuredSelection) selection).getFirstElement());
        }
    }
    
    protected void initActionState(IAction action) {
        // The show logical structure action is always enabled by default
        // when (re)starting the view
        String id = action.getId();
        if (id.endsWith("ShowLogicalStructureAction")) {
            action.setChecked(true);
        } else {
            super.initActionState(action);
        }
    }
    
    protected IDebugModelPresentation getModelPresentation() {
        if (modelPresentation == null) {
            modelPresentation = new VariablesViewModelPresentation();
        }
        return modelPresentation;
    }

    private class VariablesViewLabelProvider implements ILabelProvider, IColorProvider {

        private IDebugModelPresentation presentation;

        public VariablesViewLabelProvider(IDebugModelPresentation presentation) {
            this.presentation = presentation;
        }

        public IDebugModelPresentation getPresentation() {
            return presentation;
        }

        public Image getImage(Object element) {
            return presentation.getImage(element);
        }

        public String getText(Object element) {
            return presentation.getText(element);
        }

        public void addListener(ILabelProviderListener listener) {
            presentation.addListener(listener);
        }

        public void dispose() {
            presentation.dispose();
        }

        public boolean isLabelProperty(Object element, String property) {
            return presentation.isLabelProperty(element, property);
        }

        public void removeListener(ILabelProviderListener listener) {
            presentation.removeListener(listener);
        }

        public Color getForeground(Object element) {
            if (element instanceof IVariable) {
                IVariable variable = (IVariable) element;
                try {
                    if (variable.hasValueChanged()) {
                        return JFaceResources.getColorRegistry()
                            .get(IDebugUIConstants.PREF_CHANGED_DEBUG_ELEMENT_COLOR);
                    }
                } catch (DebugException e) {
                    DroolsEclipsePlugin.log(e);
                }
            }
            return null;
        }

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

    }
}
