/*
 * 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 java.util.ArrayList;
import java.util.List;

import org.drools.eclipse.DroolsEclipsePlugin;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.jdt.debug.core.IJavaArray;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.debug.core.IJavaVariable;

/**
 * The Agenda View content provider.
 */
public class AgendaViewContentProvider extends DroolsDebugViewContentProvider {

    private DroolsDebugEventHandlerView view;

    public AgendaViewContentProvider(DroolsDebugEventHandlerView view) {
        this.view = view;
    }
    
    protected String getEmptyString() {
        return "The selected working memory has an empty agenda.";
    }

    public Object[] getChildren(Object obj) {
        try {
            Object[] variables = null;
            if (obj != null && obj instanceof IJavaObject
                    && ("org.drools.core.impl.StatefulKnowledgeSessionImpl".equals(
                    		((IJavaObject) obj).getReferenceTypeName()) ||
                		"org.drools.core.common.AbstractWorkingMemory".equals(
                    		((IJavaObject) obj).getReferenceTypeName()) ||
                        "org.drools.core.reteoo.ReteooStatefulSession".equals(
                            ((IJavaObject) obj).getReferenceTypeName()) ||
	                    // for backwards compatibility
	                    "org.drools.reteoo.ReteooStatefulSession".equals(
	                        ((IJavaObject) obj).getReferenceTypeName()))) {
                variables = getAgendaElements((IJavaObject) obj);
            } else if (obj instanceof IVariable) {
                if (view.isShowLogicalStructure()) {
                    IValue value = getLogicalValue(((IVariable) obj).getValue(), new ArrayList<String>());
                    variables = value.getVariables();
                }
                if (variables == null) {
                    variables = ((IVariable) obj).getValue().getVariables();
                }
            }
            if (variables == null) {
                return new Object[0];
            } else {
                cache(obj, variables);
                return variables;
            }
        } catch (DebugException e) {
            DroolsEclipsePlugin.log(e);
            return new Object[0];
        }
    }
    
    private Object[] getAgendaElements(IJavaObject workingMemoryImpl) throws DebugException {
        List<MyVariableWrapper> result = new ArrayList<MyVariableWrapper>();
        IValue agendaGroupObjects = null;
        try {
        	agendaGroupObjects = DebugUtil.getValueByExpression("return ((org.drools.core.common.InternalAgenda) getAgenda()).getAgendaGroups();", workingMemoryImpl);
        } catch (RuntimeException e) {
        	// backwards compabitibility
        	agendaGroupObjects = DebugUtil.getValueByExpression("return getAgenda().getAgendaGroups();", workingMemoryImpl);
        }
        IValue focus = null;
    	try {
    		focus = DebugUtil.getValueByExpression("return ((org.drools.core.common.InternalAgenda) getAgenda()).getFocusName();", workingMemoryImpl);
    	} catch (RuntimeException e) {
    		try {
	            // Drools 4 code
	        	focus = DebugUtil.getValueByExpression("return getAgenda().getFocus();", workingMemoryImpl);
	        } catch (RuntimeException e2) {
	    		// Drools 5 code
	            focus = DebugUtil.getValueByExpression("return getAgenda().getFocusName();", workingMemoryImpl);
	        }
    	}
        if (agendaGroupObjects instanceof IJavaArray) {
            IJavaArray agendaGroupArray = (IJavaArray) agendaGroupObjects;
            IJavaValue[] agendaGroupValueArray = agendaGroupArray.getValues();
            for (int i = 0; i < agendaGroupValueArray.length; i++) {
                IJavaValue agendaGroup = agendaGroupValueArray[i];
                String name = "";
                List<VariableWrapper> activationsResult = new ArrayList<VariableWrapper>();
                IVariable[] agendaGroupVarArray = agendaGroup.getVariables();
                for (int j = 0; j < agendaGroupVarArray.length; j++) {
                    IVariable agendaGroupVar = agendaGroupVarArray[j];
                    if ("name".equals(agendaGroupVar.getName())) {
                        name = agendaGroupVar.getValue().getValueString();
                        break;
                    }
                }
                IJavaArray activations = (IJavaArray) DebugUtil.getValueByExpression("return getActivations();", agendaGroup);
                IJavaValue[] activationArray = activations.getValues();
                for (int l = 0; l < activationArray.length; l++) {
                    IJavaValue activation = activationArray[l];
                    if (activation.getJavaType() != null) {
                        activationsResult.add(new VariableWrapper("[" + l + "]",
                            new LazyActivationWrapper(activations, activation, workingMemoryImpl)));
                    }
                }

                // because the debug view does not handle spaces well, all spaces
                // in the agenda group name are replaced with '_'s.
                name = replaceSpaces(name);
                result.add(new MyVariableWrapper(name + "[" + (agendaGroup.equals(focus) ? "focus" : "nofocus") + "]",
                    new ObjectWrapper((IJavaObject) agendaGroup,
                        activationsResult.toArray(new IJavaVariable[activationsResult.size()]))));
            }
        }
        return result.toArray(new IVariable[0]);
    }
    
