package ec.gp.semantic.library;

import ec.EvolutionState;
import ec.gp.semantic.DesiredSemanticsBase;
import ec.gp.semantic.ISemanticProblem;
import library.distance.IDistanceTo;
import library.semantics.BitSet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NavigableSet;

public final class BooleanDistanceToSet implements IDistanceTo<BitSet> {

	private static final ForbiddenDescriptor[] EMPTY_ARRAY = new ForbiddenDescriptor[0];
	private static final CalculateBooleanFast calculateFast = new CalculateBooleanFast();
	private static final CalculateBooleanExactly calculateExactly = new CalculateBooleanExactly();
	private static final MatcherBooleanFast matchFast = new MatcherBooleanFast();
	private static final MatcherBooleanExactly matchExactly = new MatcherBooleanExactly();

	private final NavigableSet[] buckets;
	private final BitSet desiredSemantics;
	private final ICalculateBoolean calculator;

	private final ForbiddenDescriptor[] forbidden;

	public BooleanDistanceToSet(EvolutionState state, DesiredSemanticsBase<Boolean> desired, DesiredSemanticsBase<Boolean>... forbidden) {
		ISemanticProblem<Boolean> problem = (ISemanticProblem<Boolean>) state.evaluator.p_problem;
		final int targetSize = problem.getTargetSemantics().size();
		boolean isSimpleSemantics = targetSize == desired.size();

		// DesiredSemanticsBase dynamically expands itself, so it checks its underlying
		// structures for existence each time, we call getValuesFor. Because of that
		// we decided to store (cache) desired values for each fitness case.

		this.buckets = desired.getValues();
		for (int i = 0; i < this.buckets.length && isSimpleSemantics; ++i) {
			isSimpleSemantics = /*isSimpleSemantics && */this.buckets[i].size() == 1 && this.buckets[i].first() != null;
		}

		if (isSimpleSemantics) {
			this.calculator = BooleanDistanceToSet.calculateFast;
			this.desiredSemantics = new BitSet(this.buckets.length);
			for (int i = 0; i < this.buckets.length; ++i) {
				assert this.buckets[i].size() == 1 && this.buckets[i].first() != null;
				this.desiredSemantics.set(i, (boolean) this.buckets[i].first());
			}
		} else {
			this.calculator = BooleanDistanceToSet.calculateExactly;
			this.desiredSemantics = null;
		}

		List<ForbiddenDescriptor> forbiddenList = new ArrayList<ForbiddenDescriptor>(forbidden.length);
		forbiddenLoop:
		for (int f = 0; f < forbidden.length; ++f) {
			boolean isForbiddenSimple = targetSize == forbidden[f].size();

			ForbiddenDescriptor descriptor = new ForbiddenDescriptor();
			descriptor.buckets = forbidden[f].getValues();

			for (int i = 0; i < forbidden[f].size() && isForbiddenSimple; ++i) {
				if (descriptor.buckets[i].size() == 1 && descriptor.buckets[i].first() == null) {
					// inconsistent bucket, skip this forbidden semantics, it cannot be matched
					continue forbiddenLoop;
				}

				// If last part of condition is false (which may change isForbiddenSimple to false), the loop will be
				// broken in the next if. Otherwise it would not change value of isForbiddenSimple. Hence, we can skip
				// this test.
				isForbiddenSimple = /*isForbiddenSimple &&*/ descriptor.buckets[i].size() == 1;// && descriptor.buckets[i].first() != null;
			}

			if (isForbiddenSimple) {
				descriptor.matcher = BooleanDistanceToSet.matchFast;
				descriptor.semantics = new BitSet(descriptor.buckets.length);
				for (int i = 0; i < descriptor.buckets.length; ++i) {
					assert descriptor.buckets[i].size() == 1 && descriptor.buckets[i].first() != null;
					descriptor.semantics.set(i, (boolean) descriptor.buckets[i].first());
				}
			} else {
				descriptor.matcher = BooleanDistanceToSet.matchExactly;
				descriptor.semantics = null;
			}

			forbiddenList.add(descriptor);
		}

		this.forbidden = forbiddenList.toArray(EMPTY_ARRAY);
	}

	@Override
	public double getDistanceTo(final BitSet semantics) {
		return this.getDistanceTo(semantics, Double.POSITIVE_INFINITY);
	}

	@Override
	public double getDistanceTo(final BitSet semantics, double upperBound) {
		for (int f = 0; f < this.forbidden.length; ++f) {
			ForbiddenDescriptor descriptor = this.forbidden[f];
			if (descriptor.matcher.match(descriptor.buckets, descriptor.semantics, semantics)) {
				return Double.POSITIVE_INFINITY;
			}
		}
		return this.calculator.calculate(this.buckets, this.desiredSemantics, semantics, upperBound);
	}

