/*******************************************************************************
 * 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.model;

import org.eclipse.cdt.debug.core.cdi.CDIException;
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.ICDIVariableDescriptor;
import org.eclipse.cdt.debug.core.cdi.model.type.ICDIType;
import org.eclipse.cdt.debug.mi.core.MIException;
import org.eclipse.cdt.debug.mi.core.MISession;
import org.eclipse.cdt.debug.mi.core.cdi.MI2CDIException;
import org.eclipse.cdt.debug.mi.core.cdi.Session;
import org.eclipse.cdt.debug.mi.core.cdi.SourceManager;
import org.eclipse.cdt.debug.mi.core.cdi.VariableManager;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.IncompleteType;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
import org.eclipse.cdt.debug.mi.core.command.MIDataEvaluateExpression;
import org.eclipse.cdt.debug.mi.core.output.MIDataEvaluateExpressionInfo;
import org.eclipse.cdt.debug.mi.core.cdi.CdiResources;

/**
 */
public abstract class VariableDescriptor extends CObject implements ICDIVariableDescriptor {

	// Casting info.
	String[] castingTypes;
	int castingIndex;
	int castingLength;

	String fName;
	int position;
	StackFrame fStackFrame;
	Thread fThread;
	int stackdepth;

	String qualifiedName = null;
	String fFullName = null;
	protected ICDIType fType = null;
	protected String fTypename = null;
	String sizeof = null;

	/**
	 * Copy constructor.
	 * @param desc
	 */
	public VariableDescriptor(VariableDescriptor desc) {
		super((Target)desc.getTarget());
		fName = desc.getName();
		fFullName = desc.fFullName;
		sizeof = desc.sizeof;
		fType = desc.fType;
		try {
			fStackFrame = (StackFrame)desc.getStackFrame();
			fThread = (Thread)desc.getThread();
		} catch (CDIException e) {
		}
		position = desc.getPosition();
		stackdepth = desc.getStackDepth();
		castingIndex = desc.getCastingArrayStart();
		castingLength = desc.getCastingArrayEnd();
		castingTypes = desc.getCastingTypes();
	}

	public VariableDescriptor(Target target, Thread thread, StackFrame stack, String n, String fn, int pos, int depth) {
		super(target);
		fName = n;
		fFullName = fn;
		fStackFrame = stack;
		fThread = thread;
		position = pos;
		stackdepth = depth;
	}

	public int getPosition() {
		return position;
	}

	public int getStackDepth() {
		return stackdepth;
	}

	public void setCastingArrayStart(int start) {
		castingIndex = start;
	}
	public int getCastingArrayStart() {
		return castingIndex;
	}

	public void setCastingArrayEnd(int end) {
		castingLength = end;
	}
	public int getCastingArrayEnd() {
		return castingLength;
	}

	public void setCastingTypes(String[] t) {
		castingTypes = t;
	}
	public String[] getCastingTypes() {
		return castingTypes;
	}

	/**
	 * If the variable was a cast encode the string appropriately for GDB.
	 * For example castin to an array is of 2 elements:
	 *  (foo)@2
	 * @return
	 */
	public String encodeVariable() {
		String fn = getFullName();
		if (castingLength > 0 || castingIndex > 0) {
			StringBuffer buffer = new StringBuffer();
			buffer.append("*("); //$NON-NLS-1$
			buffer.append('(').append(fn).append(')');
			buffer.append('+').append(castingIndex).append(')');
			buffer.append('@').append(castingLength);
			fn = buffer.toString();
		} else if (castingTypes != null && castingTypes.length > 0) {
			StringBuffer buffer = new StringBuffer();
			for (int i = 0; i < castingTypes.length; ++i) {
				if (castingTypes[i] != null && castingTypes[i].length() > 0) {
					if (buffer.length() == 0) {
						buffer.append('(').append(castingTypes[i]).append(')');
						buffer.append(fn);
					} else {
						buffer.insert(0, '(');
						buffer.append(')');
						StringBuffer b = new StringBuffer();
						b.append('(').append(castingTypes[i]).append(')');
						buffer.insert(0, b.toString());
					}
				}
			}
			fn = buffer.toString();
		}
		return fn;
	}

	public String getFullName() {
		if (fFullName == null) {
			fFullName = getName();
		}
		return fFullName;
	}

