/*******************************************************************************
 * Copyright (c) 2000, 2012 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.jdt.internal.debug.eval.ast.instructions;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.jdt.debug.core.IJavaArrayType;
import org.eclipse.jdt.debug.core.IJavaClassObject;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaFieldVariable;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaReferenceType;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.eval.ast.engine.IRuntimeContext;
import org.eclipse.jdt.internal.debug.eval.ast.engine.Interpreter;
import org.eclipse.osgi.util.NLS;

/**
 * Common behavior for instructions.
 */
public abstract class Instruction {

	private Interpreter fInterpreter;

	public abstract int getSize();

	public void setInterpreter(Interpreter interpreter) {
		fInterpreter = interpreter;
	}

	public void setLastValue(IJavaValue value) {
		fInterpreter.setLastValue(value);
	}

	public void stop() {
		fInterpreter.stop();
	}

	public static int getBinaryPromotionType(int left, int right) {
		return fTypeTable[left][right];
	}

	public abstract void execute() throws CoreException;

	protected IRuntimeContext getContext() {
		return fInterpreter.getContext();
	}

	protected IJavaDebugTarget getVM() {
		return getContext().getVM();
	}

	/**
	 * Return the internal variable with the given name.
	 *
	 * @see Interpreter#getInternalVariable(String)
	 */
	protected IVariable getInternalVariable(String name) {
		return fInterpreter.getInternalVariable(name);
	}

	/**
	 * Create and return a new internal variable with the given name and the
	 * given type.
	 *
	 * @see Interpreter#createInternalVariable(String, String)
	 */
	protected IVariable createInternalVariable(String name,
			IJavaType referencType) {
		return fInterpreter.createInternalVariable(name, referencType);
	}

	/**
	 * Answers the instance of Class that the given type represents.
	 */
	protected IJavaObject getClassObject(IJavaType type) throws CoreException {
		if (type instanceof IJavaReferenceType) {
			return ((IJavaReferenceType) type).getClassObject();
		}
		return null;
	}

	protected void jump(int offset) {
		fInterpreter.jump(offset);
	}

	protected void push(Object object) {
		fInterpreter.push(object);
	}

	protected Object pop() {
		return fInterpreter.pop();
	}

	protected IJavaValue popValue() throws CoreException {
		Object element = fInterpreter.pop();
		if (element instanceof IJavaVariable) {
			return (IJavaValue) ((IJavaVariable) element).getValue();
		}
		return (IJavaValue) element;
	}

