package ec.gp.semantic.geometry;

import ec.gp.semantic.DoubleSemantics;
import ec.gp.semantic.ISemantics;
import gnu.trove.list.array.TIntArrayList;

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

/**
 * @author Tomasz Pawlak
 */
public class DoubleBallBuilder implements IBallBuilder {
    @Override
    public Iterator<ISemantics> getPointsAround(final ISemantics center, final double radius) {

        final TIntArrayList indexes = new TIntArrayList();
        indexes.add(0);

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

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

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

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

                double[] sem = (double[]) center.getValue();

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

                    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
                        if ((mask & (1 << i)) != 0) {
                            // add radius
                            copy[index] += radius;
                        } else {
                            // subtract radius
                            copy[index] -= radius;
                        }
                    }

                    this.nextSemantics.add(new DoubleSemantics(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 this.nextSemantics.poll();
            }
        };
    }
}
