package com.srbenoit.modeling.grid;

import java.awt.Color;
import java.util.logging.Level;
import com.srbenoit.geom.BasedVector2;

/**
 * A basic grid member based on <code>BasedVector2</code>.
 */
public class BasedVectorGridMember2 extends BasedVector2 implements GridMember2Int {

    /** the grid in which this member object is installed */
    private Grid2D grid;

    /** the index of the member in the grid */
    private int gridIndex;

    /** the number of neighbors this object currently has */
    private int numNeighbors;

    /** the objects that are neighbors of this object */
    private GridMember2Int[] neighbors;

    /**
     * Constructs a new <code>BasedVectorGridMember2</code>.
     *
     * @param  baseX  the X coordinate of the base point
     * @param  baseY  the Y coordinate of the base point
     * @param  vecX   the X component of the vector
     * @param  vecY   the Y component of the vector
     */
    public BasedVectorGridMember2(final double baseX, final double baseY, final double vecX,
        final double vecY) {

        super(baseX, baseY, vecX, vecY);

        this.grid = null;
        this.gridIndex = -1;

        this.numNeighbors = 0;
        this.neighbors = new GridMember2Int[4];
    }

    /**
     * Gets this grid this object is installed in.
     *
     * @return  the grid
     */
    public Grid2D getGrid() {

        return this.grid;
    }

    /**
     * Gets the index of the object in the grid where it is installed.
     *
     * @return  the grid index
     */
    public int getGridIndex() {

        return this.gridIndex;
    }

    /**
     * Gets the radius to use when drawing the grid member.
     *
     * @return  the radius (0 to draw as a point)
     */
    public double getRadius() {

        return 0;
    }

    /**
     * Gets the color in which to render the element.
     *
     * @return  the color
     */
    public Color getColor() {

        return Color.BLACK;
    }

    /**
     * Gets the fill color in which to render the element.
     *
     * @return  the color, or <code>null</code> for no fill
     */
    public Color fillColor() {

        return null;
    }

    /**
     * Installs the object in a grid. If the object is a member of a grid when this method is
     * called, the object is first removed from that grid, then added to <code>theGrid</code>.
     *
     * @param  theGrid  the grid in which the object is being installed
     */
    public void installInGrid(final Grid2D theGrid) {

        if (this.grid != null) {
            removeFromGrid();
        }

        if (theGrid == null) {
            this.gridIndex = -1;
        } else {
            this.gridIndex = theGrid.add(this);
        }

        this.grid = theGrid;
    }

    /**
     * Removes the object from the grid in which it is installed, if any.
     */
    public void removeFromGrid() {

        if (this.grid == null) {
            LOG.warning("Attempt to remove member from grid that was not in a grid");
        } else {
            this.grid.remove(this.gridIndex);
            this.grid = null;
            this.gridIndex = -1;
        }
    }

    /**
     * Moves the object to a new position. If the object is installed in a grid, the grid is
     * notified so it can update the object's grid cell address.
     *
     * @param  xCoord  the new X coordinate
     * @param  yCoord  the new Y coordinate
     */
    @Override public void setPos(final double xCoord, final double yCoord) {

        if (Double.isNaN(xCoord) || Double.isNaN(yCoord)) {
            LOG.log(Level.WARNING, "Attempt to set grid member position to NaN", new Exception());
            System.exit(1);
        }

        if (Double.isInfinite(xCoord) || Double.isInfinite(xCoord)) {
            LOG.log(Level.WARNING, "Attempt to set grid member position by Infinity",
                new Exception());
            System.exit(1);
        }

        super.setPos(xCoord, yCoord);

        if (this.grid != null) {
            this.grid.move(this.gridIndex, xCoord, yCoord);
        }
    }

    /**
     * Moves the object, by adjusting its position by a specified amount. If the object is
     * installed in a grid, the grid is notified so it can update the object's grid cell address.
     *
     * @param  deltaX  the change in X coordinate
     * @param  deltaY  the change in Y coordinate
     */
    @Override public void move(final double deltaX, final double deltaY) {

        if (Double.isNaN(deltaX) || Double.isNaN(deltaY)) {
            LOG.log(Level.WARNING, "Attempt to move grid member position by NaN", new Exception());
            System.exit(1);
        }

        if (Double.isInfinite(deltaX) || Double.isInfinite(deltaY)) {
            LOG.log(Level.WARNING, "Attempt to move grid member position by Infinity",
                new Exception());
            System.exit(1);
        }

        super.move(deltaX, deltaY);

        if (this.grid != null) {
            this.grid.move(this.gridIndex, getPosX(), getPosY());
        }
    }

    /**
     * Clears the list of this object's neighbors.
     */
    public void clearNeighbors() {

        this.numNeighbors = 0;
    }

    /**
     * Adds an object to the list of this object's neighbors.
     *
     * @param  neighbor  the neighbor to add
     */
    public void addNeighbor(final GridMember2Int neighbor) {

        GridMember2Int[] newList;

        if (this.numNeighbors == this.neighbors.length) {
            newList = new GridMember2Int[this.numNeighbors + 4];
            System.arraycopy(this.neighbors, 0, newList, 0, this.numNeighbors);
            this.neighbors = newList;
        }

        this.neighbors[this.numNeighbors] = neighbor;
        this.numNeighbors++;
    }

    /**
     * Gets the number of neighbors the member has.
     *
     * @return  the number of neighbors
     */
    public int getNumNeighbors() {

        return this.numNeighbors;
    }

    /**
     * Gets a neighbor grid member.
     *
     * @param   index  the index of the neighbor
     * @return  the neighbor
     */
    public GridMember2Int getNeighbor(final int index) {

        return this.neighbors[index];
    }
}
