package com.srbenoit.modeling.grid;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import com.srbenoit.log.LoggedObject;

/**
 * A class to test the <code>Grid</code> class by constructing a grid and randomly adding and
 * removing many objects, then testing the grid state for correctness and ensuring that neighbor
 * lists returned are valid.
 */
public class GridTest2D extends LoggedObject implements Runnable {

    /** the number of test iterations */
    private static final int NUM_ITERATIONS = 200000;

    /** the maximum number of objects to have in the grid at a time */
    private static final int MAX_OBJECTS = 100000;

    /** the grid under test */
    private final Grid2D grid;

    /** the panel that will render the grid */
    private GridPanel2D panel;

    /**
     * Constructs a new <code>GridTest</code>.
     */
    public GridTest2D() {

        // build test grid: X ranges from 0-20, Y ranges from 0-10
        this.grid = new Grid2D(0, 0, 1.0, 20, 10);
    }

    /**
     * Gets the grid being tested.
     *
     * @return  the grid
     */
    public Grid2D getGrid() {

        return this.grid;
    }

    /**
     * Builds the frame and panel in the AWT event thread.
     */
    @Override public void run() {

        JFrame frame;

        frame = new JFrame("Grid Test");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        this.panel = new GridPanel2D(800, 800, this.grid);
        frame.setContentPane(this.panel);
        frame.pack();
        frame.setVisible(true);
    }

    /**
     * Executes the tests.
     */
    public void execute() {

        Random rnd;
        List<GridMember2Int> objects;
        float pctFull;
        int index;
        boolean addNew;
        boolean move;
        boolean delete;
        GridMember2Int member;
        GridMember2Int test;
        GridIterator2D iter;
        int count;
        int xAddr;
        int yAddr;
        double distX;
        double distY;

        try {
            SwingUtilities.invokeAndWait(this);
        } catch (Exception ex) {
            Logger.getLogger(GridTest2D.class.getName()).log(Level.SEVERE, null, ex);

            return;
        }

        rnd = new Random(System.currentTimeMillis());
        objects = new ArrayList<GridMember2Int>(MAX_OBJECTS);

        for (int i = 0; i < NUM_ITERATIONS; i++) {
            pctFull = (float) objects.size() / MAX_OBJECTS;

            addNew = false;
            move = false;
            delete = false;

            // If the list is empty, we always add a node
            if (rnd.nextFloat() < pctFull) {

                if (rnd.nextBoolean()) {
                    move = true;
                } else {
                    delete = true;
                }
            } else {
                addNew = true;
            }

            if (addNew) {
                member = new DynamicGridMember2D(rnd.nextDouble() * 20, rnd.nextDouble() * 10, // NOPMD SRB
                        0, EnumElementType.FIXED, 1, 1);
                objects.add(member);
                member.installInGrid(this.grid);
            } else {
                index = rnd.nextInt(objects.size());

                if (move) {
                    member = objects.get(index);
                    member.move(rnd.nextDouble() * 20, rnd.nextDouble() * 10);
                } else if (delete) {
                    member = objects.remove(index);
                    member.removeFromGrid();
                }
            }
        }

        this.panel.update(0);
        LOG.log(Level.FINE, "After test, {0}/{1} objects",
            new Object[] { objects.size(), getGrid().getNumObjects() });

        // See if all elements in the grid have the appropriate address
        iter = new GridIterator2D(grid);
        count = 0;

        while (iter.hasNext()) {
            member = iter.next();

            xAddr = grid.getXAddress(member.getGridIndex());
            yAddr = grid.getYAddress(member.getGridIndex());

            if (xAddr != (int) member.getPosX()) {
                LOG.log(Level.WARNING, "X address mismatch: {0},{1}",
                    new Object[] { xAddr, member.getPosX() }); // NOPMD SRB
            }

            if (yAddr != (int) member.getPosY()) {
                LOG.log(Level.WARNING, "Y address mismatch: {0},{1}",
                    new Object[] { yAddr, member.getPosX() }); // NOPMD SRB
            }

            count++;
        }

        // Get all the neighbors of the first item
        index = this.grid.getNextFilled(0);
        member = this.grid.get(index);
        this.grid.getNeighborsOf(member, 0.5);

        LOG.log(Level.FINE, "{0} neighbors of point at ({1}, {2}) {3}",
            new Object[] { member.getNumNeighbors(), member.getPosX(), member.getPosY(), count });

        for (int i = 0; i < member.getNumNeighbors(); i++) {
            test = member.getNeighbor(i);

            distX = test.getPosX() - member.getPosX();
            distY = test.getPosY() - member.getPosY();

            if ((distX < -1.5) || (distX > 1.5) || (distY < -1.5) || (distY > 1.5)) {
                LOG.log(Level.FINE, "  ({0}, {1}) [{2}, {3}]",
                    new Object[] { test.getPosX(), test.getPosY(), distX, distY }); // NOPMD SRB
            }
        }
    }

    /**
     * Main method to run the test.
     *
     * @param  args  Command-line arguments (ignored).
     */
    public static void main(final String... args) {

        new GridTest2D().execute();
    }
}
