package org.wovagen;

import java.util.*;

import javax.imageio.ImageIO;

import org.wovagen.gen.AttGen;

import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOProperty;


/**
 * A <tt>Hint</tt> is used in the value generation process to influence (tweak)
 * how the values get generated. In short, <tt>Hint</tt>s are used to override
 * value generation defaults. They can influence entity based aspects of value
 * generation (the number of EOs created for an entity for example), property
 * aspects (how exactly a value gets generated for a property), and actual value
 * generation (randomizing etc.).
 * <p>
 * A <tt>Hint</tt> consists of a <tt>Type</tt> and a value. A {@link Type}
 * determines which aspect of value generation the <tt>Hint</tt> influences.
 * Note that same types might be interpreted differently depending on the
 * context in which they are used. A typical example are the <tt>MIN</tt> and
 * <tt>MAX</tt> types. The value of a hint is interpreted relative to it's
 * <tt>Type</tt> and defines how the hint influences value generation.
 * <p>
 * It is important to note that <tt>Set</tt>s of <tt>Hint</tt>s are obtainable
 * from this class using the {@link #getHints(String, String)} method. They are
 * obtained from the central repository of <tt>Hint</tt>s, to which
 * <tt>Hint</tt>s are added automatically when they are constructed. The only
 * thing that needs to be done is to create <tt>Hint</tt>s, and then use the
 * mentioned method to obtain the appropriate ones at any given moment.
 * <p>
 * <b>Overriding:</b> There are two ways one <tt>Hint</tt> can override another.
 * The first way is if one is more specialized then the other. For example, a
 * <tt>Hint</tt> set for the 'Person' entity and no property key will influence
 * value generation for all properties of that entity. If however a
 * <tt>Hint</tt> is created for the 'Person' entity and the 'name' key, then
 * that <tt>Hint</tt> will be used for that property, and the less specialized
 * one for all others. Another aspect of overriding <tt>Hint</tt>s comes from
 * the IDs of their <tt>Type</tt>s. Since a <tt>Type</tt> is equal to another if
 * their IDs are equal, and a <tt>Hint</tt> is equal to another if their types
 * are equal, then two <tt>Hint</tt>s whose types have the same IDs can not be
 * contained in the same <tt>Set</tt>. This ensures that <tt>Hint</tt>s that
 * influence the same aspect of value generation are never present in the same
 * <tt>Set</tt>. Again the more specialized <tt>Hint</tt>s take precedence. When
 * they are of same rank, the one that was created latest takes precedence.
 * 
 * @version 1.0 Sep 2, 2008
 * @author Florijan Stamenkovic (flor385@mac.com)
 */
public class Hint<T>{
	
	/**
	 * Entity level hint, determines how many EOs should be generated for
	 * any particular entity.
	 */
	public static final Type<Integer> COUNT =	new Type<Integer>(0);
	
	/**
	 * Determines how frequently a value will be set for a property
	 * that allows null.
	 */
	public static final Type<Float> SPARSITY =	new Type<Float>(1);
	
	/**
	 * Overrides randomized value generation and instead uses the provided
	 * <tt>List</tt> to randomly choose a value from.
	 */
	public static final Type<List<?>> OVRD =	new Type<List<?>>(2);
	
	/**
	 * Similar to {@link #OVRD}, but the values are not randomly chosen
	 * from the <tt>List</tt>, but in sequence.
	 */
	public static final Type<List<?>> SEQ =	new Type<List<?>>(2);
	
	/**
	 * Instead of using the default value generator for the class of the
	 * attribute, a custom provided <tt>AttGen</tt> is used.
	 */
	public static final Type<AttGen> CUSTOM =	new Type<AttGen>(2);
	
	/**
	 * An attribute generator for the given <tt>Class</tt> is used, and
	 * then the value is converted to the type the attribute expects it in.
	 */
	public static final Type<Class<?>> CONVERT =	new Type<Class<?>>(2);
	
