/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.vostan.sokoban.play;

import static java.lang.Math.*;
import java.util.Arrays;
import org.vostan.sokoban.Puzzle;

/**
 *
 * @author vahagnk
 */
public class PuzzleLogic
{
	//protected PlayActivity activity;
	protected Puzzle puzzle = null;
	protected int [] moves;
	protected int [] setOfCells;

	public PuzzleLogic()
	{}
	
	public void setPuzzle( Puzzle p )
	{
		puzzle = p;
		moves = new int[puzzle.getColumnCount()*puzzle.getRowCount()];
		setOfCells = new int[2*puzzle.getColumnCount()*puzzle.getRowCount()];
	}
	
	public boolean createSteps(Animator animator, final int x, final int y )
	{
		//
		//	Check that the x,y are valid.
		//
		if ( !puzzle.isValid(x, y) )
			return false;
		//
		//	Now check what tile was tapped.
		//	If the tapped is a floor then ...
		//
		int tile = puzzle.getSym( x, y);
		if ( Puzzle.isEmpty(tile) )
		{
			//
			//	Calculate possible moves map.
			//
			calcMoves();
			//
			//	Check if worker selected a box and can push it to the location?
			//	If yes then we are done.
			//
			if ( puzzle.isSelected() 
				&& tryMoveBox(animator, x, y) )
				return true;
			//
			//	Either nothing was selected or the box cannot be moved to
			//	tapped location. Try move worker alone. 
			//
			if ( tryMoveWorker( animator, x, y ) )
				return true;
			//
			//	Show that action is not allowed.
			//
			undoable( animator, x, y );
		}
		//
		//	The tapped is the worker. Try move the box. If not possible
		//	then unselect if selected.
		//
		else if ( Puzzle.hasWorker(tile) )
		{
			if ( !puzzle.isSelected() )
			{
				buzz( animator );
				return true;
			}
			//
			//	Calculate possible moves map.
			//
			calcMoves();
			//
			//	Check if worker selected a box and can push it to the location?
			//	If yes then we are done.
			//
			if ( tryMoveBox(animator, x, y) )
				return true;
			//
			//	If the box is not movable then unselect it.
			//
			unselect( animator );
		}
		//
		//	The tapped is a box.
		//
		else if ( Puzzle.hasBox(tile) )
		{
			//
			//	Calculate possible moves map.
			//
			calcMoves();
			//
			//	If the box is selected then unselect it.
			//
			if ( puzzle.isSelected(x,y) )
			{
				unselect( animator );
				return true;
			}
			//
			//	Try move the worker next to the box and select it if the
			//	box is not selected yet.
			//
			if ( trySelectBox( animator, x, y ) )
				return true;
			//
			//	Show that action is not allowed if reached till here.
			//
			undoable( animator, x, y );
		}
		return true;
	}
	
	//////////////////////////////////////////////////////////////////////////
	//
	//	Routes to accessible cells from where worker stands.
	//
	protected boolean tryMoveWorker( Animator animator, int x, int y )
	{
		//
		//	If the filed is not accessable then move failed.
		//
		if ( !isAccessible(x,y) )
			return false;
		//
		//	First unselect box.
		//
		if ( puzzle.isSelected() )
			unselect( animator );
		//
		//	Get directions and queue moves accordingly.
		//
		int dirs[] = getDirections(x, y);
		for ( int i = 0; i < dirs.length; i+=2 )
			move( animator, dirs[i],dirs[i+1] );
		//
		//	Done.
		//
		return true;
	}
		
