package com.hammurapi.extract;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;


/**
 * Commutative AND.
 * @author Pavel Vlasov.
 *
 * @param <T>
 */
public class CommutativeAnd<T, C> extends ExtractorBase<T, Boolean, C> implements CommutativeCompositePredicate<T, C, CommutativeAnd<T,C>>, Serializable {
	
	private Set<Predicate<T, C>> parts = new HashSet<Predicate<T, C>>();
	
	public CommutativeAnd(double initialCost, TimeUnit costUnit) {
		super(initialCost,costUnit);
	}
	
	public CommutativeAnd(double initialCost, TimeUnit costUnit, Predicate<T,C>... parts) {
		super(initialCost,costUnit);
		for (Predicate<T,C> part: parts) {
			addPart(part);
		}
	}
	
	public CommutativeAnd(double initialCost, TimeUnit costUnit, Iterable<Predicate<T,C>> parts) {
		super(initialCost,costUnit);
		for (Predicate<T,C> part: parts) {
			addPart(part);
		}
	}
	
	private void addPart(Predicate<T, C> part) {
		if (part instanceof CommutativeAnd) {
			for (Predicate<T, C> pp: ((CommutativeAnd<T, C>) part).getParts()) {
				addPart(pp);
			}
		} else {
			parts.add(part);
		}
	}

	public Set<Predicate<T, C>> getParts() {
		return Collections.unmodifiableSet(parts);
	}

	@Override
	@SuppressWarnings("unchecked")
	public ComparisonResult compareTo(Extractor<T, Boolean, C> otherPredicate) {
		if (getParts().isEmpty()) {
			return True.getInstance().compareTo((Extractor<Object, Boolean, Object>) otherPredicate);
		}
		
		if (otherPredicate instanceof CommutativeAnd && parts.equals(((CommutativeAnd) otherPredicate).parts)) {
			return ComparisonResult.EQUAL_NM;
		}
		
		boolean isTrue = true;
		boolean isFalse = false;
		for (Predicate<T, C> part: getParts()) {	
			ComparisonResult pcr = part.compareTo((Predicate<T, C>) False.getInstance());
			if (pcr.getType().equals(ComparisonResult.Type.EQUAL)) {
				isFalse = true;
				isTrue = false;
				break;				
			} else if (!pcr.getType().equals(ComparisonResult.Type.OPPOSITE)) {
				isTrue = false;
			}						
		}
		
		if (isTrue) {
			return otherPredicate.compareTo((Predicate<T, C>) True.getInstance());
		}
		
		if (isFalse) {
			return otherPredicate.compareTo((Predicate<T, C>) False.getInstance());
		}
		
		if (getParts().size()==1) {
			return getParts().iterator().next().compareTo(otherPredicate);
		}
		for (Predicate<T, C> part: getParts()) {	
			ComparisonResult pcr = part.compareTo(otherPredicate);
			if (pcr.isOneToOneMapping()) {
				switch (pcr.getType()) {
				case EQUAL:
				case MORE_RESTRICTIVE:
					return ComparisonResult.MORE_RESTRICTIVE_NM;
//				case OPPOSITE:
//				case OPPOSITE_MORE_RESTRICTIVE:
//					return ComparisonResult.OPPOSITE_MORE_RESTRICTIVE_NM;
				}
			}
		}
		
		return super.compareTo(otherPredicate);
	}

	protected Boolean extractInternal(C context, Map<C, Map<Extractor<T, ? super Boolean, C>, ? super Boolean>> cache, T... obj) {
		for (Predicate<T, C> part: parts) {
			if (!part.extract(context, cache, obj)) {
				return false;
			}
		}
		return true;
	}

	public Set<Integer> parameterIndices() {
		Set<Integer> ret = new TreeSet<Integer>();
		for (Predicate<T, C> part: parts) {
			ret.addAll(part.parameterIndices());
		}
		return ret;
	}