	/**
	 * The low boundary for randomly generated values. Different attribute type
	 * generators will expect different value types for hints of this type:
	 * <ul>
	 * <li><tt>String</tt> attributes expect an <tt>Integer</tt> denoting the
	 * minimum <tt>String</tt> length.</li>
	 * <li>Numeric attributes expect a <tt>Number</tt> of the same type the
	 * attribute is in, denoting the minimum allowed value for the attribute.</li>
	 * <li><tt>NSData</tt> attributes expect an <tt>Integer</tt> denoting the
	 * minimum number of bytes used.</li>
	 * <li><tt>NSData</tt> attributes that are images expect a <tt>Dimension</tt>
	 * denoting the minimum image size in pixels.</li>
	 * </ul>
	 */
	public static final Type<Object> MIN =	new Type<Object>(3);
	
	/**
	 * The high boundary for randomly generated values. Different attribute type
	 * generators will expect different value types for hints of this type:
	 * <ul>
	 * <li><tt>String</tt> attributes expect an <tt>Integer</tt> denoting the
	 * maximum <tt>String</tt> length.</li>
	 * <li>Numeric attributes expect a <tt>Number</tt> of the same type the
	 * attribute is in, denoting the maximum allowed value for the attribute.</li>
	 * <li><tt>NSData</tt> attributes expect an <tt>Integer</tt> denoting the
	 * maximum number of bytes used.</li>
	 * <li><tt>NSData</tt> attributes that are images expect a <tt>Dimension</tt>
	 * denoting the maximum image size in pixels.</li>
	 * </ul>
	 */
	public static final Type<Object> MAX =	new Type<Object>(4);
	
	/**
	 * Utilized only by <tt>NSData</tt> attributes, defines the attribute to be
	 * the data of an image, the value of the hint of this type should a
	 * <tt>String</tt> that defines the image format name, as expected by
	 * {@link ImageIO#getImageWritersByFormatName(String)}.
	 */
	public static final Type<String> IMAGE =	new Type<String>(5);
	
	/*
	 * The key used in the hint storage for a set of hints applicable to all
	 * entities and properties. The hints stored at this key will be returned by
	 * the getHints(...) method when more specific hints are not provided.
	 */
	private static final String GLOBAL_HINT_KEY = "__globalHintKey__";
	
	//	hint storage
	private static final Map<String, Set<Hint<?>>> hints = 
		new HashMap<String, Set<Hint<?>>>();
	
	{
		new Hint<Integer>(null, null, COUNT, 1000);
		new Hint<Float>(null, null, SPARSITY, 0.6f);
	}
	
	/**
	 * Gets a set of <tt>Hint</tt>s for the given entity name and property key.
	 * This method does all the necessary work of obtaining all global hints,
	 * and overriding them with the more specialized ones. For more information
	 * see class documentation.
	 * 
	 * @param entity The name of the entity.
	 * @param key The key of the property.
	 * @return See above.
	 */
	public static Set<Hint<?>> getHints(String entity, String key){
		Set<Hint<?>> rVal = new HashSet<Hint<?>>();
		
		rVal.addAll(hints.get(GLOBAL_HINT_KEY));
		if(entity != null)
			rVal.addAll(hints.get(entity));
		rVal.addAll(hints.get(toKey(entity, key)));
		
		return rVal;
	}
	
	/**
	 * Get's the hint set for the given entity and property, and if there is
	 * a <tt>Hint</tt> in it for the given type, it extracts it's value and
	 * returns it. Otherwise <tt>null</tt> is returned.
	 * 
	 * @param entity	The entity for which the hint value is sought.
	 * @param prop	The property for which the hint value is sought.
	 * @param type	The type of hint for which the hint value is sought.
	 * @return	See above.
	 */
	public static <T> T getValue(EOEntity entity, EOProperty prop, Type<T> type){
		return getValue(entity == null ? null : entity.name(), 
				prop == null ? null : prop.name(), type);
	}
	
