package ec.gp.semantic;

import java.util.Arrays;
import java.util.Iterator;

public final class DoubleSemantics extends SemanticsBase<double[]> {

	private final int hashCode;

	public DoubleSemantics(double... values) {
		super(values);
		this.hashCode = DoubleSemantics.calculateHashCode(values);
	}

	@Override
	@SuppressWarnings("unchecked")
	public boolean equals(Object obj) {
		SemanticsBase<double[]> other;
		try {
			other = (SemanticsBase<double[]>) obj;
		} catch (ClassCastException e) {
			return false;
		}

		boolean result = this.value.length == other.value.length; //Arrays.equals(this.value, other.value);

		if(result) {
			for (int i = 0; i < this.value.length; ++i) {
				// first comparison considers NaNs and Infinities equal, but -0 and +0 unequal
				// second comparison considers -0 and +0 equal, but NaNs and Infinities unequal
				if (Double.doubleToLongBits(this.value[i]) != Double.doubleToLongBits(other.value[i]) && this.value[i] != other.value[i]) {
					result = false;
					break;
				}
			}
		}

		assert !result || this.hashCode() == other.hashCode();
		return result;
	}

	protected static int calculateHashCode(final double[] array) {
		int result = 1;
		for (double element : array) {
			// mask at the end of statement removes sign bit
			// we make sure by this that positive and negative zeros are equally represented
			long bits = Double.doubleToLongBits(element) & 0x7fffffffffffffffL;
			result = 31 * result + (int)(bits ^ (bits >>> 32));
		}
		return result;
	}

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

	@Override
	public SemanticsBase<double[]> clone() {
		SemanticsBase<double[]> copy = (SemanticsBase<double[]>) super.clone();
		copy.value = this.value.clone();
		return copy;
	}

	@Override
	public Double getValue(int fitnessCase) {
		assert fitnessCase < this.value.length;
		return this.value[fitnessCase];
	}

	/*@Override
	public boolean isBetween(ISemantics semantics1, ISemantics semantics2, double p) {
		SemanticsBase<double[]> sem1 = (SemanticsBase<double[]>) semantics1;
		SemanticsBase<double[]> sem2 = (SemanticsBase<double[]>) semantics2;
		int equal1Count = 0;
		int equal2Count = 0;

		for (int i = 0; i < sem1.value.length; ++i) {
			// (sem1[i] <= center[i] && center[i] <= sem2[i]) || (sem2[i] <= center[i] && center[i] <= sem1[i])

			// we have to take into account bitwise representation here, since -0.0 == 0.0, but they have different hashcodes
			if (Double.doubleToLongBits(sem1.value[i]) == Double.doubleToLongBits(this.value[i])) {
				++equal1Count;
			}
			else if (Double.doubleToLongBits(sem2.value[i]) == Double.doubleToLongBits(this.value[i])) {
				++equal2Count;
			}
			else if ((sem1.value[i] <= sem2.value[i] && (sem1.value[i] > this.value[i] || this.value[i] > sem2.value[i]))
					||
					(sem1.value[i] >= sem2.value[i] && (sem2.value[i] > this.value[i] || this.value[i] > sem1.value[i]))) {
				return false;
			}
		}

		return equal1Count < this.value.length && equal2Count < this.value.length;
	}*/

	@Override
	public boolean isConstant() {
		assert this.value.length >= 1;
		double value0 = this.value[0];

		if (Double.isNaN(value0)) {
			for (int i = 1; i < this.value.length; ++i) {
				if (!Double.isNaN(this.value[i])) {
					return false;
				}
			}
			return true;
		}

		for (int i = 1; i < this.value.length; ++i) {
			if (value0 != this.value[i]) {
				return false;
			}
		}

		return true;
	}

	@Override
	public ISemantics getMidpointBetweenMeAnd(ISemantics _other) {
		DoubleSemantics other = (DoubleSemantics) _other;

		assert this.value.length == other.value.length;

		double[] midpoint = new double[this.value.length];
		for (int i = 0; i < this.value.length; ++i) {
			midpoint[i] = 0.5 * (this.value[i] + other.value[i]);
		}

		return new DoubleSemantics(midpoint);
	}

	@Override
	public int compareTo(ISemantics o) {
		DoubleSemantics other = (DoubleSemantics) o;

		assert this.value.length == other.value.length;

		for (int i = 0; i < this.value.length; ++i) {
			int cmp = Double.compare(this.value[i], other.value[i]);
			if (cmp != 0)
				return cmp;
		}

		return 0;
	}

