/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdi.internal;


import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.jdi.internal.jdwp.JdwpCommandPacket;
import org.eclipse.jdi.internal.jdwp.JdwpFrameID;
import org.eclipse.jdi.internal.jdwp.JdwpID;
import org.eclipse.jdi.internal.jdwp.JdwpReplyPacket;

import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.InvalidStackFrameException;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Locatable;
import com.sun.jdi.Location;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMMismatchException;
import com.sun.jdi.Value;

/**
 * this class implements the corresponding interfaces
 * declared by the JDI specification. See the com.sun.jdi package
 * for more information.
 *
 */
public class StackFrameImpl extends MirrorImpl implements StackFrame, Locatable {
	/** FrameID that corresponds to this reference. */
	private JdwpFrameID fFrameID;
	/** Thread under which this frame's method is running. */
	private ThreadReferenceImpl fThread;
	/** Location of the current instruction in the frame. */
	private LocationImpl fLocation;
	
	/**
	 * Creates new StackFrameImpl.
	 */
	public StackFrameImpl(VirtualMachineImpl vmImpl, JdwpFrameID ID, ThreadReferenceImpl thread, LocationImpl location) {
		super("StackFrame", vmImpl); //$NON-NLS-1$
		fFrameID = ID;
		fThread = thread;
		fLocation = location;
	}

	/**
	 * @return Returns the Value of a LocalVariable in this frame. 
	 */
	public Value getValue(LocalVariable variable) throws IllegalArgumentException, InvalidStackFrameException, VMMismatchException {
		ArrayList list = new ArrayList(1);
		list.add(variable);
		return (ValueImpl)getValues(list).get(variable);
	}
	
