package ec.gp.semantic;

import ec.util.MersenneTwisterFast;
import library.semantics.BitSet;

import java.util.Iterator;

public final class BooleanSemantics extends SemanticsBase<BitSet> {

    private final static MersenneTwisterFast random = new MersenneTwisterFast(0);
    private final int hashCode;

    public BooleanSemantics(BitSet values) {
        super(values);
        this.hashCode = values.hashCode();
    }

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

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

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

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

    @Override
    public Boolean getValue(int fitnessCase) {
        assert fitnessCase < this.value.length();
        return this.value.get(fitnessCase);
    }

	/*@Override
    public boolean isBetween(ISemantics semantics1, ISemantics semantics2) {
		SemanticsBase<BitSet> sem1 = (SemanticsBase<BitSet>) semantics1;
		SemanticsBase<BitSet> sem2 = (SemanticsBase<BitSet>) semantics2;

		BitSet mask = (BitSet) sem1.value.clone();
		mask.xor(sem2.value); // 0 - must match sem1, 1 - we don't care

		BitSet match = (BitSet) this.value.clone();
		match.xor(sem1.value); // 0 - equal, 1 - different

		match.andNot(mask); // 0 - matches mask, 1 - doesn't match mask

		return match.nextSetBit(0) == -1 && !this.value.equals(sem1.value) && !this.value.equals(sem2.value);
	}*/

    @Override
    public boolean isConstant() {
        final int cardinality = this.value.cardinality();
        return cardinality == 0 || cardinality == this.value.length();
    }

    @Override
    public ISemantics getMidpointBetweenMeAnd(ISemantics _other) {
        BooleanSemantics other = (BooleanSemantics) _other;
        Boolean lastFromThis = random.nextBoolean();

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

        BitSet midpoint = new BitSet(this.value.length());
        for (int i = 0; i < this.value.length(); ++i) {
            // this procedure tries to equalize number of bits rewritten from both parents
            // by rewriting value once from one parent and once from the other

            // the commented code and the code below are equivalent, however the latter
            // one does not involve code branching

			/*if (this.values[i] == other.values[i]) {
                midpoint[i] = this.values[i];
			} else {	
				midpoint[i] = lastFromThis ? other.values[i] : this.values[i];
				lastFromThis = !lastFromThis;
			}*/

            midpoint.set(i, (lastFromThis && this.value.get(i)) || (!lastFromThis && other.value.get(i)));
            lastFromThis = lastFromThis ^ this.value.get(i) ^ other.value.get(i);
        }

        return new BooleanSemantics(midpoint);
    }

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

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

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

        return 0;
    }

    /**
     * Calculates the distance according to the Hamming metric.
     */
    @Override
    public double distanceTo(ISemantics other) {
        return this.distanceTo(other, 1);
    }

    /**
     * Calculates the distance according to the Hamming metric.
     */
    @Override
    public double fastDistanceTo(ISemantics other) {
        return this.fastDistanceTo(other, 1);
    }

    @Override
    public double fastDistanceTo(ISemantics _other, double p) {
        BitSet other = (BitSet) ((BitSet) _other.getValue()).clone();

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

        other.xor(this.value);
        return other.cardinality();
    }

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

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

        BitSet s1c = (BitSet) s1.clone();
        s1c.and(s2);
        s1c.andNot(this.value);

        BitSet vc = (BitSet) this.value.clone();
        vc.andNot(s1);
        vc.andNot(s2);

        s1c.or(vc);
        return s1c.cardinality();
    }

    @Override
    public ISemantics counterpointTo(ISemantics o) {
        // it cannot be other point than `this', consider all possible cases:
        // this = 0, o = 0 => counterpoint = 0
        // this = 0, o = 1 => counterpoint = -1, but we cannot set it to -1, so 0
        // this = 1, o = 0 => counterpoint = 2, but we cannot set it to 2, so 1
        // this = 1, o = 1 => counterpoint = 1
        return this.clone();
    }

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

    public Iterator<Boolean> iterator() {
        return new Iterator<Boolean>() {

            private int index = 0;

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

            @Override
            public Boolean next() {
                return value.get(index++);
            }

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

        };
    }

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

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

    @Override
    public boolean isAnyComponentSmallerThan(ISemantics _other) {
        BitSet other = (BitSet) ((BitSet) _other.getValue()).clone();

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

        // this/other
        // 00 -> 0
        // 11 -> 0
        // 01 -> 1
        // 10 -> 1 (this is wrong)
        other.xor(this.value);
        // fix the wrong case
        other.andNot(this.value);

        return other.cardinality() > 0;
    }

    @Override
    public boolean isAnyComponentBiggerThan(ISemantics _other) {
        BitSet other = (BitSet) ((BitSet) _other.getValue()).clone();

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

        // this/other
        // 00 -> 0
        // 11 -> 0
        // 01 -> 1 (this is wrong)
        // 10 -> 1
        other.xor(this.value);
        // fix the wrong case
        other.and(this.value);

        return other.cardinality() > 0;
    }

    @Override
    public ISemantics min(ISemantics _other) {
        BitSet other = (BitSet) ((BitSet) _other.getValue()).clone();

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

        other.and(this.value);
        return new BooleanSemantics(other);
    }

    @Override
    public ISemantics max(ISemantics _other) {
        BitSet other = (BitSet) ((BitSet) _other.getValue()).clone();

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

        other.or(this.value);
        return new BooleanSemantics(other);
    }
}