	/**
	 * Calculates distance to other semantics according to the Euclidean distance.
	 *
	 * @param other
	 * @return
	 */
	@Override
	public double distanceTo(ISemantics other) {
		return this.distanceTo(other, 2);
	}

	/**
	 * Calculates distance to other semantics according to the Euclidean distance, without calculating the root.
	 *
	 * @param other
	 * @return
	 */
	@Override
	public double fastDistanceTo(ISemantics other) {
		return this.fastDistanceTo(other, 2);
	}

	@Override
	public double fastDistanceTo(ISemantics _other, double p) {
		SemanticsBase<double[]> other = (SemanticsBase<double[]>) _other;

		assert this.value.length == other.value.length;

		double distance = 0.0;
		for (int i = 0; i < this.value.length; ++i) {
			distance += Math.pow(Math.abs(this.value[i] - other.value[i]), p);
		}

		return distance;
	}

	@Override
	public double distanceToManhattanSegment(ISemantics startingPoint, ISemantics endingPoint) {
		double[] s1 = (double[]) startingPoint.getValue();
		double[] s2 = (double[]) endingPoint.getValue();

		assert s1.length == s2.length;
		assert s1.length == this.value.length;

		double distance = 0.0;

		for (int i = 0; i < this.value.length; ++i) {
			if ((this.value[i] < s1[i] && this.value[i] < s2[i]) || (this.value[i] > s1[i] && this.value[i] > s2[i])) {
				distance += Math.min(Math.abs(this.value[i] - s1[i]), Math.abs(this.value[i] - s2[i]));
			}
		}

		return distance;
	}

	@Override
	public ISemantics counterpointTo(ISemantics o) {
		DoubleSemantics other = (DoubleSemantics) o;
		double[] counterpoint = new double[this.value.length];

		assert this.value.length == other.value.length;

		for (int i = 0; i < this.value.length; ++i) {
			counterpoint[i] = this.value[i] + this.value[i] - other.value[i];
		}

		return new DoubleSemantics(counterpoint);
	}

	@Override
	public boolean isWellDefined() {
		for (int i = 0; i < this.value.length; ++i) {
			if (Double.isNaN(this.value[i]) || Double.isInfinite(this.value[i])) {
				return false;
			}
		}

		return true;
	}

	@Override
	public Iterator<Double> iterator() {
		return new Iterator<Double>() {

			private int index = 0;

			@Override
			public boolean hasNext() {
				return index < size();
			}

			@Override
			public Double next() {
				return value[index++];
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}

		};
	}

	@Override
	public int size() {
		return this.value.length;
	}

	@Override
	public String toString() {
		return Arrays.toString(this.value);
	}

	@Override
	public boolean isAnyComponentSmallerThan(ISemantics _other) {
		double[] other = (double[]) _other.getValue();

		assert other.length == this.value.length;

		for (int i = 0; i < this.value.length; ++i) {
			if (this.value[i] < other[i])
				return true;
		}

		return false;
	}

	@Override
	public boolean isAnyComponentBiggerThan(ISemantics _other) {
		double[] other = (double[]) _other.getValue();

		assert other.length == this.value.length;

		for (int i = 0; i < this.value.length; ++i) {
			if (this.value[i] > other[i])
				return true;
		}

		return false;
	}

	@Override
	public ISemantics min(ISemantics _other) {
		double[] other = ((double[]) _other.getValue()).clone();

		assert other.length == this.value.length;

		for (int i = 0; i < this.value.length; ++i) {
			other[i] = Math.min(other[i], this.value[i]);
			if (other[i] == Double.NEGATIVE_INFINITY || Double.isNaN(other[i])) {
				other[i] = -Double.MAX_VALUE;
			}
		}

		return new DoubleSemantics(other);
	}

	@Override
	public ISemantics max(ISemantics _other) {
		double[] other = ((double[]) _other.getValue()).clone();

		assert other.length == this.value.length;

		for (int i = 0; i < this.value.length; ++i) {
			other[i] = Math.max(other[i], this.value[i]);
			if (other[i] == Double.POSITIVE_INFINITY || Double.isNaN(other[i])) {
				other[i] = Double.MIN_VALUE;
			}
		}

		return new DoubleSemantics(other);
	}
}
