/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdi.internal;

import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.StackFrame;
import com.sun.jdi.Type;
import com.sun.jdi.VMMismatchException;

/**
 * this class implements the corresponding interfaces declared by the JDI
 * specification. See the com.sun.jdi package for more information.
 *
 */
public class LocalVariableImpl extends MirrorImpl implements LocalVariable, Comparable<LocalVariable> {
	/** Method that holds local variable. */
	private MethodImpl fMethod;
	/** First code index at which the variable is visible (unsigned). */
	private long fCodeIndex;
	/** The variable's name. */
	private String fName;
	/** The variable type's JNI signature. */
	private String fSignature;
	/** The variable type generic signature. */
	private String fGenericSignature;
	/** The variable's type */
	private Type fType;
	/** The variables type name */
	private String fTypeName;
	/**
	 * Unsigned value used in conjunction with codeIndex. The variable can be
	 * get or set only when the current codeIndex <= current frame code index <
	 * code index + length.
	 * <p>
	 * The length is set to -1 when this variable represents an inferred
	 * argument (when local variable info is unavailable). We assume that such
	 * arguments are visible for the entire method.
	 * </p>
	 * */
	private int fLength;
	/** The local variable's index in its frame. */
	private int fSlot;
	/** Is the local variable an argument of its method? */
	private boolean fIsArgument;

	public LocalVariableImpl(VirtualMachineImpl vmImpl, MethodImpl method,
			long codeIndex, String name, String signature,
			String genericSignature, int length, int slot, boolean isArgument) {
		super("LocalVariable", vmImpl); //$NON-NLS-1$
		fMethod = method;
		fCodeIndex = codeIndex;
		fName = name;
		fSignature = signature;
		fGenericSignature = genericSignature;
		fLength = length;
		fSlot = slot;
		fIsArgument = isArgument;
	}

	/**
	 * @return Returns local variable's index in its frame.
	 */
	public int slot() {
		return fSlot;
	}

	/**
	 * @return Returns the hash code value.
	 */
	@Override
	public int hashCode() {
		return fMethod.hashCode() + (int) fCodeIndex + fSlot;
	}

	/**
	 * @return Returns true if two mirrors refer to the same entity in the
	 *         target VM.
	 * @see java.lang.Object#equals(Object).
	 */
	@Override
	public boolean equals(Object object) {
		if (object != null && object.getClass().equals(this.getClass())) {
			LocalVariableImpl loc = (LocalVariableImpl) object;
			return fMethod.equals(loc.fMethod) && fCodeIndex == loc.fCodeIndex
					&& fSlot == loc.fSlot;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(LocalVariable variable) {
		if (variable == null || !variable.getClass().equals(this.getClass()))
			throw new ClassCastException(
					JDIMessages.LocalVariableImpl_Can__t_compare_local_variable_to_given_object_1);

		// See if methods are the same, if not return comparison between
		// methods.
		LocalVariableImpl var2 = (LocalVariableImpl) variable;
		if (!method().equals(var2.method()))
			return method().compareTo(var2.method());

		// Return comparison between the index of each local variable in its
		// stack frame.
		// Code indexes must be treated as unsigned. This matters if you have to
		// compare them.
		if (fCodeIndex < 0 || var2.fCodeIndex < 0)
			throw new InternalError(
					JDIMessages.LocalVariableImpl_Code_indexes_are_assumed_to_be_always_positive_2);

		long index2 = var2.fCodeIndex;
		if (fCodeIndex < index2)
			return -1;
		else if (fCodeIndex > index2)
			return 1;
		else
			return 0;
	}

	/**
	 * @return Returns true if this variable is an argument to its method.
	 */
	@Override
	public boolean isArgument() {
		return fIsArgument;
	}

	@Override
	public boolean isVisible(StackFrame frame) throws IllegalArgumentException,
			VMMismatchException {
		checkVM(frame);
		StackFrameImpl frameImpl = (StackFrameImpl) frame;
		if (!fMethod.equals(frameImpl.location().method()))
			throw new IllegalArgumentException(
					JDIMessages.LocalVariableImpl_The_stack_frame__s_method_does_not_match_this_variable__s_method_3);

		if (fLength == -1) {
			// inferred argument - assume visible for entire method
			return true;
		}
		long currentIndex = frameImpl.location().codeIndex();

		// Code indexes must be treated as unsigned. This matters if you have to
		// compare them.
		if (currentIndex >= 0 && fCodeIndex >= 0 && fCodeIndex + fLength >= 0)
			return fCodeIndex <= currentIndex
					&& currentIndex < fCodeIndex + fLength;

		throw new InternalError(
				JDIMessages.LocalVariableImpl_Code_indexes_are_assumed_to_be_always_positive_4);
	}

	/**
	 * @return Returns the name of the local variable.
	 */
	@Override
	public String name() {
		return fName;
	}

	/**
	 * @return Returns the signature of the local variable.
	 */
	@Override
	public String signature() {
		return fSignature;
	}

	/**
	 * @return Returns the type of the this LocalVariable.
	 */
	@Override
	public Type type() throws ClassNotLoadedException {
		if (fType == null) {
			fType = TypeImpl.create(virtualMachineImpl(), fSignature, method()
					.declaringType().classLoader());
		}
		return fType;
	}

	/**
	 * @return Returns a text representation of the declared type of this
	 *         variable.
	 */
	@Override
	public String typeName() {
		if (fTypeName == null) {
			fTypeName = TypeImpl.signatureToName(fSignature);
		}
		return fTypeName;
	}

	/**
	 * @return Returns the tag of the declared type of this variable.
	 */
	public byte tag() {
		return TypeImpl.signatureToTag(fSignature);
	}

	/**
	 * @return Returns the method that holds the local variable.
	 */
	public MethodImpl method() {
		return fMethod;
	}

	/**
	 * @return Returns true if the local variable is the 'this' pointer.
	 */
	public boolean isThis() {
		return slot() == 0 && !method().isStatic();
	}

	/**
	 * @return Returns description of Mirror object.
	 */
	@Override
	public String toString() {
		return fName;
	}

	@Override
	public String genericSignature() {
		return fGenericSignature;
	}

}
