package com.srbenoit.modeling.grid;

import com.srbenoit.log.LoggedObject;
import com.srbenoit.sparsearray.SparseArray;
import com.srbenoit.sparsearray.SparseArrayListener;

/**
 * The base class for grid that may contain objects . Contained objects must be derived from <code>
 * GridMember</code>. This class provides a very fast way to iterate over the set of potential
 * neighbors of an object.
 *
 * <p>The grid manages a sparse array data structure that assigns each added object an array index
 * that does not change while the object is part of the grid. Removing objects may open gaps in the
 * array, which newly added objects will fill first before expanding the array
 */
public abstract class AbstractGrid2D extends LoggedObject implements SparseArrayListener {

    /** number of objects in an allocated block (a multiple of 64) */
    protected static final int BLOCK_SIZE = 128;

    /** a pre-allocated array of zero integers */
    protected static final int[] ZERO_INTS = new int[0];

    /** width and height of the grid cells (they must be square) */
    protected final double gridCellSize;

    /** the list containing grid objects (size >= N) */
    private SparseArray<GridMember2Int> objects;

    /**
     * Constructs a new <code>AbstractGrid</code>.
     *
     * @param  cellSize  width and height of the grid cells
     */
    public AbstractGrid2D(final double cellSize) {

        this.gridCellSize = cellSize;
        this.objects = new SparseArray<GridMember2Int>(GridMember2Int.class, 128);

        this.objects.addListener(this);
    }

    /**
     * Gets the size of a grid cell.
     *
     * @return  the grid cell size
     */
    public double getGridCellSize() {

        return this.gridCellSize;
    }

    /**
     * Given an index, returns the index of the next <code>GridMember</code>.
     *
     * @param   index  the index to test
     * @return  the index of the next set bit after (or including) the given index
     */
    public int getNextFilled(final int index) {

        return this.objects.nextFilled(index);
    }

    /**
     * Gets a particular member of the grid.
     *
     * @param   index  the index of the member to retrieve
     * @return  the grid member, or <code>null</code> if that index is empty
     */
    public GridMember2Int get(final int index) {

        return this.objects.get(index);
    }

    /**
     * Adds a member object to the grid.
     *
     * @param   member  the grid member to add to the grid
     * @return  the immutable integer index of the member in the grid's object array
     */
    public int add(final GridMember2Int member) {

        int index;

        index = this.objects.add(member);

        // Let subclasses process the addition
        memberAdded(index);

        return index;
    }

    /**
     * Gets the number of objects in the grid.
     *
     * @return  the number of objects
     */
    public int getNumObjects() {

        return this.objects.size();
    }

    /**
     * Called when a member has been added - subclasses should compute grid cells for the object
     * and add the object to sorted index arrays
     *
     * @param  index  the index of the member that has just been added
     */
    protected abstract void memberAdded(final int index);

    /**
     * Removes a member object from the grid.
     *
     * @param  index  the index of the grid member to remove
     */
    public void remove(final int index) {

        if (this.objects.remove(index) == null) {
            LOG.warning("Attempt to remove from empty index");
        } else {
            memberRemoved(index);
        }
    }

    /**
     * Called when a member is about to be removed - subclasses should clean up actions performed
     * in <code>memberAdded</code>. The object still exists in the grid's object array when this
     * method is called.
     *
     * @param  index  the index of the member that is being removed
     */
    protected abstract void memberRemoved(final int index);

    /**
     * Updates the grid address for a grid member that has moved.
     *
     * @param  index  the index of the grid member that moved
     * @param  newX   the grid member's new X coordinate
     * @param  newY   the grid member's new Y coordinate
     */
    public abstract void move(final int index, final double newX, final double newY);

    /**
     * Converts a coordinate value to a grid address.
     *
     * @param   coord   the coordinate to convert
     * @param   min     the minimum coordinate value of the grid on the coordinate axis
     * @param   extent  the extent of the grid in the dimension of interest
     * @return  the address of the coordinate
     */
    protected int coordinateToAddress(final double coord, final double min, final int extent) {

        double addr;
        int result;

        addr = (coord - min) / this.gridCellSize;

        if (addr < 0) {
            result = 0;
        } else if (addr >= extent) {
            result = extent - 1;
        } else {
            result = (int) addr;
        }

        return result;
    }

    /**
     * Called when the capacity of the sparse array is changed, to allow other programs that need
     * to keep arrays of the same size to adjust their arrays.
     *
     * @param  array        the array whose capacity is changing
     * @param  newCapacity  the new capacity of the sparse array
     */
    public abstract void capacityChanged(SparseArray<?> array, int newCapacity);
}
