package com.hammurapi.extract;

import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;


abstract class ArithmeticExtractor<T, V, C> extends ExtractorBase<T, V, C> implements Extractor<T, V, C>, Mappable<T, V, C>, BinaryExtractor<T,V,C> {
	
	protected enum PromotionLevel { 
		INT(0), 
		LONG(1), 
		FLOAT(2), 
		DOUBLE(3);
		
		private PromotionLevel(int level) {
			this.level = level;
		}
		
		int level;
	}
	
	protected Extractor<T, V, C> leftExtractor;
	protected Extractor<T, V, C> rightExtractor;
	private boolean commutative;

	protected ArithmeticExtractor(double initialCost, TimeUnit costUnit, Extractor<T, V, C> leftExtractor, Extractor<T, V, C> rightExtractor, boolean commutative) {
		super(initialCost, costUnit);
		this.leftExtractor = leftExtractor;
		this.rightExtractor = rightExtractor;
		this.commutative = commutative;
	}

	@Override
	public boolean isContextDependent() {
		return leftExtractor.isContextDependent() || rightExtractor.isContextDependent();
	}

	@Override
	public Set<Integer> parameterIndices() {
		Set<Integer> ret = new TreeSet<Integer>();
		ret.addAll(leftExtractor.parameterIndices());
		ret.addAll(rightExtractor.parameterIndices());
		return ret;
	}

	@Override
	public int hashCode() {
		int result = ((leftExtractor == null) ? 0 : leftExtractor.hashCode());
		result += ((rightExtractor == null) ? 0 : rightExtractor.hashCode());
		return result;
	}
	
	@Override
	public ComparisonResult compareTo(Extractor<T, V, C> obj) {
		if (this == obj)
			return ComparisonResult.EQUAL_NM;
		if (obj == null)
			return ComparisonResult.NOT_EQUAL_NM;
		if (getClass() != obj.getClass())
			return ComparisonResult.NOT_EQUAL_NM;
		
		ArithmeticExtractor other = (ArithmeticExtractor) obj;
		if (leftExtractor.equals(other.leftExtractor) && rightExtractor.equals(other.rightExtractor)) {
			return ComparisonResult.EQUAL_NM;
		}
		
		if (commutative && leftExtractor.equals(other.rightExtractor) && rightExtractor.equals(other.leftExtractor)) {
			return ComparisonResult.EQUAL_NM;
		}
		return super.compareTo(obj);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected V extractInternal(
			C context,
			Map<C, Map<Extractor<T, ? super V, C>, ? super V>> cache, 
			T... obj) {
		
		V o1 = leftExtractor.extract(context, cache, obj);
		V o2 = rightExtractor.extract(context, cache, obj);
		
		if (o1 instanceof Number && o2 instanceof Number) {
			Number n1 = (Number) o1;
			Number n2 = (Number) o2;
			switch (promote(n1, n2)) {
			case INT:
				return (V) doOp(n1.intValue(), n2.intValue());
			case LONG:
				return (V) doOp(n1.longValue(), n2.longValue());
			case FLOAT:
				return (V) doOp(n1.floatValue(), n2.floatValue());
			case DOUBLE:
				return (V) doOp(n1.doubleValue(), n2.doubleValue());		
			default:
				throw new IllegalArgumentException("Unexpected promotion level "+promote(n1, n2));
			}
		} else {		
			return doOp(o1, o2);
		}
	}

 	protected V doOp(Object o1, Object o2) {
 		throw new UnsupportedOperationException("Can't do "+getClass().getName()+" on "+o1+" and "+o2);
 	}
 	
 	protected abstract Object doOp(int n1, int n2);
 	protected abstract Object doOp(long n1, long n2);
 	protected abstract Object doOp(float n1, float n2);
 	protected abstract Object doOp(double n1, double n2);
 	
 	private PromotionLevel nLevel(Number n) {
 		if (n instanceof Integer) {
 			return PromotionLevel.INT;
 		}
 		if (n instanceof Long) {
 			return PromotionLevel.LONG;
 		}
 		if (n instanceof Float) {
 			return PromotionLevel.FLOAT;
 		}
 		if (n instanceof Double) {
 			return PromotionLevel.DOUBLE;
 		}
 		throw new IllegalArgumentException(n.getClass()+" is not supported");
 	}
 	
 	protected PromotionLevel promote(Number n1, Number n2) {
 		PromotionLevel pl1 = nLevel(n1);
 		PromotionLevel pl2 = nLevel(n2); 		
 		return pl1.level>=pl2.level ? pl1 : pl2;
 	}
 	
 	@Override
 	public String toString() {
 		return getClass().getName()+"("+leftExtractor+", "+rightExtractor+")";
 	}

 	@Override
 	public double getCost() {
 		if (cost==-1 && costUnit==null) {
 			return leftExtractor.getCost()+rightExtractor.getCost();
 		}
 		return super.getCost();
 	}
 	
	@SuppressWarnings("unchecked")
	@Override
	public Extractor<T, V, C> map(int[] map) {
		Mappable<T, V, C> leftMappable = ExtractorUtil.toMappable(leftExtractor);
		if (leftMappable!=null) {
			Mappable<T, V, C> rightMappable = ExtractorUtil.toMappable(rightExtractor);
			if (rightMappable!=null) {
				Extractor<T,V,C> lMapped = leftMappable.map(map);
				if (lMapped!=null) {
					Extractor<T,V,C> rMapped = rightMappable.map(map);
					if (rMapped!=null) {
						return newInstance(lMapped, rMapped);
					}
				}
			}
		}
		return null;
	}

	protected abstract Extractor<T, V, C> newInstance(Extractor<T, V, C> leftExtractor,	Extractor<T, V, C> rightExtractor);

	public Extractor<T, V, C> getLeftExtractor() {
		return leftExtractor;
	}
	
	public Extractor<T, V, C> getRightExtractor() {
		return rightExtractor;
	}
}