	/**
	 * Get's the hint set for the given entity and property, and if there is
	 * a <tt>Hint</tt> in it for the given type, it extracts it's value and
	 * returns it. Otherwise <tt>null</tt> is returned.
	 * 
	 * @param entity	The entity for which the hint value is sought.
	 * @param prop	The property for which the hint value is sought.
	 * @param type	The type of hint for which the hint value is sought.
	 * @return	See above.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getValue(String entity, String key, Type<T> type){
		
		Set<Hint<?>> hints = Hint.getHints(entity, key);
		for(Hint<?> h : hints)
			if(h.getType() == type)
				return (T)h.getValue();
		
		return null;
	}
	
	
	
	private Type<T> type;
	private T value;
	
	/**
	 * Creates a <tt>Hint</tt> with the given params, and adds it to the hint
	 * storage. The created <tt>Hint</tt> will be returned by the
	 * {@link #getHints(String, String)} method for the appropriate arguments.
	 * 
	 * @param entity The entity for which this <tt>Hint</tt> is applicable. If
	 *            <tt>null</tt> the <tt>Hint</tt> will be applicable to all
	 *            entities and all properties.
	 * @param key The property key for which this <tt>Hint</tt> is applicable.
	 *            If <tt>null</tt> the <tt>Hint</tt> will be applicable to all
	 *            the properties of <tt>entity</tt>.
	 * @param type The type of hint, determines which aspect of value generation
	 *            it will influence.
	 * @param value The value which determines how the hint will influence value
	 *            generation, interpreted relative to the <tt>type</tt>.
	 */
	public Hint(String entity, String key, Type<T> type, T value){
		
		this.type = type;
		this.value = value;
		
		//	late init of Hint Sets for the given entity.key or just entity
		Set<Hint<?>> setOfHints = hints.get(toKey(entity, key));
		if(setOfHints == null){
			setOfHints = new HashSet<Hint<?>>();
			hints.put(toKey(entity, key), setOfHints);
		}
		
		//	add the hint to the set
		setOfHints.add(this);
	}
	
	/**
	 * A <tt>Hint</tt> is equal to another one if their <tt>Type</tt>s are
	 * equal.
	 * 
	 * @param o The object to compare to.
	 * @return See above.
	 */
	public boolean equals(Object o){
		return o instanceof Hint && type.equals(((Hint<?>)o).type);
	}
	
	/**
	 * Overridden to ensure consistency with {@link #equals(Object)}, returns
	 * the hash code of the <tt>Hint</tt>'s <tt>Type</tt>.
	 * 
	 * @return See above.
	 */
	public int hashCode(){
		return type.hashCode();
	}
	
	/**
	 * Returns the <tt>Type</tt> of this <tt>Hint</tt>, which determines which
	 * aspect of value generation the <tt>Hint</tt> will influence.
	 * 
	 * @return	See above.
	 */
	public Type<T> getType(){ return type; }
	
	/**
	 * Returns the value which determines how the hint will influence value
	 * generation, interpreted relative to the <tt>type</tt>.
	 * 
	 * @return See above.
	 */
	public T getValue(){ return value; }
	
	/*
	 * Produces the right key to be used with hint storage, based on the
	 * given entity name and property key.
	 */
	private static String toKey(String entity, String key){
		if(entity == null){
			return GLOBAL_HINT_KEY;
		}else if(key == null) return entity;
		
		return entity+"."+key;
	}
	
	/**
	 * A <tt>Type</tt> is used to determine which aspect of value generation a
	 * <tt>Hint</tt> will influence. For more info on how, see {@link Hint}
	 * documentation.
	 * 
	 * @version 1.0 Sep 2, 2008
	 * @author Florijan Stamenkovic (flor385@mac.com)
	 */
	public static class Type<T>{
		
		private int id;
		
		private Type(int id){
			this.id = id;
		}
		
		public int hashCode(){ return id; }
		
		public boolean equals(Object o){
			return o instanceof Type && id == ((Type<?>)o).id;
		}
	}
}
