package ec.gp.semantic;

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

/**
 * Semantics represented by byte vector.
 *
 * @author Tomasz Pawlak
 */
public class ByteSemantics extends SemanticsBase<byte[]> {

	private final int hashCode;

	public ByteSemantics(byte[] values) {
		super(values);
		this.hashCode = Arrays.hashCode(values);
	}

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

		boolean result = Arrays.equals(this.value, other.value);
		assert !result || this.hashCode() == other.hashCode();
		return result;
	}

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

	@Override
	public ISemantics getMidpointBetweenMeAnd(ISemantics other) {
		byte[] _other = (byte[]) other.getValue();
		byte[] midpoint = new byte[this.value.length];

		for (int i = 0; i < this.value.length; ++i) {
			// all arithmetic operations on bytes in java results in integer, so there is no risk of an overflow
			midpoint[i] = (byte) ((this.value[i] + _other[i]) >> 1);
		}

		return new ByteSemantics(midpoint);
	}

	@Override
	public Object getValue(int fitnessCase) {
		return this.value[fitnessCase];
	}

	@Override
	public boolean isAnyComponentSmallerThan(ISemantics other) {
		byte[] _other = (byte[]) other.getValue();
		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) {
		byte[] _other = (byte[]) other.getValue();
		for (int i = 0; i < this.value.length; ++i) {
			if (this.value[i] > _other[i]) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isConstant() {
		byte value0 = this.value[0];
		for (int i = 1; i < this.value.length; ++i) {
			if (value0 != this.value[i]) {
				return false;
			}
		}
		return true;
	}

	@Override
	public ISemantics min(ISemantics other) {
		byte[] _other = (byte[]) other.getValue();
		byte[] min = new byte[this.value.length];

		for (int i = 0; i < this.value.length; ++i) {
			// all arithmetic operations on bytes in java results in integer, so there is no risk of an overflow
			min[i] = this.value[i] < _other[i] ? this.value[i] : _other[i];
		}

		return new ByteSemantics(min);
	}

	@Override
	public ISemantics max(ISemantics other) {
		byte[] _other = (byte[]) other.getValue();
		byte[] max = new byte[this.value.length];

		for (int i = 0; i < this.value.length; ++i) {
			// all arithmetic operations on bytes in java results in integer, so there is no risk of an overflow
			max[i] = this.value[i] > _other[i] ? this.value[i] : _other[i];
		}

		return new ByteSemantics(max);
	}

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

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

		int distance = 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 distanceTo(ISemantics other) {
		return this.distanceTo(other, 1);
	}

	@Override
	public double fastDistanceTo(ISemantics other) {
		return this.fastDistanceTo(other, 1);
	}

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

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

		int distance = 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 _other) {
		DoubleSemantics other = (DoubleSemantics) _other;
		byte[] counterpoint = new byte[this.value.length];

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

		for (int i = 0; i < this.value.length; ++i) {
			// all arithmetic operations on bytes in java result in int, thus there is no risk of an overflow
			counterpoint[i] = (byte) (this.value[i] + this.value[i] - other.value[i]);
		}

		return new ByteSemantics(counterpoint);
	}

	@Override
	public boolean isWellDefined() {
		return true;
	}

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

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

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

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

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

		return 0;
	}

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

			private int index = 0;

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

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

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

		};
	}
}
