#pragma once
#include "classfile.h"
#include <map>
#include <vector>

namespace java
{
	enum element_value_type : uint8_t
	{
		INVALID = 0,
		STRING = 's',
		ENUM_CONSTANT = 'e',
		CLASS = 'c',
		ANNOTATION = '@',
		ARRAY = '[', // one array dimension
		PRIMITIVE_INT = 'I', // integer
		PRIMITIVE_BYTE = 'B', // signed byte
		PRIMITIVE_CHAR = 'C', // Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16
		PRIMITIVE_DOUBLE = 'D', // double-precision floating-point value
		PRIMITIVE_FLOAT = 'F', // single-precision floating-point value
		PRIMITIVE_LONG = 'J', // long integer
		PRIMITIVE_SHORT = 'S', // signed short
		PRIMITIVE_BOOLEAN = 'Z' // true or false
	};
	/**
	 * The element_value structure is a discriminated union representing the value of an element-value pair.
	 * It is used to represent element values in all attributes that describe annotations
	 * - RuntimeVisibleAnnotations
	 * - RuntimeInvisibleAnnotations
	 * - RuntimeVisibleParameterAnnotations
	 * - RuntimeInvisibleParameterAnnotations).
	 *
	 * The element_value structure has the following format:
	 */
	class element_value
	{
	protected:
		element_value_type type;
		constant_pool & pool;

	public:
		element_value(element_value_type type, constant_pool & pool): type(type), pool(pool) {};

		element_value_type getElementValueType()
		{
			return type;
		}
		
		virtual std::string toString() = 0;

		static element_value * readElementValue(util::membuffer & input, constant_pool & pool);
	};
	
	/**
	 * Each value of the annotations table represents a single runtime-visible annotation on a program element.
	 * The annotation structure has the following format:
	 */
	class annotation
	{
	public:
		typedef std::vector< std::pair<uint16_t, element_value * > > value_list;
	protected:
		/**
		 * The value of the type_index item must be a valid index into the constant_pool table.
		 * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure
		 * representing a field descriptor representing the annotation type corresponding
		 * to the annotation represented by this annotation structure.
		 */
		uint16_t type_index;
		/**
		 * map between element_name_index and value.
		 * 
		 * The value of the element_name_index item must be a valid index into the constant_pool table.
		 * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure representing
		 * a valid field descriptor (§4.3.2) that denotes the name of the annotation type element represented
		 * by this element_value_pairs entry.
		 */
		value_list name_val_pairs;
		/**
		 * Reference to the parent constant pool
		 */
		constant_pool & pool;
	public:
		annotation(uint16_t type_index, constant_pool& pool):type_index(type_index), pool(pool) {};
		~annotation()
		{
			for(unsigned i = 0 ; i < name_val_pairs.size(); i++)
			{
				delete name_val_pairs[i].second;
			}
		}
		void add_pair(uint16_t key, element_value * value)
		{
			name_val_pairs.push_back(std::make_pair(key, value));
		};
		value_list::const_iterator begin()
		{
			return name_val_pairs.cbegin();
		}
		value_list::const_iterator end()
		{
			return name_val_pairs.cend();
		}
		std::string toString();
		static annotation * read(util::membuffer & input, constant_pool & pool);
	};
	typedef std::vector<annotation *> annotation_table;
	
	
	/// type for simple value annotation elements
	class element_value_simple : public element_value
	{
	protected:
		/// index of the constant in the constant pool
		uint16_t index;
	public:
		element_value_simple(element_value_type type, uint16_t index , constant_pool& pool):
		element_value(type, pool), index(index)
		{
			// TODO: verify consistency
		};
		uint16_t getIndex()
		{
			return index;
		}
		virtual std::string toString()
		{
			return pool[index].toString();
		};
	};
	/// The enum_const_value item is used if the tag item is 'e'.
	class element_value_enum : public element_value
	{
	protected:
		/**
		 * The value of the type_name_index item must be a valid index into the constant_pool table.
		 * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure
		 * representing a valid field descriptor (§4.3.2) that denotes the internal form of the binary
		 * name (§4.2.1) of the type of the enum constant represented by this element_value structure.
		 */
		uint16_t typeIndex;
		/**
		 * The value of the const_name_index item must be a valid index into the constant_pool table.
		 * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure
		 * representing the simple name of the enum constant represented by this element_value structure.
		 */
		uint16_t valueIndex;
	public:
		element_value_enum(element_value_type type, uint16_t typeIndex, uint16_t valueIndex, constant_pool& pool):
		element_value(type, pool), typeIndex(typeIndex), valueIndex(valueIndex)
		{
			// TODO: verify consistency
		}
		uint16_t getValueIndex()
		{
			return valueIndex;
		}
		uint16_t getTypeIndex()
		{
			return typeIndex;
		}
		virtual std::string toString()
		{
			return "enum value";
		};
	};
	
	class element_value_class : public element_value
	{
	protected:
		/**
		 * The class_info_index item must be a valid index into the constant_pool table.
		 * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure
		 * representing the return descriptor (§4.3.3) of the type that is reified by the class
		 * represented by this element_value structure.
		 * 
		 * For example, 'V' for Void.class, 'Ljava/lang/Object;' for Object, etc.
		 * 
		 * Or in plain english, you can store type information in annotations. Yay.
		 */
		uint16_t classIndex;
	public:
		element_value_class(element_value_type type, uint16_t classIndex, constant_pool& pool):
		element_value(type, pool), classIndex(classIndex)
		{
			// TODO: verify consistency
		}
		uint16_t getIndex()
		{
			return classIndex;
		}
		virtual std::string toString()
		{
			return "class";
		};
	};
	
	/// nested annotations... yay
	class element_value_annotation : public element_value
	{
	private:
		annotation * nestedAnnotation;
	public:
		element_value_annotation(element_value_type type, annotation * nestedAnnotation, constant_pool& pool):
		element_value(type, pool), nestedAnnotation(nestedAnnotation)
		{};
		~element_value_annotation()
		{
			if(nestedAnnotation)
			{
				delete nestedAnnotation;
				nestedAnnotation = nullptr;
			}
		}
		virtual std::string toString()
		{
			return "nested annotation";
		};
	};
	
	/// and arrays!
	class element_value_array : public element_value
	{
	public:
		typedef std::vector <element_value *> elem_vec;
	protected:
		elem_vec values;
	public:
		element_value_array ( element_value_type type, std::vector <element_value *>& values, constant_pool& pool ):
		element_value(type, pool), values(values)
		{};
		~element_value_array ()
		{
			for(unsigned i = 0; i < values.size();i++)
			{
				delete values[i];
			}
		};
		elem_vec::const_iterator begin()
		{
			return values.cbegin();
		}
		elem_vec::const_iterator end()
		{
			return values.cend();
		}
		virtual std::string toString()
		{
			return "array";
		};
	};
}