/*******************************************************************************
 * Copyright (c) 2000, 2009 QNX Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     QNX Software Systems - Initial API and implementation
 *     Alena Laskavaia (QNX) - Bug 197986, Bug 221224
 *******************************************************************************/
package org.eclipse.cdt.debug.mi.core.cdi;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.debug.core.cdi.CDIException;
import org.eclipse.cdt.debug.core.cdi.model.ICDIArgumentDescriptor;
import org.eclipse.cdt.debug.core.cdi.model.ICDILocalVariableDescriptor;
import org.eclipse.cdt.debug.core.cdi.model.ICDIStackFrame;
import org.eclipse.cdt.debug.core.cdi.model.ICDIThread;
import org.eclipse.cdt.debug.core.cdi.model.ICDIThreadStorageDescriptor;
import org.eclipse.cdt.debug.core.cdi.model.ICDIVariable;
import org.eclipse.cdt.debug.mi.core.MIException;
import org.eclipse.cdt.debug.mi.core.MISession;
import org.eclipse.cdt.debug.mi.core.RxThread;
import org.eclipse.cdt.debug.mi.core.cdi.model.Argument;
import org.eclipse.cdt.debug.mi.core.cdi.model.ArgumentDescriptor;
import org.eclipse.cdt.debug.mi.core.cdi.model.GlobalVariable;
import org.eclipse.cdt.debug.mi.core.cdi.model.GlobalVariableDescriptor;
import org.eclipse.cdt.debug.mi.core.cdi.model.LocalVariable;
import org.eclipse.cdt.debug.mi.core.cdi.model.LocalVariableDescriptor;
import org.eclipse.cdt.debug.mi.core.cdi.model.Register;
import org.eclipse.cdt.debug.mi.core.cdi.model.RegisterDescriptor;
import org.eclipse.cdt.debug.mi.core.cdi.model.StackFrame;
import org.eclipse.cdt.debug.mi.core.cdi.model.Target;
import org.eclipse.cdt.debug.mi.core.cdi.model.Thread;
import org.eclipse.cdt.debug.mi.core.cdi.model.ThreadStorage;
import org.eclipse.cdt.debug.mi.core.cdi.model.ThreadStorageDescriptor;
import org.eclipse.cdt.debug.mi.core.cdi.model.Variable;
import org.eclipse.cdt.debug.mi.core.cdi.model.VariableDescriptor;
import org.eclipse.cdt.debug.mi.core.command.CLIPType;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
import org.eclipse.cdt.debug.mi.core.command.MIStackListArguments;
import org.eclipse.cdt.debug.mi.core.command.MIStackListLocals;
import org.eclipse.cdt.debug.mi.core.command.MIVarCreate;
import org.eclipse.cdt.debug.mi.core.command.MIVarDelete;
import org.eclipse.cdt.debug.mi.core.command.MIVarUpdate;
import org.eclipse.cdt.debug.mi.core.event.MIEvent;
import org.eclipse.cdt.debug.mi.core.event.MIVarChangedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIVarDeletedEvent;
import org.eclipse.cdt.debug.mi.core.output.CLIPTypeInfo;
import org.eclipse.cdt.debug.mi.core.output.MIArg;
import org.eclipse.cdt.debug.mi.core.output.MIFrame;
import org.eclipse.cdt.debug.mi.core.output.MIStackListArgumentsInfo;
import org.eclipse.cdt.debug.mi.core.output.MIStackListLocalsInfo;
import org.eclipse.cdt.debug.mi.core.output.MIVar;
import org.eclipse.cdt.debug.mi.core.output.MIVarChange;
import org.eclipse.cdt.debug.mi.core.output.MIVarCreateInfo;
import org.eclipse.cdt.debug.mi.core.output.MIVarUpdateInfo;

/**
 */
public class VariableManager extends Manager {

	static final ICDIVariable[] EMPTY_VARIABLES = {};
	// We put a restriction on how deep we want to
	// go when doing update of the variables.
	// If the number is to high, gdb will just hang.
	int MAX_STACK_DEPTH = Thread.STACKFRAME_DEFAULT_DEPTH;
	Map variablesMap;
	MIVarChange[] noChanges = new MIVarChange[0];

