package org.vostan.banvor.model;

import java.util.Arrays;
import java.util.Collections;
import java.util.Vector;

public class PuzzleRouteFinder {
    protected Puzzle puzzle = null;
    protected int [] moves;

    //
    // This is to avoid fragmentation of memory.
    //
    private Vector<XYPair> setOfCells = new Vector<XYPair>();

    public PuzzleRouteFinder(Puzzle p) {
        puzzle = p;
        moves = new int[puzzle.getColumnCount() * puzzle.getRowCount()];
        setOfCells.ensureCapacity(puzzle.getColumnCount() * puzzle.getRowCount());
    }

    protected final int getMoves( XYPair xy ) {
        return moves[puzzle.getIndex(xy)];
    }
    protected void setMoves( XYPair xy, int v )
    {
        moves[puzzle.getIndex(xy)]=v;
    }
    private boolean setMovesIfGreater( XYPair xy, int l )
    {
        //
        //	If out of borders then nothing to do.
        //
        if ( !puzzle.isValid(xy) )
            return false;
        //
        //	Check if the cell is a floor or goal. If yes then set the l
        //	if current value is greater than l.
        //
        if ( getMoves(xy) > l && puzzle.isEmpty(puzzle.getSym(xy)))
        {
            setMoves(xy,l);
            return true;
        }
        else
            return false;
    }

    public void calcMoves()
    {
        //
        //	Erase moves array.
        //
        Arrays.fill(moves,Integer.MAX_VALUE);
        //
        //	For the beginning there is no cell in the list.
        //
        int front = 0;
        setOfCells.removeAllElements();
        //
        //	Set the seed.
        //
        setMoves(puzzle.getWorker(),0);
        setOfCells.add(puzzle.getWorker());
        //
        //	Now on each loop pop one cell from the list and calculate the
        //	distance of cell around that cell.
        //
        while ( front < setOfCells.size() )
        {
            //
            //	Pop the cell.
            //
            XYPair xy = setOfCells.elementAt(front++);
            //
            //	Increase the length of cells all around given cell and push
            //	them into the list.
            //
            int l = getMoves(xy)+1;
            if ( setMovesIfGreater(xy.left(),l) ) {
                setOfCells.add(xy.left());
            }
            if ( setMovesIfGreater(xy.right(),l) ) {
                setOfCells.add(xy.right());
            }
            if ( setMovesIfGreater(xy.down(),l) ) {
                setOfCells.add(xy.down());
            }
            if ( setMovesIfGreater(xy.up(),l) ) {
                setOfCells.add(xy.up());
            }
        }
    }

    public final int stepsAway( XYPair xy )
    {
        return getMoves(xy);
    }

    public final boolean isAccessible( XYPair xy ) {
        return puzzle.isValid(xy) && stepsAway(xy) != Integer.MAX_VALUE;
    }

    public Vector<XYPair> getDirections( XYPair xy )
    {
        int away = stepsAway(xy);
        if (away == Integer.MAX_VALUE)
            return null;
        //
        //	Ok looks there is a routh to given cell. Now create an array
        //	and fill in the step to get to the cell.
        //
        Vector<XYPair> steps = new Vector<XYPair>(away);
        steps.add(xy);
        while ( steps.size() < away )
        {
            xy = steps.lastElement();
            int j = stepsAway(xy);
            if ( stepsAway(xy.left()) < j ) {
                steps.add(xy.left());
            }
            else if ( stepsAway(xy.right()) < j ) {
                steps.add(xy.right());
            }
            else if ( stepsAway(xy.down()) < j ) {
                steps.add(xy.down());
            }
            else if ( stepsAway(xy.up()) < j ) {
                steps.add(xy.up());
            }
        }
        Collections.reverse(steps);
        return steps;
    }

    public XYPair [] getPushDirections( XYPair xy )
    {
        //
        //	The selected box can be moved only orthogonally.
        //	Check that worker is on opposite side of the box.
        //	Statement:
        //	If scaliar product of selected->x,y and worker->selected is equal
        //	to manhatten distance of x,y from the selected box then the worker
        //	push direction is directed to x,y and is not opposit. In other words
        //	selected->x,y and worker->selected are codirectional.
        //
        XYPair sel = puzzle.getSelected();
        XYPair dist = XYPair.sub(xy, puzzle.getSelected());
        XYPair dir = XYPair.sub(puzzle.getSelected(), puzzle.getWorker());
        double scaliar = (double)(dist.x())*(double)(dir.x())
                +(double)(dist.y())*(double)(dir.y());
        int len = dist.l1_norm();
        if ( scaliar != (double)len )
            return null;
        //
        //	Now check that all cell till x,y are free.
        //
        XYPair ixy = new XYPair(puzzle.getSelected());
        while ( !xy.isEqual(ixy) )
        {
            ixy = ixy.add(dir);
            if ( !puzzle.isEmpty(puzzle.getSym(ixy)) )
                return null;
        }
        //
        //	Looks we could move the box till x,y. Create the steps array
        //	and fill it with push steps.
        //
        XYPair steps[] = new XYPair[len];
        int i = 0;
        ixy.set(puzzle.getSelected());
        while ( !xy.isEqual(ixy) )
        {
            steps[i++] = ixy;
            ixy = ixy.add(dir);
        }
        return steps;
    }

}
