/*******************************************************************************
 * Copyright (c) 2000, 2008 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 221224
 *******************************************************************************/
package org.eclipse.cdt.debug.mi.core.cdi;

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

import org.eclipse.cdt.debug.core.cdi.CDIException;
import org.eclipse.cdt.debug.core.cdi.model.ICDIExpression;
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.cdi.model.Expression;
import org.eclipse.cdt.debug.mi.core.cdi.model.LocalVariable;
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.Variable;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
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.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 ExpressionManager extends Manager {

	final static ICDIExpression[] EMPTY_EXPRESSIONS = {};
	Map expMap;
	Map varMap;
	MIVarChange[] noChanges = new MIVarChange[0];

	public ExpressionManager(Session session) {
		super(session, true);
		expMap = new Hashtable();
		varMap = new Hashtable();
	}

	synchronized List getExpressionList(Target target) {
		List expList = (List)expMap.get(target);
		if (expList == null) {
			expList = Collections.synchronizedList(new ArrayList());
			expMap.put(target, expList);
		}
		return expList;
	}

	synchronized List getVariableList(Target target) {
		List varList = (List)varMap.get(target);
		if (varList == null) {
			varList = Collections.synchronizedList(new ArrayList());
			varMap.put(target, varList);
		}
		return varList;
	}

	/**
	 * @see org.eclipse.cdt.debug.core.cdi.ICDIExpressionManager#createExpression(String)
	 */
	public ICDIExpression createExpression(Target target, String name) throws CDIException {
		Expression expression = new Expression(target, name);
		List exprList = getExpressionList(target);
		exprList.add(expression);
		return expression;
	}

	public ICDIExpression[] getExpressions(Target target) throws CDIException {
		List expList = (List) expMap.get(target);
		if (expList != null) {
			return (ICDIExpression[])expList.toArray(EMPTY_EXPRESSIONS);
		}
		return EMPTY_EXPRESSIONS;
	}

	/**
	 * @see org.eclipse.cdt.debug.core.cdi.ICDIExpressionManager#removeExpression(ICDIExpression)
	 */
	public void destroyExpressions(Target target, ICDIExpression[] expressions) throws CDIException {
		List expList = getExpressionList(target);
		for (int i = 0; i < expressions.length; ++i) {
			expList.remove(expressions[i]);
		}
	}

	/**
	 * @see org.eclipse.cdt.debug.core.cdi.ICDIExpressionManager#removeExpression(ICDIExpression)
	 */
	public void destroyAllExpressions(Target target) throws CDIException {
		ICDIExpression[] expressions = getExpressions(target);
		destroyExpressions(target, expressions);
	}

	@Override
	public void update(Target target) throws CDIException {
//		deleteAllVariables(target);
		List eventList = new ArrayList();
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		List varList = getVariableList(target);
		Variable[] variables = (Variable[]) varList.toArray(new Variable[varList.size()]);
		for (int i = 0; i < variables.length; i++) {
			Variable variable = variables[i];
			String varName = variable.getMIVar().getVarName();
			MIVarChange[] changes = noChanges;
			MIVarUpdate update = factory.createMIVarUpdate(varName);
			MIVarUpdateInfo info = null;
			try {
				mi.postCommand(update);
			} catch (MIException e) {
				throw new MI2CDIException(e);
			}
			boolean timedout = false;
			try {
				info = update.getMIVarUpdateInfo();
				if (info == null) {
					timedout = true;
					throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
				}
				changes = info.getMIVarChanges();
			} catch (MIException e) {
				if (timedout) {
					throw new MI2CDIException(e);
				}
				eventList.add(new MIVarDeletedEvent(mi, varName));
			}
			for (int j = 0; j < changes.length; j++) {
				String n = changes[j].getVarName();
				if (changes[j].isInScope()) {
					eventList.add(new MIVarChangedEvent(mi, n));
				} else {
					deleteVariable(variable);
					eventList.add(new MIVarDeletedEvent(mi, n));
				}
			}
		}
		MIEvent[] events = (MIEvent[]) eventList.toArray(new MIEvent[0]);
		mi.fireEvents(events);
	}

	/**
	 * @param miSession
	 * @param varName
	 * @return
	 */
	public Variable getVariable(MISession miSession, String varName) {
		Session session = (Session)getSession();
		Target target = session.getTarget(miSession);
		List varList = getVariableList(target);
		Variable[] vars = (Variable[])varList.toArray(new Variable[0]);
		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) {
				// ignore
			}
		}
		return null;
	}

	public Variable createVariable(StackFrame frame, String code) throws CDIException {
		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();
			MIVarCreate var = factory.createMIVarCreate(code);
			mi.postCommand(var);
			MIVarCreateInfo info = var.getMIVarCreateInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			Variable variable = new LocalVariable(target, null, frame, code, null, 0, 0, info.getMIVar());
			List varList = getVariableList(target);
			varList.add(variable);
			return variable;
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
				target.setCurrentThread(currentThread, false);
				currentThread.setCurrentStackFrame(currentFrame, false);
		}
		}
	}

	/**
	 * Remove variable form the maintained cache list.
	 * @param miSession
	 * @param varName
	 * @return
	 */
	public Variable removeVariableFromList(MISession miSession, String varName) {
		Variable var = getVariable(miSession, varName);
		if (var != null) {
			Target target = ((Session)getSession()).getTarget(miSession);
			List varList = getVariableList(target);
			varList.remove(var);
			return var;
		}
		return null;
	}

	public void deleteAllVariables(Target target) throws CDIException {
		List varList = getVariableList(target);
		Variable[] variables = (Variable[]) varList.toArray(new Variable[varList.size()]);
		for (int i = 0; i < variables.length; ++i) {
			deleteVariable(variables[i]);
		}
	}
	/**
	 * Get rid of the underlying variable.
	 */
	public void deleteVariable(Variable variable) throws CDIException {
		Target target = (Target)variable.getTarget();
		MISession miSession = target.getMISession();
		MIVar miVar = variable.getMIVar();
		//remove the underlying var-object now.
		CommandFactory factory = miSession.getCommandFactory();
		MIVarDelete var = factory.createMIVarDelete(miVar.getVarName());
		try {
			miSession.postCommand(var);
			var.getMIInfo();
		} catch (MIException e) {
			//throw new MI2CDIException(e);
		}
		List varList = getVariableList(target);
		varList.remove(variable);

		// remove any children
		ICDIVariable[] children = variable.children;
		if (children != null) {
			for (int i = 0; i < children.length; ++i) {
				if (children[0] instanceof Variable) {
					Variable child = (Variable)children[i];
					MIVarDeletedEvent event = new MIVarDeletedEvent(miSession, child.getMIVar().getVarName());
					miSession.fireEvent(event);
				}
			}
		}
		miSession.fireEvent(new MIVarDeletedEvent(miSession, variable.getMIVar().getVarName()));
	}

}
