package com.hammurapi.extract;

import java.util.Arrays;
import java.util.Set;

/**
 * Result of comparison of two extractors/predicates.
 * @author Pavel Vlasov
 *
 */
public class ComparisonResult {
	
	// Not mapping constants
	public static final ComparisonResult EQUAL_NM =  new ComparisonResult(Type.EQUAL, null);
	public static final ComparisonResult UNEQUAL_NM =  new ComparisonResult(Type.UNEQUAL, null);
	public static final ComparisonResult LESS_RESTRICTIVE_NM =  new ComparisonResult(Type.LESS_RESTRICTIVE, null);
	public static final ComparisonResult MORE_RESTRICTIVE_NM =  new ComparisonResult(Type.MORE_RESTRICTIVE, null);
	public static final ComparisonResult NOT_EQUAL_NM =  new ComparisonResult(Type.NOT_EQUAL, null);
	public static final ComparisonResult OPPOSITE_NM =  new ComparisonResult(Type.OPPOSITE, null);
	public static final ComparisonResult OPPOSITE_LESS_RESTRICTIVE_NM =  new ComparisonResult(Type.OPPOSITE_LESS_RESTRICTIVE, null);
	public static final ComparisonResult OPPOSITE_MORE_RESTRICTIVE_NM =  new ComparisonResult(Type.OPPOSITE_MORE_RESTRICTIVE, null);
	
	/**
	 * Result of extractor comparison.
	 * @author Pavel Vlasov.
	 *
	 */
	public enum Type {
		/**
		 * Extractors are equal.
		 */
		EQUAL,
		/**
		 * Extractors are not equal, i.e. no conclusion can be drawn about values
		 * returned by extractors.
		 */
		NOT_EQUAL,
		
		// Enums below are applicable only to predicates.
		/**
		 * This predicate is less restrictive than the parameter
		 * predicate. E.g. x&lt;=5 is less restrictive than x&lt;5. 
		 */
		LESS_RESTRICTIVE,
		/**
		 * This predicate is more restrictive than the parameter
		 * predicate. E.g. x&lt;6 is more restrictive than x&lt;7. 
		 */
		MORE_RESTRICTIVE,
		/**
		 * When this predicate evaluates to true, the parameter
		 * predicate evaluates to false and vice-versa.
		 */
		OPPOSITE,
		/**
		 * The opposite of this predicate is less restrictive than 
		 * the parameter predicate. 
		 * E.g. x&gt;5 opposite is x&lt;=5 and it is is less restrictive than x&lt;5.
		 */		
		OPPOSITE_LESS_RESTRICTIVE,
		/**
		 * The opposite of this predicate is more restrictive than 
		 * the parameter predicate. 
		 * E.g. x&gt;5 opposite is x&lt;=5 and it is is more restrictive than x&lt;7. 
		 */		
		OPPOSITE_MORE_RESTRICTIVE,
		
		// Enums applicable to extractors and constants.
		
		/**
		 * Value extracted by the first extractor is less than value extracted by the second extractor
		 * for any combination of parameters.
		 */
		LESS_THAN,
		
		/**
		 * Value extracted by the first extractor is less than or equal to the value extracted by the second extractor
		 * for any combination of parameters.
		 */
		LESS_EQUAL,
		
		/**
		 * Value extracted by the first extractor is more than or equal to the value extracted by the second extractor
		 * for any combination of parameters.
		 */
		MORE_EQUAL,
		
		/**
		 * Value extracted by the first extractor is more than value extracted by the second extractor
		 * for any combination of parameters.
		 */
		MORE_THAN,
						
		/**
		 * Value extracted by the first extractor is not equal to the value extracted by the second extractor
		 * for any combination of parameters. Do not mistake this with NOT_EQUAL. For boolean values (predicates) UNEQUAL is
		 * equivalent to OPPOSITE.
		 */
		UNEQUAL
	}

	private ComparisonResult.Type type;
	private int[] indexMap;
	
	public ComparisonResult(ComparisonResult.Type type, int[] indexMap) {
		this.type = type;
		this.indexMap = indexMap;
	}
	
	/**
	 * @return mapping of indices of parameter extractor to this extractor. E.g. extractors
	 * <code>arg[0] &lt; arg[1]</code> and <code>arg[1] &gt; arg[0]</code> are equal with index mapping {1, 0}.
	 * <code>null</code> is returned for no mapping (if extractors are <code>NOT_EQUAL</code>) and for 1 to 1 mapping. 
	 * In other words, if cr = e1.compareTo(e2), 
	 * then new MappedExtractor(e1, cr.getIndexMap()).compareTo(e2).isOneToOneMapping()==true or
	 * return value of new MappedExtractor(e1, cr.getIndexMap()).extract() is the same as of e2.extract() 
	 */
	public int[] getIndexMap() {
		return indexMap;
	}
	
	public Type getType() {
		return type;
	}
	
	public boolean isOneToOneMapping() {
		return isOneToOneMapping(indexMap);
	}
	
	public static boolean isOneToOneMapping(int[] map) {
		if (map==null) {
			return true;
		}
		for (int i=0; i<map.length; ++i) {
			if (map[i]!=i && map[i]!=-1) {
				return false;
			}
		}
		return true;				
	}
	
	public boolean compareIndexMaps(int[] otherIndexMap) {
		if (indexMap==null) { // Assumes 1-1 mapping
			if (otherIndexMap==null) {
				return true;
			}
			
			for (int i=0; i<otherIndexMap.length; ++i) {
				if (otherIndexMap[i]!=i) {
					return false;
				}
			}
			return true;
		}
		
		if (otherIndexMap==null) {
			for (int i=0; i<indexMap.length; ++i) {
				if (indexMap[i]!=i) {
					return false;
				}
			}
			return true;				
		}
		
		return Arrays.equals(indexMap, otherIndexMap);
	}

	@Override
	public String toString() {
		return "ComparisonResult [type=" + type + ", indexMap="
				+ Arrays.toString(indexMap) + "]";
	}

	/**
	 * Inverses index map.
	 * @param indexMap2
	 * @return
	 */
	public static int[] inverse(int[] map, Set<Integer> parameterIndices) {
		if (map==null) {
			return null;
		}
		
		int dim = -1;
		for (int i=0; i<map.length; ++i) {
			if (parameterIndices.contains(i)) {
				if (map[i]>dim) {
					dim = map[i];
				}
			}
		}
		int[] ret = new int[dim+1];
		
		for (int i=0; i<map.length; ++i) {
			if (parameterIndices.contains(i)) {
				ret[map[i]] = i;
			}
		}
		return ret;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(indexMap);
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ComparisonResult other = (ComparisonResult) obj;
		if (!(isOneToOneMapping() && other.isOneToOneMapping())) {
			if (!Arrays.equals(indexMap, other.indexMap))
				return false;
		}
		
		if (type != other.type)
			return false;
		return true;
	}
	
}