	/**
	 * @return Returns the values of multiple local variables in this frame.
	 */
	public Map getValues(List variables) throws IllegalArgumentException, InvalidStackFrameException, VMMismatchException {
		// Note that this information should not be cached.
		Map map = new HashMap(variables.size());
		// if the variable list is empty, nothing to do
		if (variables.isEmpty()) {
			return map;
		}
		/*
		 * If 'this' is requested, we have to use a special JDWP request.
		 * Therefore, we remember the positions in the list of requests for 'this'.
		 */
		int sizeAll = variables.size();
		int sizeThis = 0;
		boolean[] isThisValue = new boolean[sizeAll];
		for (int i = 0; i < sizeAll; i++) {
			LocalVariableImpl var = (LocalVariableImpl)variables.get(i);
			isThisValue[i] = var.isThis();
			if (isThisValue[i]) {
				sizeThis++;
			}
		}
		int sizeNotThis = sizeAll - sizeThis;
		
		if (sizeThis > 0) {
			Value thisValue = thisObject();
			for (int i = 0; i < sizeAll; i++) {
				if (isThisValue[i]) {
					map.put(variables.get(i), thisValue);
				}
			}
		}
		
		// If only 'this' was requested, we're finished.
		if (sizeNotThis == 0) {
			return map;
		}
			
		// Request values for local variables other than 'this'.
		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			writeWithThread(this, outData);
			writeInt(sizeNotThis, "size", outData); //$NON-NLS-1$
			for (int i = 0; i < sizeAll; i++) {
				if (!isThisValue[i]) {
					LocalVariableImpl var = (LocalVariableImpl)variables.get(i);
					checkVM(var);
					writeInt(var.slot(), "slot", outData); //$NON-NLS-1$
					writeByte(var.tag(), "tag", JdwpID.tagMap(), outData); //$NON-NLS-1$
				}
			}
			JdwpReplyPacket replyPacket = requestVM(JdwpCommandPacket.SF_GET_VALUES, outBytes);
			defaultReplyErrorHandler(replyPacket.errorCode());
		
			DataInputStream replyData = replyPacket.dataInStream();
			int nrOfElements = readInt("elements", replyData); //$NON-NLS-1$
			if (nrOfElements != sizeNotThis) 
				throw new InternalError(JDIMessages.getString("StackFrameImpl.Retrieved_a_different_number_of_values_from_the_VM_than_requested_1")); //$NON-NLS-1$
			
			for (int i = 0, j = 0; i < sizeAll; i++) {
				if (!isThisValue[i])
					map.put(variables.get(j++), ValueImpl.readWithTag(this, replyData));
			}
			return map;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns the Location of the current instruction in the frame.
	 */
	public Location location() {
		return fLocation;
	}
	
	/**
	 * Sets the Value of a LocalVariable in this frame. 
	 */
	public void setValue(LocalVariable var, Value value) throws InvalidTypeException, ClassNotLoadedException {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			((ThreadReferenceImpl)thread()).write(this, outData);
			write(this, outData);
			writeInt(1, "size", outData);	// We only set one field //$NON-NLS-1$
			checkVM(var);
			writeInt(((LocalVariableImpl)var).slot(), "slot", outData); //$NON-NLS-1$
			
			// check the type and the vm of the value, convert the value if needed.
			ValueImpl checkedValue= ValueImpl.checkValue(value, var.type(), virtualMachineImpl());
			
			if (checkedValue != null) {
				checkedValue.writeWithTag(this, outData);
			} else {
				ValueImpl.writeNullWithTag(this, outData);
			}
	
			JdwpReplyPacket replyPacket = requestVM(JdwpCommandPacket.SF_SET_VALUES, outBytes);
			switch (replyPacket.errorCode()) {
				case JdwpReplyPacket.TYPE_MISMATCH:
					throw new InvalidTypeException();
				case JdwpReplyPacket.INVALID_CLASS:
					throw new ClassNotLoadedException(var.typeName());
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns the value of 'this' for the current frame.
	 */
	public ObjectReference thisObject() throws InvalidStackFrameException {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			writeWithThread(this, outData);
	
			JdwpReplyPacket replyPacket = requestVM(JdwpCommandPacket.SF_THIS_OBJECT, outBytes);
			defaultReplyErrorHandler(replyPacket.errorCode());
			
			DataInputStream replyData = replyPacket.dataInStream();
			ObjectReference result = ObjectReferenceImpl.readObjectRefWithTag(this, replyData);
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns the thread under which this frame's method is running.
	 */
	public ThreadReference thread() {
		return fThread;
	}
	
	/**
	 * @return Returns a LocalVariable that matches the given name and is visible at the current frame location.
	 */
	public LocalVariable visibleVariableByName(String name) throws AbsentInformationException {
		Iterator iter = visibleVariables().iterator();
		while (iter.hasNext()) {
			LocalVariableImpl var = (LocalVariableImpl)iter.next();
			if (var.name().equals(name)) {
				return var;
			}
		}
		
		return null;
	}
	
	/**
	 * @return Returns the values of multiple local variables in this frame. 
	 */
	public List visibleVariables() throws AbsentInformationException {
		List variables= fLocation.method().variables();
		Iterator iter = variables.iterator();
		List visibleVars = new ArrayList(variables.size());
		while (iter.hasNext()) {
			LocalVariableImpl var = (LocalVariableImpl)iter.next();
			// Only return local variables other than the this pointer.
			if (var.isVisible(this) && !var.isThis()) {
				visibleVars.add(var);
			}
		}
		return visibleVars;
	}
	
	/** 
	 * @return Returns the hash code value.
	 */
	public int hashCode() {
		return fThread.hashCode() + fFrameID.hashCode();
	}
	
	/**
	 * @return Returns true if two mirrors refer to the same entity in the target VM.
	 * @see java.lang.Object#equals(Object)
	 */
	public boolean equals(Object object) {
		return object != null && object.getClass().equals(this.getClass()) && fThread.equals(((StackFrameImpl)object).fThread) && fFrameID.equals(((StackFrameImpl)object).fFrameID);
	}
	
	/**
	 * Writes JDWP representation.
	 */
	public void write(MirrorImpl target, DataOutputStream out) throws IOException {
		fFrameID.write(out);
		if (target.fVerboseWriter != null) {
			target.fVerboseWriter.println("stackFrame", fFrameID.value()); //$NON-NLS-1$
		}
	}
	
	/**
	 * Writes JDWP representation.
	 */
	public void writeWithThread(MirrorImpl target, DataOutputStream out) throws IOException {
		fThread.write(target, out);
		write(target, out);
	}
	
	/**
	 * @return Reads JDWP representation and returns new instance.
	 */
	public static StackFrameImpl readWithLocation(MirrorImpl target, ThreadReferenceImpl thread, DataInputStream in)  throws IOException {
		VirtualMachineImpl vmImpl = target.virtualMachineImpl();
		JdwpFrameID ID = new JdwpFrameID(vmImpl);
		ID.read(in);
		if (target.fVerboseWriter != null) {
			target.fVerboseWriter.println("stackFrame", ID.value()); //$NON-NLS-1$
		}

		if (ID.isNull()) {
			return null;
		}
		LocationImpl location = LocationImpl.read(target, in);
		if (location == null) {
			return null;
		}

		return new StackFrameImpl(vmImpl, ID, thread, location);
	}
}
