package ec.gp.semantic.geometry;

import ec.gp.semantic.BooleanSemantics;
import ec.gp.semantic.ISemantics;
import gnu.trove.list.array.TIntArrayList;
import library.semantics.BitSet;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;

/**
 * @author Tomasz Pawlak
 */
public class BooleanBallBuilder implements IBallBuilder {
    @Override
    public Iterator<ISemantics> getPointsAround(ISemantics center, double radius) {
        final Queue<ISemantics> nextSemantics = new LinkedList<ISemantics>();
        final TIntArrayList indexes = new TIntArrayList();
        indexes.add(0);

        BitSet sem = (BitSet) center.getValue();
        int zeros = 0, ones = 0;
        for (int i = 0; i < sem.length(); ++i) {
            boolean bit = sem.get(i);
            if ((bit && ++ones != 0 && zeros != 0) || (!bit && ones != 0 && ++zeros != 0)) {
                break;
            }
        }
        if (zeros == sem.length() || ones == sem.length()) {
            // we have to add center semantics to the set, otherwise we cannot include it in Boolean ball
            nextSemantics.add(center);
        }

        return new Iterator<ISemantics>() {
            private boolean canProduceMore = true;

            @Override
            public boolean hasNext() {
                return nextSemantics.size() != 0 || this.canProduceMore;
            }

            @Override
            public ISemantics next() {
                if (nextSemantics.size() > 0) {
                    return nextSemantics.poll();
                }

                if (!this.canProduceMore) {
                    throw new NoSuchElementException();
                }

                BitSet sem = (BitSet) center.getValue();

                // modify components at positions marked by indexes
                int maxMask = 1 << indexes.size();
                for (int mask = 0; mask < maxMask; ++mask) {
                    BitSet copy = (BitSet) sem.clone();

                    boolean atLeastOneBitChanged = false;
                    for (int i = 0; i < indexes.size(); ++i) {
                        int index = indexes.get(i); // index to modify
                        // mask tells whether to add to or subtract from a particular component of semantics
                        boolean newBit = (mask & (1 << i)) != 0;
                        if (copy.get(index) != newBit) {
                            copy.set(index, newBit);
                            atLeastOneBitChanged = true;
                        }
                    }

                    if (atLeastOneBitChanged) {
                        assert !sem.equals(copy);
                        nextSemantics.add(new BooleanSemantics(copy));
                    }
                }

                // update indexes
                int carry = 1;
                for (int i = indexes.size() - 1; i >= 0; --i) {
                    int index = indexes.get(i) + carry;
                    int newVal = index % (sem.length() - (indexes.size() - i - 1));
                    indexes.set(i, newVal);
                    if (index != newVal) {
                        carry = 1;
                    } else {
                        carry = 0;
                        break;
                    }
                }
                for (int i = 1, lastIndex = indexes.get(0); i < indexes.size(); ++i) {
                    int index = indexes.get(i);
                    if (lastIndex >= index) {
                        indexes.set(i, lastIndex + 1);
                    }
                }

                if (carry != 0) {
                    // not enough indexes, add one
                    // but only if number of semantics components is less than the current number of indexes
                    if (indexes.size() < sem.length()) {
                        indexes.add(indexes.size());
                        // at this point indexes should be consecutive natural numbers
                        for (int i = 0; i < indexes.size(); ++i) {
                            assert i == indexes.get(i);
                        }
                    } else {
                        this.canProduceMore = false;
                    }
                }

                return nextSemantics.poll();
            }
        };
    }
}