    private String replaceSpaces(String name) {
        return name.replace(' ', '_');
    }

    private class LazyActivationWrapper extends ObjectWrapper {

        private IJavaValue activation;
        private IJavaValue workingMemoryImpl;

        public LazyActivationWrapper(IJavaObject object, IJavaValue activation, IJavaObject workingMemoryImpl) {
            super(object, null);
            this.activation = activation;
            this.workingMemoryImpl = workingMemoryImpl;
        }

        public IVariable[] getVariables() {
            IVariable[] result = super.getVariables();
            if (result == null) {
                try {
                    List<VariableWrapper> variables = new ArrayList<VariableWrapper>();
                    variables.add(new VariableWrapper("ruleName", (IJavaValue) DebugUtil.getValueByExpression("return getRule().getName();", activation)));
                    String activationId = null;
                    IVariable[] activationVarArray = activation.getVariables();
                    for (int j = 0; j < activationVarArray.length; j++) {
                        IVariable activationVar = activationVarArray[j];
                        if ("activationNumber".equals(activationVar.getName())) {
                            activationId = activationVar.getValue().getValueString();
                            break;
                        }
                    }
                    if (activationId != null) {
                        IValue objects = DebugUtil.getValueByExpression("return getActivationParameters(" + activationId + ");", workingMemoryImpl);
                        if (objects instanceof IJavaArray) {
                        	addVariablesFromArray(variables, (IJavaArray) objects);
                        }
                        result = variables.toArray(new IJavaVariable[variables.size()]);
                    }
                } catch (Throwable t) {
                    DroolsEclipsePlugin.log(t);
                }
                if (result == null) {
                    result = new IJavaVariable[0];
                }
                setVariables((IJavaVariable[]) result);
            }
            return result;
        }
        
        private void addVariablesFromArray(List<VariableWrapper> variables, IJavaArray array) throws Exception {
            IJavaValue[] javaVals = array.getValues();
            for ( int k = 0; k < javaVals.length; k++ ) {
                IJavaValue mapEntry = javaVals[k];
                IJavaObject key = null;
                IJavaValue value = null;

                IVariable[] vars = mapEntry.getVariables();
                for ( int j = 0; j < vars.length; j++ ) {
                    IVariable var = vars[j];
                    if ("key".equals(var.getName())) {
                        key = (IJavaObject) var.getValue();
                    } else if ("value".equals(var.getName())) {
                        value = (IJavaValue) var.getValue();
                    }
                }
                if (value instanceof IJavaArray) {
                	List<VariableWrapper> nestedVariables = new ArrayList<VariableWrapper>();
                	addVariablesFromArray(nestedVariables, (IJavaArray)value);
                	IJavaVariable[] nestedResult = nestedVariables.toArray(new IJavaVariable[nestedVariables.size()]);
                	variables.add(new VariableWrapper("[" + k + "]", new ObjectWrapper(key, nestedResult)));
                } else {
                	variables.add(new VariableWrapper(key.getValueString(), value));
                }
            }
        }

        public boolean hasVariables() {
            return true;
        }

        public String getValueString() throws DebugException {
            return "Activation";
        }

        public String getReferenceTypeName() throws DebugException {
            return "";
        }
    }
    
    /**
     * Special VariableWrapper that considers variables with the same name
     * as equal.
     */
    private class MyVariableWrapper extends VariableWrapper {

        public MyVariableWrapper(String name, IJavaValue value) {
            super(name, value);
        }

        public boolean equals(Object obj) {
            if (obj instanceof VariableWrapper) {
                VariableWrapper var = (VariableWrapper) obj;
                return var.getName().equals(getName());
            }
            return false;
        }

        public int hashCode() {
            return getName().hashCode();
        }

    }
    
}