	@Override
	public int hashCode() {
		return Arrays.hashCode(this.buckets) ^ Arrays.hashCode(this.forbidden);
	}

	@Override
	public boolean equals(Object obj) {
		assert obj == null || obj instanceof BooleanDistanceToSet;
		return obj != null && Arrays.deepEquals(this.buckets, ((BooleanDistanceToSet) obj).buckets)
				&& Arrays.deepEquals(this.forbidden, ((BooleanDistanceToSet) obj).forbidden);
	}

	class ForbiddenDescriptor {
		public NavigableSet[] buckets;
		public BitSet semantics;
		public IMatcherBoolean matcher;

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			ForbiddenDescriptor that = (ForbiddenDescriptor) o;
			return Arrays.equals(buckets, that.buckets);
		}

		@Override
		public int hashCode() {
			return Arrays.hashCode(this.buckets);
		}
	}
}

interface ICalculateBoolean {
	double calculate(final NavigableSet[] buckets, final BitSet desiredSemantics, final BitSet semantics,
					 final double upperBound);
}

interface IMatcherBoolean {
	boolean match(final NavigableSet[] forbiddenBuckets, final BitSet forbiddenSemantics, final BitSet semantics);
}

/**
 * The class implements standard Hamming distance. Use only if desired semantics has exactly one value for each fitness
 * case.
 */
final class CalculateBooleanFast implements ICalculateBoolean {

	@Override
	public double calculate(final NavigableSet[] buckets, final BitSet desiredSemantics, final BitSet semantics,
							final double upperBound) {
		assert desiredSemantics.length() == semantics.length();

		int distance = 0;

		BitSet temp = (BitSet) desiredSemantics.clone();
		temp.xor(semantics);
		distance = temp.cardinality();

		/*for (int i = 0; i < desiredSemantics.length(); ++i) {
			if (desiredSemantics.get(i) != semantics.get(i)) {
				distance += 1;

				assert !Double.isInfinite(distance) && !Double.isNaN(distance);
				if (distance > upperBound)
					return Double.NaN;
			}
		}
		
		assert distance == temp.cardinality();*/

		assert distance >= 0;

		return distance;
	}
}

/**
 * This class calculates minimal Hamming distance over all possible combinations of desired values.
 */
final class CalculateBooleanExactly implements ICalculateBoolean {

	@Override
	public double calculate(final NavigableSet[] buckets, final BitSet desiredSemantics, final BitSet semantics,
							final double upperBound) {
		long distance = 0;
		//int inconsistencies = 0;
		NavigableSet<Boolean> desiredValues;
		Boolean v;

		assert semantics.length() == buckets.length;

		for (int fc = 0; fc < buckets.length; ++fc) {
			desiredValues = buckets[fc];

			// desiredValues.size():
			// 0 = don't care
			// 1 = inconsistent or a single value
			// 2 = add 0 and continue the loop, since bool can only have two values, and one of them must be equal to
			// the given semantics

			if (desiredValues.size() == 1) {
				v = desiredValues.first();
				if (v == null) {
					//++inconsistencies;
				} else if (semantics.get(fc) != v.booleanValue()) {
					distance += 1;
				}

				assert !Double.isInfinite(distance /** (1 + inconsistencies)*/) && !Double.isNaN(distance /** (1 + inconsistencies)*/);
				if (distance /** (1 + inconsistencies)*/ > upperBound)
					return Double.NaN;
			}
		}

		// penalize inconsistencies
		//distance = distance * (1 + inconsistencies);

		assert distance >= 0;

		return distance;
	}
}

final class MatcherBooleanFast implements IMatcherBoolean {

	@Override
	public boolean match(final NavigableSet[] forbiddenBuckets, final BitSet forbiddenSemantics, final BitSet semantics) {
		assert forbiddenSemantics.length() == semantics.length();

		return forbiddenSemantics.equals(semantics);
	}
}

final class MatcherBooleanExactly implements IMatcherBoolean {

	@Override
	public boolean match(final NavigableSet[] forbiddenBuckets, final BitSet forbiddenSemantics, final BitSet semantics) {
		assert forbiddenBuckets.length == semantics.length();

		//int matches = 0;

		for (int i = 0; i < forbiddenBuckets.length; ++i) {

			// if bucket contains inconsistency or does not contain value of semantics
			if (!forbiddenBuckets[i].isEmpty() && (forbiddenBuckets[i].first() == null || !forbiddenBuckets[i].contains(semantics.get(i)))) {
				return false;
			}

			// O(1)
			/*if (forbiddenBuckets[i].isEmpty()) {
                // don't care
				++matches;
			} else if (forbiddenBuckets[i].first() == null) { // O(1)
				// inconsistency
				return false;
			} else if (forbiddenBuckets[i].contains(semantics[i])) {
				++matches;
			} else {
				return false;
			}*/
		}

		//assert matches == forbiddenBuckets.length;

		return true;
	}
}