	protected ICDIType getFromTypeCache(String nameType) throws CDIException {
		StackFrame frame = (StackFrame)getStackFrame();
		ICDIType detailedType = null;
		if (frame != null) {
			detailedType = frame.getFromTypeCache(nameType);
		}
		return detailedType;
	}

	protected void addToTypeCache(String nameType, ICDIType typeDefinition) throws CDIException {
		StackFrame frame = (StackFrame)getStackFrame();
		if (frame != null) {
			frame.addToTypeCache(nameType, typeDefinition);
		}
	}

	/**
	 * @see org.eclipse.cdt.debug.core.cdi.ICDIVariableDescriptor#getName()
	 */
	@Override
	public String getName() {
		return fName;
	}

	/**
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariable#getType()
	 */
	@Override
	public ICDIType getType() throws CDIException {
		if (fType == null) {
			String nametype = getTypeName();
			Target target = (Target)getTarget();
			Session session = (Session) target.getSession();
			SourceManager sourceMgr = session.getSourceManager();
			try {
				fType = sourceMgr.getType(target, nametype);
			} catch (CDIException e) {
				// We are here because the parser did not recognize the type, it may be something
				// like "builtin_x86_vector" or even a class or a typedef
				// typedef struct foobar Foobar_t
				// for this case we need to call "Ptype" for more details.

				// For speed we save the type definitions in the stackframe, try it first.
				fType = getFromTypeCache(nametype);
				if (fType == null) {
					// Try with ptype.
					try {
						String ptype = sourceMgr.getDetailTypeName(target, nametype);
						fType = sourceMgr.getType(target, ptype);
					} catch (CDIException ex) {
						// Some version of gdb does not work on the name of the class
						// ex: class data foo --> ptype data --> fails
						// ex: class data foo --> ptype foo --> succeed
						StackFrame frame = (StackFrame)getStackFrame();
						if (frame == null) {
							Thread thread = (Thread)getThread();
							if (thread != null) {
								frame = thread.getCurrentStackFrame();
							} else {
								frame = ((Thread)target.getCurrentThread()).getCurrentStackFrame();
							}
						}
						try {
							String ptype = sourceMgr.getDetailTypeNameFromVariable(frame, getQualifiedName());
							fType = sourceMgr.getType(target, ptype);
						} catch (CDIException e2) {
							// give up.
						}
					}
				}
			}
			if (fType == null) {
				fType = new IncompleteType(target, nametype);
			}
			// cache the result
			addToTypeCache(nametype, fType);
		}
		return fType;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor#sizeof()
	 */
	@Override
	public int sizeof() throws CDIException {
		if (sizeof == null) {
			Target target = (Target) getTarget();
			Thread currentThread = (Thread)target.getCurrentThread();
			StackFrame currentFrame = currentThread.getCurrentStackFrame();
			StackFrame frame = (StackFrame)getStackFrame();
			Thread thread = (Thread)getThread();
			synchronized(target.getLock()) {
			try {
				if (frame != null) {
					target.setCurrentThread(frame.getThread(), false);				
					((Thread)frame.getThread()).setCurrentStackFrame(frame, false);
				} else if (thread != null) {
					target.setCurrentThread(thread, false);				
				}
				MISession mi = target.getMISession();
				CommandFactory factory = mi.getCommandFactory();
				String exp = "sizeof(" + getTypeName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
				MIDataEvaluateExpression evaluate = factory.createMIDataEvaluateExpression(exp);
				mi.postCommand(evaluate);
				MIDataEvaluateExpressionInfo info = evaluate.getMIDataEvaluateExpressionInfo();
				if (info == null) {
					throw new CDIException(CdiResources.getString("cdi.model.VariableDescriptor.Target_not_responding")); //$NON-NLS-1$
				}
				sizeof = info.getExpression();
			} catch (MIException e) {
				throw new MI2CDIException(e);
			} finally {
				if (frame != null) {
					target.setCurrentThread(currentThread, false);
					currentThread.setCurrentStackFrame(currentFrame, false);
				} else if (thread != null) {
					target.setCurrentThread(currentThread, false);
				}
			}
			}
		}

		if (sizeof != null) {
			try {
				return Integer.parseInt(sizeof);
			} catch (NumberFormatException e) {
				throw new CDIException(e.getMessage());
			}
		}
		return 0;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor#getStackFrame()
	 */
	public ICDIStackFrame getStackFrame() throws CDIException {
		return fStackFrame;
	}

	public ICDIThread getThread() throws CDIException {
		return fThread;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor#getTypeName()
	 */
	@Override
	public String getTypeName() throws CDIException {
		if (fTypename == null) {
			Target target = (Target)getTarget();
			StackFrame frame = (StackFrame)getStackFrame();
			if (frame == null) {
				Thread thread = (Thread)getThread();
				if (thread != null) {
					frame = thread.getCurrentStackFrame();
				} else {
					frame = ((Thread)target.getCurrentThread()).getCurrentStackFrame();
				}
			}
			Session session = (Session) target.getSession();
			SourceManager sourceMgr = session.getSourceManager();
			if (frame != null) {
				fTypename = sourceMgr.getTypeNameFromVariable(frame, getQualifiedName());
			} else {
				fTypename = sourceMgr.getTypeName(target, getQualifiedName());
			}
		}
		return fTypename;
	}

	/**
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor#getQualifiedName()
	 */
	@Override
	public String getQualifiedName() throws CDIException {
		if (qualifiedName == null) {
			qualifiedName = encodeVariable();
		}
		return qualifiedName;
	}

	public static boolean equalsCasting(VariableDescriptor var1, VariableDescriptor var2) {
		String[] castings1 = var1.getCastingTypes();
		String[] castings2 = var2.getCastingTypes();
		if (castings1 == null && castings2 == null) {
			return true;
		} else if (castings1 != null && castings2 != null && castings1.length == castings2.length) {
			for (int i = 0; i < castings1.length; ++i) {
				if (!castings1[i].equals(castings2[i])) {
					return false;
				}
			}
			return true;
		}
		return false;
	}
	/**
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor#equals(ICDIVariableDescriptor)
	 */
	@Override
	public boolean equals(ICDIVariableDescriptor varDesc) {
		if (varDesc instanceof VariableDescriptor) {
			VariableDescriptor desc = (VariableDescriptor) varDesc;
			if (desc.getFullName().equals(getFullName())
				&& desc.getName().equals(getName()) // see bug #113364
				&& desc.getCastingArrayStart() == getCastingArrayStart()
				&& desc.getCastingArrayEnd() == getCastingArrayEnd()
				&& equalsCasting(desc, this)) {

				// Check the threads
				ICDIThread varThread = null;
				ICDIThread ourThread = null;
				try {
					varThread = desc.getThread();
					ourThread = getThread();
				} catch (CDIException e) {
					// ignore
				}
				if ((ourThread == null && varThread == null) ||
						(varThread != null && ourThread != null && varThread.equals(ourThread))) {
					// check the stackFrames
					ICDIStackFrame varFrame = null;
					ICDIStackFrame ourFrame = null;
					try {
						varFrame = desc.getStackFrame();
						ourFrame = getStackFrame();
					} catch (CDIException e) {
						// ignore
					}
					if (ourFrame == null && varFrame == null) {
						return true;
					} else if (varFrame != null && ourFrame != null && varFrame.equals(ourFrame)) {
						if (desc.getStackDepth() == getStackDepth()) {
							if (desc.getPosition() == getPosition()) {
								return true;
							}
						}
					}
				}
				return false;
			}
		}
		return super.equals(varDesc);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor#getVariableDescriptorAsArray(org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor, int, int)
	 */
	@Override
	public ICDIVariableDescriptor getVariableDescriptorAsArray(int start, int length) throws CDIException {
		Session session = (Session)getTarget().getSession();
		VariableManager mgr = session.getVariableManager();
		return mgr.getVariableDescriptorAsArray(this, start, length);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor#getVariableDescriptorAsType(org.eclipse.cdt.debug.core.cdi.model.ICDIVariableDescriptor, java.lang.String)
	 */
	@Override
	public ICDIVariableDescriptor getVariableDescriptorAsType(String type) throws CDIException {
		Session session = (Session)getTarget().getSession();
		VariableManager mgr = session.getVariableManager();
		return mgr.getVariableDescriptorAsType(this, type);
	}

}