	public VariableManager(Session session) {
		super(session, true);
		variablesMap = new Hashtable();
	}

	synchronized List getVariablesList(Target target) {
		List variablesList = (List) variablesMap.get(target);
		if (variablesList == null) {
			variablesList = Collections.synchronizedList(new ArrayList());
			variablesMap.put(target, variablesList);
		}
		return variablesList;
	}

	/**
	 * Return the element that have the uniq varName.
	 * null is return if the element is not in the cache.
	 */
	public Variable getVariable(MISession miSession, String varName) {
		Target target = ((Session)getSession()).getTarget(miSession);
		return getVariable(target, varName);
	}

	/**
	 * Return the element that have the uniq varName.
	 * null is return if the element is not in the cache.
	 */
	public Variable getVariable(Target target, String varName) {
		Variable[] vars = getVariables(target);
		for (int i = 0; i < vars.length; i++) {
			try {
				if (vars[i].getMIVar().getVarName().equals(varName)) {
					return vars[i];
				}
				Variable v = vars[i].getChild(varName);
				if (v != null) {
					return v;
				}
			} catch (CDIException e) {
				// 
			}
		}
		return null;
	}

	/**
	 * Return the Element with this thread/stackframe, and with this name.
	 * null is return if the element is not in the cache.
	 */
	Variable findVariable(VariableDescriptor v) throws CDIException {
		Target target = (Target)v.getTarget();
		ICDIStackFrame vstack = v.getStackFrame();
		ICDIThread vthread = v.getThread();
		int position = v.getPosition();
		int depth = v.getStackDepth();
		Variable[] vars = getVariables(target);
		for (int i = 0; i < vars.length; i++) {
			if (vars[i].getFullName().equals(v.getFullName())
				&& vars[i].getName().equals(v.getName()) // see bug #113364
				&& vars[i].getCastingArrayStart() == v.getCastingArrayStart()
				&& vars[i].getCastingArrayEnd() == v.getCastingArrayEnd()
				&& VariableDescriptor.equalsCasting(vars[i], v)) {
				// check threads
				ICDIThread thread = vars[i].getThread();
				if ((vthread == null && thread == null) ||
						(vthread != null && thread != null && thread.equals(vthread))) {
					// check stackframes
					ICDIStackFrame frame = vars[i].getStackFrame();
					if (vstack == null && frame == null) {
						return vars[i];
					} else if (frame != null && vstack != null && frame.equals(vstack)) {
						if (vars[i].getPosition() == position) {
							if (vars[i].getStackDepth() == depth) {
								return vars[i];
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Returns all the elements that are in the cache.
	 */
	Variable[] getVariables(Target target) {
		List variableList = (List)variablesMap.get(target);
		if (variableList != null) {
			return (Variable[]) variableList.toArray(new Variable[variableList.size()]);
		}
		return new Variable[0];
	}

	/**
	 * Check the type
	 */
	public void checkType(StackFrame frame, String type) throws CDIException {
		if (type != null && type.length() > 0) {
			Target target = (Target)frame.getTarget();
			Thread currentThread = (Thread)target.getCurrentThread();
			StackFrame currentFrame = currentThread.getCurrentStackFrame();
			synchronized(target.getLock()) {
			try {
				target.setCurrentThread(frame.getThread(), false);
				((Thread)frame.getThread()).setCurrentStackFrame(frame, false);
				MISession miSession = target.getMISession();
				RxThread rxThread = miSession.getRxThread();
				rxThread.setEnableConsole(false);
				CommandFactory factory = miSession.getCommandFactory();
				CLIPType ptype = factory.createCLIPType(type);
				miSession.postCommand(ptype);
				CLIPTypeInfo info = ptype.getMIPtypeInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
			} catch (MIException e) {
				throw new MI2CDIException(e);
			} finally {
					MISession miSession = target.getMISession();
					RxThread rxThread = miSession.getRxThread();
					rxThread.setEnableConsole(true);
					target.setCurrentThread(currentThread, false);
					currentThread.setCurrentStackFrame(currentFrame, false);
			}
			}
		} else {
			throw new CDIException(CdiResources.getString("cdi.VariableManager.Unknown_type")); //$NON-NLS-1$
		}
	}

	/**
	 * Tell gdb to remove the underlying var-object also.
	 */
	void removeMIVar(MISession miSession, MIVar miVar) throws CDIException {
		CommandFactory factory = miSession.getCommandFactory();
		MIVarDelete var = factory.createMIVarDelete(miVar.getVarName());
		try {
			miSession.postCommand(var);
			var.getMIInfo();
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	/**
	 * Remove variable form the maintained cache list.
	 * @param miSession
	 * @param varName
	 * @return
	 */
	public Variable removeVariableFromList(MISession miSession, String varName) {
		Target target = ((Session)getSession()).getTarget(miSession);
		List varList = getVariablesList(target);
		synchronized (varList) {
			for (Iterator iterator = varList.iterator(); iterator.hasNext();) {
				Variable variable = (Variable)iterator.next();
				try {
					if (variable.getMIVar().getVarName().equals(varName)) {
						iterator.remove();
						return variable;
					}
				} catch (CDIException e) {
					//
				}
			}
		}
		return null;
	}

	/**
	 * Encode the variableDescriptor as an array
	 * @param varDesc
	 * @param start
	 * @param length
	 * @return
	 * @throws CDIException
	 */
	public VariableDescriptor getVariableDescriptorAsArray(VariableDescriptor varDesc, int start, int length)
		throws CDIException {
		Target target = (Target)varDesc.getTarget();
		Thread thread = (Thread)varDesc.getThread();
		StackFrame frame = (StackFrame)varDesc.getStackFrame();
		String name = varDesc.getName();
		String fullName = varDesc.getFullName();
		int pos = varDesc.getPosition();
		int depth = varDesc.getStackDepth();
		VariableDescriptor vo = null;

		if (varDesc instanceof ArgumentDescriptor || varDesc instanceof Argument) {
			vo = new ArgumentDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof LocalVariableDescriptor || varDesc instanceof LocalVariable) {
			vo = new LocalVariableDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof GlobalVariableDescriptor || varDesc instanceof GlobalVariable) {
			vo = new GlobalVariableDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof RegisterDescriptor || varDesc instanceof Register) {
			vo = new RegisterDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof ThreadStorageDescriptor || varDesc instanceof ThreadStorage) {
			vo = new ThreadStorageDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else {
			throw new CDIException(CdiResources.getString("cdi.VariableManager.Unknown_variable_object")); //$NON-NLS-1$			
		}

		vo.setCastingArrayStart(varDesc.getCastingArrayStart() + start);
		vo.setCastingArrayEnd(length);
		return vo;
	}

	/**
	 * Encode the variableDescriptor in a typecasting.
	 * @param varDesc
	 * @param type
	 * @return
	 * @throws CDIException
	 */
	public VariableDescriptor getVariableDescriptorAsType(VariableDescriptor varDesc, String type) throws CDIException {
		// throw an exception if not a good type.
		Target target = (Target)varDesc.getTarget();
		Thread thread = (Thread)varDesc.getThread();
		StackFrame frame = (StackFrame)varDesc.getStackFrame();
		String name = varDesc.getName();
		String fullName = varDesc.getFullName();
		int pos = varDesc.getPosition();
		int depth = varDesc.getStackDepth();

		// Check the type validity.
		{
			StackFrame f = frame;
			if (f == null) {
				if (thread != null) {
					f = thread.getCurrentStackFrame();
				} else {
					Thread t = (Thread)target.getCurrentThread();
					f = t.getCurrentStackFrame();
				}
			}
			checkType(f, type);
		}

		VariableDescriptor vo = null;

		if (varDesc instanceof ArgumentDescriptor || varDesc instanceof Argument) {
			vo = new ArgumentDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof LocalVariableDescriptor || varDesc instanceof LocalVariable) {
			vo = new LocalVariableDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof GlobalVariableDescriptor || varDesc instanceof GlobalVariable) {
			vo = new GlobalVariableDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof ThreadStorageDescriptor || varDesc instanceof ThreadStorage) {
			vo = new ThreadStorageDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else if (varDesc instanceof RegisterDescriptor || varDesc instanceof Register) {
			vo = new RegisterDescriptor(target, thread, frame, name, fullName, pos, depth);
		} else {
			throw new CDIException(CdiResources.getString("cdi.VariableManager.Unknown_variable_object")); //$NON-NLS-1$			
		}

		String[] castings = varDesc.getCastingTypes();
		if (castings == null) {
			castings = new String[] { type };
		} else {
			String[] temp = new String[castings.length + 1];
			System.arraycopy(castings, 0, temp, 0, castings.length);
			temp[castings.length] = type;
			castings = temp;
		}
		vo.setCastingTypes(castings);
		return vo;
	}

	public Variable createVariable(VariableDescriptor varDesc) throws CDIException {
		if (varDesc instanceof ArgumentDescriptor) {
			return createArgument((ArgumentDescriptor)varDesc);
		} else if (varDesc instanceof LocalVariableDescriptor) {
			return createLocalVariable((LocalVariableDescriptor)varDesc);
		} else if (varDesc instanceof GlobalVariableDescriptor) {
			return createGlobalVariable((GlobalVariableDescriptor)varDesc);
		} else if (varDesc instanceof RegisterDescriptor) {
			RegisterManager regMgr = ((Session)getSession()).getRegisterManager();
			return regMgr.createRegister((RegisterDescriptor)varDesc);
		} else if (varDesc instanceof ThreadStorageDescriptor) {
			return createThreadStorage((ThreadStorageDescriptor)varDesc);
		}
		throw new CDIException(CdiResources.getString("cdi.VariableManager.Unknown_variable_object")); //$NON-NLS-1$			
	}

	public Argument createArgument(ArgumentDescriptor argDesc) throws CDIException {
		Variable variable = findVariable(argDesc);
		Argument argument = null;
		if (variable != null && variable instanceof Argument) {
			argument = (Argument) variable;
		}
		if (argument == null) {
			String name = argDesc.getQualifiedName();
			StackFrame stack = (StackFrame)argDesc.getStackFrame();
			Target target = (Target)argDesc.getTarget();
			Thread currentThread = (Thread)target.getCurrentThread();
			StackFrame currentFrame = currentThread.getCurrentStackFrame();
			synchronized(target.getLock()) {
			try {
				target.setCurrentThread(stack.getThread(), false);
				((Thread)stack.getThread()).setCurrentStackFrame(stack, false);
				MISession mi = target.getMISession();
				CommandFactory factory = mi.getCommandFactory();
				MIVarCreate var = factory.createMIVarCreate(name);
				mi.postCommand(var, -1);
				argument = new Argument(argDesc, var);
//				mi.postCommand(var);
//				MIVarCreateInfo info = var.getMIVarCreateInfo();
//				if (info == null) {
//					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
//				}
//				argument = new Argument(argDesc, info.getMIVar());
				List variablesList = getVariablesList(target);
				variablesList.add(argument);
			} catch (MIException e) {
				throw new MI2CDIException(e);
			} finally {
					target.setCurrentThread(currentThread, false);
					currentThread.setCurrentStackFrame(currentFrame, false);
			}
			}
		}
		return argument;
	}

	public ICDIArgumentDescriptor[] getArgumentDescriptors(StackFrame frame) throws CDIException {
		List argObjects = new ArrayList();
		Target target = (Target)frame.getTarget();
		Thread currentThread = (Thread)target.getCurrentThread();
		StackFrame currentFrame = currentThread.getCurrentStackFrame();
		synchronized(target.getLock()) {
		try {
			target.setCurrentThread(frame.getThread(), false);
			((Thread)frame.getThread()).setCurrentStackFrame(frame, false);
			MISession mi = target.getMISession();
			CommandFactory factory = mi.getCommandFactory();
			int depth = frame.getThread().getStackFrameCount();
			int level = frame.getLevel();
			// Need the GDB/MI view of level which the reverse i.e. Highest frame is 0
			int miLevel = depth - level;
			MIStackListArguments listArgs = factory.createMIStackListArguments(false, miLevel, miLevel);
			MIArg[] args = null;
			mi.postCommand(listArgs);
			MIStackListArgumentsInfo info = listArgs.getMIStackListArgumentsInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$ 
			}
			MIFrame[] miFrames = info.getMIFrames();
			if (miFrames != null && miFrames.length == 1) {
				args = miFrames[0].getArgs();
			}
			if (args != null) {
				for (int i = 0; i < args.length; i++) {
					ArgumentDescriptor arg = new ArgumentDescriptor(target, null, frame, args[i].getName(), null, args.length - i, level);
					argObjects.add(arg);
				}
			}
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
				target.setCurrentThread(currentThread, false);
				currentThread.setCurrentStackFrame(currentFrame, false);
		}
		}
		return (ICDIArgumentDescriptor[]) argObjects.toArray(new ICDIArgumentDescriptor[0]);
	}

	public GlobalVariableDescriptor getGlobalVariableDescriptor(Target target, String filename, String function, String name) throws CDIException {
		if (filename == null) {
			filename = new String();
		}
		if (function == null) {
			function = new String();
		}
		if (name == null) {
			name = new String();
		}
		StringBuffer buffer = new StringBuffer();
		if (filename.length() > 0) {
			buffer.append('\'').append(filename).append('\'').append("::"); //$NON-NLS-1$
		}
		if (function.length() > 0) {
			buffer.append(function).append("::"); //$NON-NLS-1$
		}
		buffer.append(name);
		return new GlobalVariableDescriptor(target, null, null, buffer.toString(), null, 0, 0);
	}

	public GlobalVariable createGlobalVariable(GlobalVariableDescriptor varDesc) throws CDIException {
		Variable variable = findVariable(varDesc);
		GlobalVariable global = null;
		if (variable instanceof GlobalVariable) {
			global = (GlobalVariable)variable;
		}
		if (global == null) {
			String name = varDesc.getQualifiedName();
			Target target = (Target)varDesc.getTarget();
			try {
				MISession mi = target.getMISession();
				CommandFactory factory = mi.getCommandFactory();
				MIVarCreate var = factory.createMIVarCreate(name);
				mi.postCommand(var, -1);
				global = new GlobalVariable(varDesc, var);
//				mi.postCommand(var;
//				MIVarCreateInfo info = var.getMIVarCreateInfo();
//				if (info == null) {
//					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
//				}
//				global = new GlobalVariable(varDesc, info.getMIVar());
				List variablesList = getVariablesList(target);
				variablesList.add(global);
			} catch (MIException e) {
				throw new MI2CDIException(e);
			}
		}
		return global;
	}

	public ICDILocalVariableDescriptor[] getLocalVariableDescriptors(StackFrame frame) throws CDIException {
		List varObjects = new ArrayList();
		Target target = (Target)frame.getTarget();
		Thread currentThread = (Thread)target.getCurrentThread();
		StackFrame currentFrame = currentThread.getCurrentStackFrame();
		synchronized(target.getLock()) {
		try {
			target.setCurrentThread(frame.getThread(), false);
			((Thread)frame.getThread()).setCurrentStackFrame(frame, false);
			MISession mi = target.getMISession();
			CommandFactory factory = mi.getCommandFactory();
			int level = frame.getLevel();
			MIArg[] args = null;
			MIStackListLocals locals = factory.createMIStackListLocals(false);
			mi.postCommand(locals);
			MIStackListLocalsInfo info = locals.getMIStackListLocalsInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			args = info.getLocals();
			if (args != null) {
				for (int i = 0; i < args.length; i++) {
					LocalVariableDescriptor varObj = new LocalVariableDescriptor(target, null, frame, args[i].getName(), null, args.length - i, level);
					varObjects.add(varObj);
				}
			}
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {	
				target.setCurrentThread(currentThread, false);
				currentThread.setCurrentStackFrame(currentFrame, false);
		}
		}
		return (ICDILocalVariableDescriptor[]) varObjects.toArray(new ICDILocalVariableDescriptor[0]);
	}

	public LocalVariable createLocalVariable(LocalVariableDescriptor varDesc) throws CDIException {
		Variable variable = findVariable(varDesc);
		LocalVariable local = null;
		if (variable instanceof LocalVariable) {
			local = (LocalVariable)variable;
		}
		if (local == null) {
			String name = varDesc.getQualifiedName();
			StackFrame stack = (StackFrame)varDesc.getStackFrame();
			Target target = (Target)varDesc.getTarget();
			Thread currentThread = (Thread)target.getCurrentThread();
			StackFrame currentFrame = currentThread.getCurrentStackFrame();
			synchronized(target.getLock()) {
			try {
				target.setCurrentThread(stack.getThread(), false);
				((Thread)stack.getThread()).setCurrentStackFrame(stack, false);
				MISession mi = target.getMISession();
				CommandFactory factory = mi.getCommandFactory();
				MIVarCreate var = factory.createMIVarCreate(name);
				mi.postCommand(var, -1);
				local = new LocalVariable(varDesc, var);
//				mi.postCommand(var);
//				MIVarCreateInfo info = var.getMIVarCreateInfo();
//				if (info == null) {
//					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
//				}
//				local = new LocalVariable(varDesc, info.getMIVar());
				List variablesList = getVariablesList(target);
				variablesList.add(local);
			} catch (MIException e) {
				throw new MI2CDIException(e);
			} finally {
				target.setCurrentThread(currentThread, false);
				currentThread.setCurrentStackFrame(currentFrame, false);
			}
			}
		}
		return local;
	}

	public ICDIThreadStorageDescriptor[] getThreadStorageDescriptors(Thread thread) throws CDIException {
		return new ICDIThreadStorageDescriptor[0];
	}

	public ThreadStorage createThreadStorage(ThreadStorageDescriptor desc) throws CDIException {
		throw new CDIException(CdiResources.getString("cdi.VariableManager.Unknown_variable_object")); //$NON-NLS-1$
	}

	public void destroyVariable(Variable variable) throws CDIException {
		// Fire  a destroyEvent ?
		Target target = (Target)variable.getTarget();
		MISession mi = target.getMISession();
		// no need to call -var-delete for variable that are not in
		// the list most probaby they are children of other variables and in this case
		// we should not delete them
		List varList = getVariablesList(target);
		if (varList.contains(variable)) {
			removeMIVar(mi, variable.getMIVar());
		}
		MIVarDeletedEvent del = new MIVarDeletedEvent(mi, variable.getMIVar().getVarName());
		mi.fireEvent(del);
	}

	public void destroyAllVariables(Target target) throws CDIException {
		Variable[] variables = getVariables(target);
		MISession mi = target.getMISession();
		for (int i = 0; i < variables.length; ++i) {
			removeMIVar(mi, variables[i].getMIVar());
			MIVarDeletedEvent del = new MIVarDeletedEvent(mi, variables[i].getMIVar().getVarName());
			mi.fireEvent(del);			
		}
	}

	/**
	 * Update the elements in the cache, from the response of the "-var-update"
	 * mi/command.  Althought tempting we do not use the "-var-update *" command, since
	 * for some reason on gdb-5.2.1 it starts to misbehave until it hangs ... sigh
	 * We take the approach of updating the variables ourselfs.  But we do it a smart
	 * way by only updating the variables visible in the current stackframe but not
	 * the other locals in different frames.  The downside if any side effects we loose,
	 * This ok, since the IDE only a frame at a time.
	 *
	 */
	@Override
	public void update(Target target) throws CDIException {
		int highLevel = 0;
		int lowLevel = 0;
		List eventList = new ArrayList();
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		Variable[] vars = getVariables(target);
		ICDIStackFrame[] frames = null;
		StackFrame currentStack = null;
		Thread currentThread = (Thread)target.getCurrentThread();
		if (currentThread != null) {
			currentStack = currentThread.getCurrentStackFrame();
			if (currentStack != null) {
				highLevel = currentStack.getLevel();
			}
			if (highLevel > MAX_STACK_DEPTH) {
				highLevel = MAX_STACK_DEPTH;
			}
			lowLevel = highLevel - MAX_STACK_DEPTH;
			if (lowLevel < 0) {
				lowLevel = 0;
			}
			frames = currentThread.getStackFrames(0, highLevel);
		}
		for (int i = 0; i < vars.length; i++) {
			Variable variable = vars[i];
			if (isVariableNeedsToBeUpdate(variable, currentStack, frames, lowLevel)) {
				update(target, variable, eventList);
			} else {
				variable.setUpdated(false);
			}
		}
		MIEvent[] events = (MIEvent[]) eventList.toArray(new MIEvent[0]);
		mi.fireEvents(events);
	}

	public void update(Variable variable) throws CDIException {
		Target target = (Target)variable.getTarget();
		MISession mi = target.getMISession();
		List eventList = new ArrayList();
		update(target, variable, eventList);
		MIEvent[] events = (MIEvent[]) eventList.toArray(new MIEvent[0]);
		mi.fireEvents(events);		
	}

	public void update(Target target, Variable variable, List eventList) throws CDIException {
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		MIVarChange[] changes = noChanges;
		try {
			String miVarName = variable.getMIVar().getVarName();
			MIVarUpdate update = factory.createMIVarUpdate(miVarName);
			try {
				mi.postCommand(update);
				MIVarUpdateInfo info = update.getMIVarUpdateInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
				changes = info.getMIVarChanges();
			} catch (MIException e) {
				//throw new MI2CDIException(e);
				eventList.add(new MIVarDeletedEvent(mi, miVarName));
			}
		} catch (CDIException exc) {
			// When the variable was out of scope the fisrt time, the getMIVar() generates an exception.
			// Then create again the variable, set the fVarCreateCMD of Variable class and try again the update command.
			try {						
				MIVarCreate var = factory.createMIVarCreate(variable.getName());
				mi.postCommand(var);	
				MIVarCreateInfo info = var.getMIVarCreateInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
				variable.setMIVarCreate(var);						
				update(target, variable, eventList);
			} catch (MIException e) {
				// Creating failed, variable not in scope => remove
				// No events to fire as the variable isn't backed by a MIVar
				getVariablesList(target).remove(variable);
				variable.setUpdated(false);
				return;
			} catch (CDIException e) {
				throw e;
			}
		}
		variable.setUpdated(true);
		for (int j = 0; j < changes.length; j++) {
			String n = changes[j].getVarName();
			if (changes[j].isInScope()) {
				eventList.add(new MIVarChangedEvent(mi, n));
			} else {
				destroyVariable(variable);
				eventList.add(new MIVarDeletedEvent(mi, n));
			}
		}
	}

	/**
	 * We are trying to minimize the impact of the updates, this can be very long and unncessary if we
	 * have a very deep stack and lots of local variables.  We can assume here that the local variables
	 * in the other non-selected stackframe will not change and only update the selected frame variables.
	 * 
	 * @param variable
	 * @param current
	 * @param frames
	 * @return
	 */
	boolean isVariableNeedsToBeUpdate(Variable variable, ICDIStackFrame current, ICDIStackFrame[] frames, int lowLevel)
		throws CDIException {
		ICDIStackFrame varStack = variable.getStackFrame();
		boolean inScope = false;

		// Something wrong and the program terminated bail out here.
		if (current == null || frames == null) {
			return false;
		}

		// If the variable Stack is null, it means this is a global variable we should update
		if (varStack == null) {
			return true;
		} else if (varStack.equals(current)) {
			// The variable is in the current selected frame it should be updated
			return true;
		} else {
			if (varStack.getLevel() >= lowLevel) {
				// Check if the Variable is still in Scope 
				// if it is no longer in scope so update() can call "-var-delete".
				for (int i = 0; i < frames.length; i++) {
					if (varStack.equals(frames[i])) {
						inScope = true;
					}
				}
			} else {
				inScope = true;
			}
		}
		// return true if the variable is no longer in scope we
		// need to call -var-delete.
		return !inScope;
	}

}