	protected boolean tryMoveBox( Animator animator, int x, int y )
	{
		//
		//	If no box is selected then we cannot move no box.
		//
		if ( !puzzle.isSelected() )
			return false;
		//
		//	Check that asked move is orthogonal to the slected box.
		//
		int bx = puzzle.getSelectedX();
		int by = puzzle.getSelectedY();
		int dx = x-bx;
		int dy = y-by;
		if ( dx != 0 && dy != 0 )
			return false;
		//
		//	There is no point to continue also in case if the asked cell
		//	is the box.
		//
		if ( dx == 0 && dy == 0 )
			return false;
		//
		//	Now find the desired place for the worker to start push this
		//	box.
		//
		int wx = bx;
		int wy = by;
		if ( x < bx )
			++wx;
		else if ( x > bx )
			--wx;
		else if ( y < by )
			++wy;
		else
			--wy;
		//
		//	Check if the desired place for the worker is accessable? If not
		//	then there is no point to continue.
		//
		if ( !isAccessible(wx, wy) )
			return false;
		//
		//	Now check that all cell till x,y are empty and that we can
		//	push box till there.
		//
		int sx = bx-wx;
		int sy = by-wy;
		int ix = bx;
		int iy = by;
		while ( x != ix || y != iy )
		{
			ix+=sx;
			iy+=sy;
			int v = puzzle.getSym(ix,iy);
			if ( !puzzle.isEmpty(v) && !puzzle.hasWorker(v) )
				return false;
		}
		//
		//	Ok, looks we can do the desired action. Now put instructions on
		//	what to do. First move worker to desired position if he is not
		//	there already.
		//
		if ( wx != puzzle.getWorkerX() || wy != puzzle.getWorkerY() )
		{
			tryMoveWorker( animator, wx, wy );
			select( animator, bx, by );
		}
		//
		//	Now create the steps to push the box.
		//
		ix = bx;
		iy = by;
		while ( x != ix || y != iy )
		{
			push( animator, ix, iy );
			ix+=sx;
			iy+=sy;
		}
		//
		//	Done
		//
		return true;
	}

	protected boolean trySelectBox( Animator animator, int x, int y )
	{
		int north = puzzle.getSym(x,y-1);
		int south = puzzle.getSym(x,y+1);
		int west = puzzle.getSym(x-1,y);
		int east = puzzle.getSym(x+1,y);
		//
		//	First check if there is a worker in a nighbour cell. If
		//	yes then simplly select the box. If the box is already selected
		//	then do nothing and if othe box is selected then unselect it first
		//	and then select the box.
		//
		if ( Puzzle.hasWorker( west )
			|| Puzzle.hasWorker( east )
			|| Puzzle.hasWorker( north )
			|| Puzzle.hasWorker( south ) )
		{
			if ( !puzzle.isSelected(x,y) )
			{
				if ( puzzle.isSelected() )
					unselect( animator );
				select( animator, x, y );
			}
		}
		//
		//	Otherwise, check which is of the cells is in closes walking 
		//	distance and move worker to that cell, then select.
		//
		else
		{
			int pref_x = -1;
			int pref_y = -1;
			int shortest = Integer.MAX_VALUE;
			if ( Puzzle.isEmpty( north )
					&& shortest > stepsAway( x, y-1) )
			{
				shortest = stepsAway( x, y-1);
				pref_x = x;
				pref_y = y-1;
			}
			if ( Puzzle.isEmpty( south )
					&& shortest > stepsAway( x, y+1) )
			{
				shortest = stepsAway( x, y+1);
				pref_x = x;
				pref_y = y+1;
			}
			if ( Puzzle.isEmpty( west )
					&& shortest > stepsAway( x-1, y) )
			{
				shortest = stepsAway( x-1, y);
				pref_x = x-1;
				pref_y = y;
			}
			if ( Puzzle.isEmpty( east )
					&& shortest > stepsAway( x+1, y) )
			{
				shortest = stepsAway( x+1, y);
				pref_x = x+1;
				pref_y = y;
			}
			//
			//	Move the worker to the direction. If we cannot move worker
			//	next to the box then we cannot select it.
			//
			if ( !puzzle.isValid(pref_x, pref_y)
				|| !tryMoveWorker(animator, pref_x, pref_y) )
				return false;
			//
			//	Select the box.
			//
			select(animator,x,y);
		}
		//
		//	Done
		//
		return true;
	}

	protected void move( Animator animator, int x, int y )
	{
		animator.queue( new Animator.Move(x,y) );
	}

	protected void push( Animator animator, int x, int y )
	{
		animator.queue( new Animator.Push(x,y) );
	}

	protected void select( Animator animator, int x, int y )
	{
		animator.queue( new Animator.Select(x,y) );
	}

	protected void unselect( Animator animator )
	{
		animator.queue( new Animator.Unselect() );
	}

	protected void buzz( Animator animator )
	{
		//animator.queue( new Animator.NoMove(x,y) );
	}

	protected void undoable( Animator animator, int x, int y )
	{
		animator.queue( new Animator.NoMove(x,y) );
	}