	protected void pushNewValue(boolean value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(boolean value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(byte value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(byte value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(short value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(short value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(int value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(int value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(long value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(long value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(char value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(char value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(float value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(float value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(double value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(double value) {
		return getVM().newValue(value);
	}

	protected void pushNewValue(String value) {
		fInterpreter.push(newValue(value));
	}

	protected IJavaValue newValue(String value) {
		return getVM().newValue(value);
	}

	protected void pushNullValue() {
		fInterpreter.push(nullValue());
	}

	protected IJavaValue nullValue() {
		return getVM().nullValue();
	}

	public static int getUnaryPromotionType(int typeId) {
		return fTypeTable[typeId][T_int];
	}

	protected IJavaType getType(String qualifiedName) throws CoreException {
		// Force the class to be loaded, and record the class reference
		// for later use if there are multiple classes with the same name.
		IJavaClassObject classReference = getContext().classForName(
				qualifiedName);
		// Found many classes, look for the right one for this scope.
		if (classReference == null) {
			throw new CoreException(new Status(IStatus.ERROR,
					JDIDebugPlugin.getUniqueIdentifier(), IStatus.OK,
					NLS.bind(
							InstructionsEvaluationMessages.Instruction_No_type,
							new String[] { qualifiedName }), null));
		}
		return classReference.getInstanceType();
	}

	/**
	 * Returns the primitive type with the given name.
	 *
	 * @param name
	 *            type name, for example - "int"
	 * @return primitive type
	 * @throws CoreException
	 */
	protected IJavaType getPrimitiveType(String name) throws CoreException {
		IJavaReferenceType type = null;
		if ("boolean".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Boolean"); //$NON-NLS-1$
		} else if ("byte".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Byte"); //$NON-NLS-1$
		} else if ("char".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Character"); //$NON-NLS-1$
		} else if ("double".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Double"); //$NON-NLS-1$
		} else if ("float".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Float"); //$NON-NLS-1$
		} else if ("int".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Integer"); //$NON-NLS-1$
		} else if ("long".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Long"); //$NON-NLS-1$
		} else if ("short".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Short"); //$NON-NLS-1$
		} else if ("void".equals(name)) { //$NON-NLS-1$
			type = (IJavaReferenceType) getType("java.lang.Void"); //$NON-NLS-1$
		}
		if (type != null) {
			IJavaFieldVariable field = type.getField("TYPE"); //$NON-NLS-1$
			IJavaClassObject clazz = (IJavaClassObject) field.getValue();
			return clazz.getInstanceType();
		}
		throw new CoreException(new Status(IStatus.ERROR,
				JDIDebugPlugin.getUniqueIdentifier(), IStatus.OK,
				NLS.bind(
						InstructionsEvaluationMessages.Instruction_No_type,
						new String[] { name }), null));
	}

	protected IJavaArrayType getArrayType(String typeSignature, int dimension)
			throws CoreException {
		String qualifiedName = RuntimeSignature.toString(typeSignature);
		String braces = ""; //$NON-NLS-1$
		for (int i = 0; i < dimension; i++) {
			qualifiedName += "[]"; //$NON-NLS-1$
			braces += "["; //$NON-NLS-1$
		}
		String signature = braces + typeSignature;
		// Force the class to be loaded, and record the class reference
		// for later use if there are multiple classes with the same name.
		IJavaObject classReference = getContext().classForName(signature);
		if (classReference == null) {
			throw new CoreException(new Status(IStatus.ERROR,
					JDIDebugPlugin.getUniqueIdentifier(), IStatus.OK,
					NLS.bind(
							InstructionsEvaluationMessages.Instruction_No_type,
							new String[] { qualifiedName }), null));
		}
		IJavaType[] types = getVM().getJavaTypes(qualifiedName);
		checkTypes(types, qualifiedName);
		if (types.length == 1) {
			// Found only one class.
			return (IJavaArrayType) types[0];
		}
		// Found many classes, look for the right one for this scope.
		for (IJavaType type : types) {
			if (classReference.equals(getClassObject(type))) {
				return (IJavaArrayType) type;
			}
		}

		// At this point a very strange thing has happened,
		// the VM was able to return multiple types in the classesByName
		// call, but none of them were the class that was returned in
		// the classForName call.

		throw new CoreException(new Status(IStatus.ERROR,
				JDIDebugPlugin.getUniqueIdentifier(), IStatus.OK,
				NLS.bind(
						InstructionsEvaluationMessages.Instruction_No_type,
						new String[] { qualifiedName }), null));
	}

	protected void checkTypes(IJavaType[] types, String qualifiedName)
			throws CoreException {
		if (types == null || types.length == 0) {
			throw new CoreException(new Status(IStatus.ERROR,
					JDIDebugPlugin.getUniqueIdentifier(), IStatus.OK,
					NLS.bind(
							InstructionsEvaluationMessages.Instruction_No_type,
							new String[] { qualifiedName }), null));
		}
	}

	static public final int T_undefined = 0;
	static public final int T_Object = 1;
	static public final int T_char = 2;
	static public final int T_byte = 3;
	static public final int T_short = 4;
	static public final int T_boolean = 5;
	static public final int T_void = 6;
	static public final int T_long = 7;
	static public final int T_double = 8;
	static public final int T_float = 9;
	static public final int T_int = 10;
	static public final int T_String = 11;
	static public final int T_null = 12;

	private static final int[][] fTypeTable = {
			/* undefined */{ T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined },
			/* object */{ T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_String,
					T_undefined },
			/* char */{ T_undefined, T_undefined, T_int, T_int, T_int,
					T_undefined, T_undefined, T_long, T_double, T_float, T_int,
					T_String, T_undefined },
			/* byte */{ T_undefined, T_undefined, T_int, T_int, T_int,
					T_undefined, T_undefined, T_long, T_double, T_float, T_int,
					T_String, T_undefined },
			/* short */{ T_undefined, T_undefined, T_int, T_int, T_int,
					T_undefined, T_undefined, T_long, T_double, T_float, T_int,
					T_String, T_undefined },
			/* boolean */{ T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_boolean, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_String,
					T_undefined },
			/* void */{ T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined },
			/* long */{ T_undefined, T_undefined, T_long, T_long, T_long,
					T_undefined, T_undefined, T_long, T_double, T_float,
					T_long, T_String, T_undefined },
			/* double */{ T_undefined, T_undefined, T_double, T_double,
					T_double, T_undefined, T_undefined, T_double, T_double,
					T_double, T_double, T_String, T_undefined },
			/* float */{ T_undefined, T_undefined, T_float, T_float, T_float,
					T_undefined, T_undefined, T_float, T_double, T_float,
					T_float, T_String, T_undefined },
			/* int */{ T_undefined, T_undefined, T_int, T_int, T_int,
					T_undefined, T_undefined, T_long, T_double, T_float, T_int,
					T_String, T_undefined },
			/* String */{ T_undefined, T_String, T_String, T_String, T_String,
					T_String, T_undefined, T_String, T_String, T_String,
					T_String, T_String, T_String },
			/* null */{ T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_undefined,
					T_undefined, T_undefined, T_undefined, T_String,
					T_undefined }, };
}