	@SuppressWarnings("unchecked")
	public Predicate<T, C> remove(Predicate<T, C> part) {
		if (!parts.contains(part)) {
			return this;
		}
		try {
			CommutativeAnd<T, C> ret = (CommutativeAnd<T, C>) clone();
			ret.parts.remove(part);
			return ret.parts.size()==1 ? ret.parts.iterator().next() : ret;
		} catch (CloneNotSupportedException e) {
			throw new ExtractorException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public Predicate<T, C> normalize() {
		if (getParts().isEmpty()) {
			return True.getInstance();
		}
		if (getParts().size()==1) {
			Predicate<T, C> ret = getParts().iterator().next();
			if (ret instanceof CompositePredicate) {
				((CompositePredicate) ret).normalize();
			}
			return ret;
		}
		ComparisonResult cr = compareTo((Predicate<T, C>) True.getInstance());
		if (cr.getType().equals(ComparisonResult.Type.EQUAL)) {
			return True.getInstance();
		}
		if (cr.getType().equals(ComparisonResult.Type.OPPOSITE)) {
			return False.getInstance();
		}
		Predicate<T, C>[] pa = getParts().toArray(new Predicate[getParts().size()]);
		Z: for (int i=0; i<pa.length; ++i) {
			for (int j=0; j<pa.length; ++j) {
				if (j!=i && pa[i]!=null && pa[j]!=null) {
					ComparisonResult pcr1 = pa[i].compareTo(pa[j]);
					if (pcr1.isOneToOneMapping()) {
						switch (pcr1.getType()) {
						case EQUAL:
						case LESS_RESTRICTIVE:
							pa[j]=null;
							continue;
						case OPPOSITE:
						case OPPOSITE_LESS_RESTRICTIVE:
							return False.getInstance();
						}
					}
					
					ComparisonResult pcr2 = pa[j].compareTo(pa[i]);
					if (pcr2.isOneToOneMapping()) {
						switch (pcr2.getType()) {
						case EQUAL:
						case LESS_RESTRICTIVE:
							pa[i]=null;
							continue Z;
						case OPPOSITE:
						case OPPOSITE_LESS_RESTRICTIVE:
							return False.getInstance();
						}
					}					
				}
			}
		}
		
		int pc = 0; 
		for (Predicate<T, C> part: pa) {
			if (part!=null) {
				++pc;
			}
		}
		
		if (pc==0) {
			return True.getInstance();
		}
		
		if (pc==1) {
			for (Predicate<T, C> part: pa) {
				if (part!=null) {
					if (part instanceof CompositePredicate) {
						((CompositePredicate) part).normalize();
					}
					return part;
				}
			}
		}
		
		if (pc!=pa.length) {
			CommutativeAnd<T, C> ret = new CommutativeAnd<T, C>(cost, costUnit);
			for (Predicate<T, C> part: pa) {
				if (part!=null) {
					ret.addPart(part);
				}
			}
			return ret;
		}
		
		return this;
	}

	public boolean isContextDependent() {
		for (Predicate<T, C> part: getParts()) {
			if (part.isContextDependent()) {
				return true;
			}
		}
		return false;
	}
	
	@Override
	public String toString() {
		return getClass().getName()+getParts();
	}
	
	@Override
	public double getCost() {
		if (cost==0 && costUnit==null) {
			double ret = 0;
			for (Predicate<T,C> part: parts) {
				ret+=part.getCost();
			}
			return ret;
		}
		return super.getCost();
	}
	

	@Override
	public Extractor<T, Boolean, C> map(int[] map) {
		CommutativeAnd<T,C> ret = new CommutativeAnd<T, C>(initialCost, costUnit);
		for (Predicate<T,C> part: parts) {
			ret.addPart(MappedPredicate.mapPredicate(part, map));
		}
		return ret;
	}
	
	@Override
	public Object clone() throws CloneNotSupportedException {
		@SuppressWarnings("unchecked")
		CommutativeAnd<T,C> ret = (CommutativeAnd<T, C>) super.clone();
		ret.parts = new HashSet<Predicate<T,C>>(parts); 
		return ret;
	}
	
	@Override
	public CommutativeAnd<T, C> add(Predicate<T, C> part) {
		CommutativeAnd<T, C> ret = new CommutativeAnd<T, C>(initialCost, costUnit, parts);
		ret.parts.add(part);
		return ret;
	}
}