	//////////////////////////////////////////////////////////////////////////
	//
	//	Routes to accessible cells from where worker stands.
	//
	
	protected final int getMoves( int x, int y )
	{
		return moves[puzzle.getIndex(x,y)];
	}
	protected void setMoves( int x, int y, int v )
	{
		moves[puzzle.getIndex(x,y)]=v;
	}
	private boolean setMovesIfGreater( int x, int y, int l )
	{
		//
		//	If out of borders then nothing to do.
		//
		if ( y < 0 || y >= puzzle.getRowCount()
			|| x < 0 || x >= puzzle.getColumnCount() )
			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(x,y) > l && puzzle.isEmpty(puzzle.getSym(x,y)))
		{
			setMoves(x,y,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;
		int last = 0;
		//
		//	Set the seed.
		//
		setMoves(puzzle.getWorkerX(),puzzle.getWorkerY(),0);
		setOfCells[last++] = puzzle.getWorkerX();
		setOfCells[last++] = puzzle.getWorkerY();
		//
		//	Now on each loop pop one cell from the list and calculate the
		//	distance of cell around that cell.
		//
		while ( front < last )
		{
			//
			//	Pop the cell.
			//
			int x = setOfCells[front++];
			int y = setOfCells[front++];
			//
			//	Increase the length of cells all around given cell and push
			//	them into the list.
			//
			int l = getMoves(x,y)+1;
			if ( setMovesIfGreater(x-1,y,l) )
			{
				setOfCells[last++] = x-1;
				setOfCells[last++] = y;
			}
			if ( setMovesIfGreater(x+1,y,l) )
			{
				setOfCells[last++] = x+1;
				setOfCells[last++] = y;
			}
			if ( setMovesIfGreater(x,y-1,l) )
			{
				setOfCells[last++] = x;
				setOfCells[last++] = y-1;
			}
			if ( setMovesIfGreater(x,y+1,l) )
			{
				setOfCells[last++] = x;
				setOfCells[last++] = y+1;
			}
		}
	}

	public final int stepsAway( int x, int y )
	{
		return getMoves(x,y);
	}

	public final boolean isAccessible( int x, int y )
	{
		return puzzle.isValid(x, y) && stepsAway(x, y) != Integer.MAX_VALUE;
	}
	
	public int [] getDirections( int x, int y )
	{
		int away = stepsAway(x,y);
		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.
		//
		int [] steps = new int[2*away];
		int i = steps.length;
		steps[--i] = y;
		steps[--i] = x;
		while ( i > 0 )
		{
			x = steps[i];
			y = steps[i+1];
			int j = stepsAway(x,y);
			if ( stepsAway(x-1,y) < j )
			{
				steps[--i] = y;
				steps[--i] = x-1;
			}
			else if ( stepsAway(x+1,y) < j )
			{
				steps[--i] = y;
				steps[--i] = x+1;
			}
			else if ( stepsAway(x,y-1) < j )
			{
				steps[--i] = y-1;
				steps[--i] = x;
			}
			else if ( stepsAway(x,y+1) < j )
			{
				steps[--i] = y+1;
				steps[--i] = x;
			}
		}
		return steps;
	}

	public int [] getPushDirections( int x, int y )
	{
		//
		//	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.
		//
		int selx = puzzle.getSelectedX();
		int sely = puzzle.getSelectedY();
		int xx = x-selx;
		int yy = y-sely;
		int sx = selx-puzzle.getWorkerX();
		int sy = sely-puzzle.getWorkerY();
		double scaliar = (double)(xx)*(double)(sx)
							+(double)(yy)*(double)(sy);
		int len = abs(xx)+abs(yy);
		if ( scaliar != (double)len )
			return null;
		//
		//	Now check that all cell till x,y are free.
		//
		int ix = selx;
		int iy = sely;
		while ( x != ix || y != iy )
		{
			ix+=sx;
			iy+=sy;
			if ( !puzzle.isEmpty(puzzle.getSym(ix,iy)) )
				return null;
		}
		//
		//	Looks we could move the box till x,y. Create the steps array
		//	and fill it with push steps.
		//
		int steps[] = new int [2*len];
		int i = 0;
		ix = selx;
		iy = sely;
		while ( x != ix || y != iy )
		{
			steps[i++] = ix;
			steps[i++] = iy;
			ix+=sx;
			iy+=sy;
		}		
		return steps;
	}
}
