/**
 * Copyright (c) 2001-2017 Mathew A. Nelson and Robocode contributors
 * 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://robocode.sourceforge.net/license/epl-v10.html
 */
package net.sf.robocode.host.security;


import net.sf.robocode.io.Logger;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;


/**
 * @author Mathew A. Nelson (original)
 * @author Flemming N. Larsen (contributor)
 * @author Robert D. Maupin (contributor)
 */
class ClassAnalyzer {
	private final static byte CONSTANT_Class = 7;
	private final static byte CONSTANT_Fieldref = 9;
	private final static byte CONSTANT_Methodref = 10;
	private final static byte CONSTANT_InterfaceMethodref = 11;
	private final static byte CONSTANT_String = 8;
	private final static byte CONSTANT_Integer = 3;
	private final static byte CONSTANT_Float = 4;
	private final static byte CONSTANT_Long = 5;
	private final static byte CONSTANT_Double = 6;
	private final static byte CONSTANT_NameAndType = 12;
	private final static byte CONSTANT_Utf8 = 1;
	private final static byte CONSTANT_MethodHandle = 15;
	private final static byte CONSTANT_MethodType = 16;
	private final static byte CONSTANT_InvokeDynamic = 18;

	/**
	 * ClassAnalyzer constructor comment.
	 */
	public ClassAnalyzer() {
		super();
	}

	static void getReferencedClasses(ByteBuffer classFile, Set<String> collection) {

		/*
		 http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html

		 4.1 The ClassFile Structure
		 A class file consists of a single ClassFile structure:

		 ClassFile {
		 u4 magic;
		 u2 minor_version;
		 u2 major_version;
		 u2 constant_pool_count;
		 cp_info constant_pool[constant_pool_count-1];
		 u2 access_flags;
		 u2 this_class;
		 u2 super_class;
		 u2 interfaces_count;
		 u2 interfaces[interfaces_count];
		 u2 fields_count;
		 field_info fields[fields_count];
		 u2 methods_count;
		 method_info methods[methods_count];
		 u2 attributes_count;
		 attribute_info attributes[attributes_count];
		 }
		 */
		String strings[];
		List<Integer> classNameIndexes = new ArrayList<Integer>();

		try {
			DataInputStream in = new DataInputStream(new ByteArrayInputStream(classFile.array(), 0, classFile.limit()));
			long magic = in.readInt();

			if (magic != 0xCAFEBABE) {
				Logger.logError("Not a class file!");
				return;
			}
			in.readUnsignedShort(); // minor version
			in.readUnsignedShort(); // major version
			int constant_pool_count = in.readUnsignedShort();

			strings = new String[constant_pool_count];

			/*

			 All constant_pool table entries have the following general format:


			 cp_info {
			 u1 tag;
			 u1 info[];
			 }

			 Constant Type  Value
			 CONSTANT_Class  7
			 CONSTANT_Fieldref  9
			 CONSTANT_Methodref  10
			 CONSTANT_InterfaceMethodref  11
			 CONSTANT_String  8
			 CONSTANT_Integer  3
			 CONSTANT_Float  4
			 CONSTANT_Long  5
			 CONSTANT_Double  6
			 CONSTANT_NameAndType  12
			 CONSTANT_Utf8  1
			 CONSTANT_MethodHandle  15
			 CONSTANT_MethodType  16
			 CONSTANT_InvokeDynamic  18
			 */


			for (int i = 1; i < constant_pool_count; i++) {
				byte tag = in.readByte();

				switch (tag) {

				/*
				 CONSTANT_Class_info {
				 u1 tag;
				 u2 name_index;
				 }
				 */
				case CONSTANT_Class: {
						int name_index = in.readUnsignedShort();

						classNameIndexes.add(name_index);
					}
					break;

				/*
				 CONSTANT_Fieldref_info {
				 u1 tag;
				 u2 class_index;
				 u2 name_and_type_index;
				 }
				 CONSTANT_Methodref_info {
				 u1 tag;
				 u2 class_index;
				 u2 name_and_type_index;
				 }
				 CONSTANT_InterfaceMethodref_info {
				 u1 tag;
				 u2 class_index;
				 u2 name_and_type_index;
				 }
				 CONSTANT_InvokeDynamic_info {
				 u1 tag;
				 u2 bootstrap_method_attr_index;
				 u2 name_and_type_index;
				 }
				 */
				case CONSTANT_Fieldref:
				case CONSTANT_Methodref:
				case CONSTANT_InterfaceMethodref:
				case CONSTANT_InvokeDynamic: {
						in.readUnsignedShort(); // class index
						in.readUnsignedShort(); // name and type index
					}
					break;

				/*
				 CONSTANT_MethodHandle_info {
				 u1 tag;
				 u1 reference_kind;
				 u2 reference_index;
				 }
				 */
				case CONSTANT_MethodHandle: {
						in.readUnsignedByte(); //reference_kind
						in.readUnsignedShort(); // reference_index
					}
					break;
					
				/*
				 CONSTANT_String_info {
				 u1 tag;
				 u2 string_index;
				 }
				 CONSTANT_MethodType_info {
				 u1 tag;
				 u2 descriptor_index;
				 }
				 */
				case CONSTANT_String:
				case CONSTANT_MethodType: {
						in.readUnsignedShort();
					}
					break;

				/*
				 CONSTANT_Integer_info {
				 u1 tag;
				 u4 bytes;
				 }
				 CONSTANT_Float_info {
				 u1 tag;
				 u4 bytes;
				 }
				 */
				case CONSTANT_Integer:
				case CONSTANT_Float: {
						in.readInt(); // bytes
					}
					break;

				/*
				 CONSTANT_Long_info {
				 u1 tag;
				 u4 high_bytes;
				 u4 low_bytes;
				 }
				 CONSTANT_Double_info {
				 u1 tag;
				 u4 high_bytes;
				 u4 low_bytes;
				 }
				 All 8-byte constants take up two entries in the constant_pool table of the class file. If a CONSTANT_Long_info or CONSTANT_Double_info structure is the item in the constant_pool table at index n, then the next usable item in the pool is located at index n+2. The constant_pool index n+1 must be valid but is considered unusable.2
				 */

				case CONSTANT_Long:
				case CONSTANT_Double: {
						in.readInt(); // high bytes
						in.readInt(); // low bytes
						i++; // see "all 8-byte..." comment above.
					}
					break;

				/*
				 CONSTANT_NameAndType_info {
				 u1 tag;
				 u2 name_index;
				 u2 descriptor_index;
				 }
				 */
				case CONSTANT_NameAndType: {
						in.readUnsignedShort(); // name index
						in.readUnsignedShort(); // descriptor index
					}
					break;

				/*
				 CONSTANT_Utf8_info {
				 u1 tag;
				 u2 length;
				 u1 bytes[length];
				 }
				 */
				case CONSTANT_Utf8: {
						String utf8_string = in.readUTF();

						strings[i] = utf8_string;
					}
					break;
				} // switch
			} // for i
		} catch (IOException e) {
			return;
		}

		for (Integer classNameIndex : classNameIndexes) {
			String className = strings[classNameIndex].replace('\\', '.').replace('/', '.');

			if (className.indexOf("[") != 0 && !collection.contains(className)) {
				collection.add(className);
			}
		}
	}
}
