/*******************************************************************************
 * 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 java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jdi.internal.jdwp.JdwpArrayID;
import org.eclipse.jdi.internal.jdwp.JdwpCommandPacket;
import org.eclipse.jdi.internal.jdwp.JdwpID;
import org.eclipse.jdi.internal.jdwp.JdwpObjectID;
import org.eclipse.jdi.internal.jdwp.JdwpReplyPacket;

import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.ArrayType;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.Field;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.Type;
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 ArrayTypeImpl extends ReferenceTypeImpl implements ArrayType {
	/** JDWP Tag. */
	public static final byte typeTag = JdwpID.TYPE_TAG_ARRAY;
	/** component type */
	private Type fComponentType;
	/** Component type name */
	private String fComponentTypeName;

	/**
	 * Creates new ArrayTypeImpl.
	 * @param vmImpl the VM
	 * @param arrayID the array ID
	 */
	public ArrayTypeImpl(VirtualMachineImpl vmImpl, JdwpArrayID arrayID) {
		super("ArrayType", vmImpl, arrayID); //$NON-NLS-1$
	}

	/**
	 * Creates new ArrayTypeImpl.
	 * @param vmImpl the VM
	 * @param arrayID the array ID
	 * @param signature the array type signature
	 * @param genericSignature the array type generic signature
	 */
	public ArrayTypeImpl(VirtualMachineImpl vmImpl, JdwpArrayID arrayID,
			String signature, String genericSignature) {
		super("ArrayType", vmImpl, arrayID, signature, genericSignature); //$NON-NLS-1$
	}

	/**
	 * @return Returns type tag.
	 */
	@Override
	public byte typeTag() {
		return typeTag;
	}

	/**
	 * @return Create a null value instance of the type.
	 */
	@Override
	public Value createNullValue() {
		return new ArrayReferenceImpl(virtualMachineImpl(), new JdwpObjectID(
				virtualMachineImpl()));
	}

	/**
	 * @return Returns the JNI signature of the components of this array class.
	 */
	@Override
	public String componentSignature() {
		return signature().substring(1);
	}

	/**
	 * @return Returns the type of the array components.
	 * @throws ClassNotLoadedException if the class has not been loaded
	 */
	@Override
	public Type componentType() throws ClassNotLoadedException {
		if (fComponentType == null) {
			fComponentType = TypeImpl.create(virtualMachineImpl(),
					componentSignature(), classLoader());
		}
		return fComponentType;
	}

	/**
	 * @return Returns a text representation of the component type.
	 */
	@Override
	public String componentTypeName() {
		if (fComponentTypeName == null) {
			fComponentTypeName = signatureToName(componentSignature());
		}
		return fComponentTypeName;
	}

	/**
	 * @param length the desired length of the new array
	 * @return Creates and returns a new instance of this array class in the
	 *         target VM.
	 */
	@Override
	public ArrayReference newInstance(int length) {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			write(this, outData);
			writeInt(length, "length", outData); //$NON-NLS-1$

			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.AT_NEW_INSTANCE, outBytes);
			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			ArrayReferenceImpl arrayRef = (ArrayReferenceImpl) ObjectReferenceImpl
					.readObjectRefWithTag(this, replyData);
			return arrayRef;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns a List filled with all Location objects that map to the
	 *         given line number.
	 */
	@Override
	public List<Location> locationsOfLine(int line) {
		// If this reference type is an ArrayType, the returned list is always
		// empty.
		return Collections.EMPTY_LIST;
	}

	/**
	 * @param target the target
	 * @param in the stream
	 * @return Reads JDWP representation and returns new instance.
	 * @throws IOException if the reading fails
	 */
	public static ArrayTypeImpl read(MirrorImpl target, DataInputStream in)
			throws IOException {
		VirtualMachineImpl vmImpl = target.virtualMachineImpl();
		JdwpArrayID ID = new JdwpArrayID(vmImpl);
		ID.read(in);
		if (target.fVerboseWriter != null)
			target.fVerboseWriter.println("arrayType", ID.value()); //$NON-NLS-1$

		if (ID.isNull())
			return null;

		ArrayTypeImpl mirror = (ArrayTypeImpl) vmImpl.getCachedMirror(ID);
		if (mirror == null) {
			mirror = new ArrayTypeImpl(vmImpl, ID);
			vmImpl.addCachedMirror(mirror);
		}
		return mirror;
	}

	/**
	 * @return Returns modifier bits.
	 */
	@Override
	public int modifiers() {
		return MODIFIER_ACC_PUBLIC | MODIFIER_ACC_FINAL;
	}

	/**
	 * @return Returns a list containing each Field declared in this type.
	 */
	@Override
	public List<Field> fields() {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @return Returns a list containing each Method declared in this type.
	 */
	@Override
	public List<Method> methods() {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @return a Map of the requested static Field objects with their Value.
	 */
	@Override
	public Map<Field, Value> getValues(List<? extends Field> fields) {
		if (fields.isEmpty()) {
			return new HashMap<>();
		}

		throw new IllegalArgumentException(
				JDIMessages.ArrayTypeImpl_getValues_not_allowed_on_array_1);
	}

	/**
	 * @return Returns a List containing each ReferenceType declared within this
	 *         type.
	 */
	@Override
	public List<ReferenceType> nestedTypes() {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @return Returns status of class/interface.
	 */
	@Override
	protected int status() {
		return ReferenceTypeImpl.JDWP_CLASS_STATUS_INITIALIZED
				| ReferenceTypeImpl.JDWP_CLASS_STATUS_PREPARED
				| ReferenceTypeImpl.JDWP_CLASS_STATUS_VERIFIED;
	}

	/**
	 * @param target the target
	 * @param withGenericSignature if the generic signature should be read
	 * @param in the stream
	 * @return Reads JDWP representation and returns new instance.
	 * @throws IOException if the read fails
	 */
	public static ArrayTypeImpl readWithSignature(MirrorImpl target,
			boolean withGenericSignature, DataInputStream in)
			throws IOException {
		VirtualMachineImpl vmImpl = target.virtualMachineImpl();
		JdwpArrayID ID = new JdwpArrayID(vmImpl);
		ID.read(in);
		if (target.fVerboseWriter != null)
			target.fVerboseWriter.println("arrayType", ID.value()); //$NON-NLS-1$

		String signature = target.readString("signature", in); //$NON-NLS-1$
		String genericSignature = null;
		if (withGenericSignature) {
			genericSignature = target.readString("generic signature", in); //$NON-NLS-1$
		}
		if (ID.isNull())
			return null;

		ArrayTypeImpl mirror = (ArrayTypeImpl) vmImpl.getCachedMirror(ID);
		if (mirror == null) {
			mirror = new ArrayTypeImpl(vmImpl, ID);
			vmImpl.addCachedMirror(mirror);
		}
		mirror.setSignature(signature);
		mirror.setGenericSignature(genericSignature);
		return mirror;
	}

	/**
	 * @see com.sun.jdi.ReferenceType#allLineLocations()
	 */
	@Override
	public List<Location> allLineLocations() {
		// If this reference type is an ArrayType, the returned list is always
		// empty.
		return Collections.EMPTY_LIST;
	}

	/**
	 * @see com.sun.jdi.ReferenceType#allMethods()
	 */
	@Override
	public List<Method> allMethods() {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @see com.sun.jdi.ReferenceType#allFields()
	 */
	@Override
	public List<Field> allFields() {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @return Returns an identifying name for the source corresponding to the
	 *         declaration of this type.
	 */
	@Override
	public String sourceName() throws AbsentInformationException {
		throw new AbsentInformationException(
				JDIMessages.ArrayTypeImpl_No_source_name_for_Arrays_1);
	}

	/**
	 * @see com.sun.jdi.ReferenceType#visibleFields()
	 */
	@Override
	public List<Field> visibleFields() {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @see com.sun.jdi.ReferenceType#visibleMethods()
	 */
	@Override
	public List<Method> visibleMethods() {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @see com.sun.jdi.ReferenceType#fieldByName(String)
	 */
	@Override
	public Field fieldByName(String arg1) {
		return null;
	}

	/**
	 * @see com.sun.jdi.ReferenceType#methodsByName(String)
	 */
	@Override
	public List<Method> methodsByName(String arg1) {
		return Collections.EMPTY_LIST;
	}

	/**
	 * @see com.sun.jdi.ReferenceType#methodsByName(String, String)
	 */
	@Override
	public List<Method> methodsByName(String arg1, String arg2) {
		return Collections.EMPTY_LIST;
	}
}
