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

import java.util.ListIterator;
import java.util.Vector;

/**
 *
 */
public class PuzzleChoreographer
{
	private Puzzle puzzle = null;
	private PuzzleRouteFinder routeFinder = null;
	private IPuzzleAnimator animator;

	public PuzzleChoreographer()
	{}
	
	public void setPuzzle( Puzzle p )
	{
		puzzle = p;
		routeFinder = new PuzzleRouteFinder(p);
	}

	public boolean createSteps(IPuzzleAnimator _animator, XYPair xy )
	{
		animator = _animator;
		//
		//	Check that the x,y are valid.
		//
		if ( !puzzle.isValid(xy) )
			return false;
		//
		//	Now check what tile was tapped.
		//	If the tapped is a floor then ...
		//
		int tile = puzzle.getSym(xy);
		if ( Puzzle.isEmpty(tile) )
		{
			//
			//	Calculate possible moves map.
			//
			routeFinder.calcMoves();
			//
			//	Check if worker selected a box and can push it to the location?
			//	If yes then we are done.
			//
			if ( puzzle.isSelected() && tryMoveSelectedBox(xy) )
				return true;
			//
			//	Either nothing was selected or the box cannot be moved to
			//	tapped location. Try move worker alone. 
			//
			if ( tryMoveWorker(xy) )
				return true;
			//
			//	Show that action is not allowed.
			//
			animator.moveImpossible(xy);
		}
		//
		//	The tapped is the worker. Try move the box. If not possible
		//	then unselect if selected.
		//
		else if ( Puzzle.hasWorker(tile) )
		{
			if ( !puzzle.isSelected() )
			{
				animator.buzz();
				return true;
			}
			//
			//	Calculate possible moves map.
			//
			routeFinder.calcMoves();
			//
			//	Check if worker selected a box and can push it to the location?
			//	If yes then we are done.
			//
			if ( tryMoveSelectedBox(xy) ) {
				return true;
			}
			//
			//	If the box is not movable then unselect it.
			//
			animator.unselect();
		}
		//
		//	The tapped is a box.
		//
		else if ( Puzzle.hasBox(tile) )
		{
			//
			//	Calculate possible moves map.
			//
			routeFinder.calcMoves();
			//
			//	If the box is selected then unselect it.
			//
			if ( puzzle.isSelected(xy) )
			{
				animator.unselect();
				return true;
			}
			//
			//	Try move the worker next to the box and select it if the
			//	box is not selected yet.
			//
			if ( trySelectBox(xy) ) {
				return true;
			}
			//
			//	Show that action is not allowed if reached till here.
			//
			animator.moveImpossible(xy);
		}
		return true;
	}
	
	//////////////////////////////////////////////////////////////////////////
	//
	//	Routes to accessible cells from where worker stands.
	//
	protected boolean tryMoveWorker(XYPair xy)
	{
		//
		//	If the filed is not accessable then move failed.
		//
		if ( !routeFinder.isAccessible(xy) ) {
			return false;
		}
		//
		//	First unselect box.
		//
		if ( puzzle.isSelected() ) {
			animator.unselect();
		}
		//
		//	Get directions and queue moves accordingly.
		//
		Vector<XYPair> dirs = routeFinder.getDirections(xy);
		ListIterator<XYPair> it = dirs.listIterator();
		while ( it.hasNext() ) {
			animator.move(it.next());
		}
		//
		//	Done.
		//
		return true;
	}


	protected boolean tryMoveSelectedBox(XYPair xy)
	{
		//
		//	If no box is selected then we cannot move no box.
		//
		if ( !puzzle.isSelected() ) {
			return false;
		}
		final XYPair box_xy = puzzle.getSelected();
		//
		//	There is no point to continue if we are asked to move the box to where is it.
		//
		if ( xy.isEqual(box_xy) ) {
			return false;
		}
		//
		//	Check that asked move is orthogonal to the slected box.
		//
		//	TODO: check that all cells are empty on the route.
		//
		if ( xy.x() == box_xy.x() || xy.y() == box_xy.y() ) {
			return tryOrthogonalMoveSelectedBox(xy);
		}
		//
		//	NOT IMPLEMENTED.
		//
		return false;
	}

	protected boolean tryOrthogonalMoveSelectedBox(XYPair xy)
	{
		//
		//	If no box is selected then we cannot move no box.
		//
		if ( !puzzle.isSelected() ) {
			return false;
		}
		final XYPair box_xy = puzzle.getSelected();
		//
		//	There is no point to continue if we are asked to move the box to where is it.
		//
		if ( xy.isEqual(box_xy) ) {
			return false;
		}
		//
		//	Check that asked move is orthogonal to the selected box.
		//
		if ( xy.x() != box_xy.x() && xy.y() != box_xy.y() ) {
			return false;
		}
		//
		//	Now find the desired place for the worker to start push this
		//	box.
		//
		XYPair w = new XYPair(box_xy);
		if ( xy.x() < box_xy.x() )
			w = w.right();
		else if ( xy.x() > box_xy.x() )
			w = w.left();
		else if ( xy.y() < box_xy.y() )
			w = w.up();
		else
			w = w.down();
		//
		//	Check if the desired place for the worker is accessable? If not
		//	then there is no point to continue.
		//
		if ( !routeFinder.isAccessible(w) ) {
			return false;
		}
		//
		//	Now check that all cell till x,y are empty and that we can
		//	push box till there.
		//
		XYPair step_xy = box_xy.sub(w);
		for ( XYPair i = box_xy; !xy.isEqual(i);  )
		{
			i = i.add(step_xy);
			int v = puzzle.getSym(i);
			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 ( !w.isEqual(puzzle.getWorker()) )
		{
			if (!tryMoveWorker(w)){
				return false;
			}
			animator.select(box_xy);
		}
		//
		//	Now create the steps to push the box.
		//
		for ( XYPair i = box_xy; !i.isEqual(xy); i = i.add(step_xy) ) {
			animator.push(i);
		}
		//
		//	Done
		//
		return true;
	}


	protected boolean trySelectBox(XYPair xy)
	{
		int north = puzzle.getSym(xy.up());
		int south = puzzle.getSym(xy.down());
		int west = puzzle.getSym(xy.left());
		int east = puzzle.getSym(xy.right());
		//
		//	First check if there is a worker in a neighboring cell. If
		//	yes then simply select the box. If the box is already selected
		//	then do nothing and if other 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(xy) )
			{
				if ( puzzle.isSelected() )
					animator.unselect();
				animator.select(xy);
			}
		}
		//
		//	Otherwise, check which of the cells is in closes walking
		//	distance and move worker to that cell, then select.
		//
		else
		{
			XYPair pref = new XYPair(-1, -1);
			int shortest = Integer.MAX_VALUE;
			if ( Puzzle.isEmpty( north )
					&& shortest > routeFinder.stepsAway(xy.down()) )
			{
				shortest = routeFinder.stepsAway(xy.down());
				pref.set(xy.down());
			}
			if ( Puzzle.isEmpty( south )
					&& shortest > routeFinder.stepsAway(xy.up()) )
			{
				shortest = routeFinder.stepsAway(xy.up());
				pref.set(xy.up());
			}
			if ( Puzzle.isEmpty( west )
					&& shortest > routeFinder.stepsAway(xy.left()) )
			{
				shortest = routeFinder.stepsAway(xy.left());
				pref.set(xy.left());
			}
			if ( Puzzle.isEmpty( east )
					&& shortest > routeFinder.stepsAway( xy.right()) )
			{
				shortest = routeFinder.stepsAway( xy.right());
				pref.set(xy.right());
			}
			//
			//	Move the worker to the direction. If we cannot move worker
			//	next to the box then we cannot select it.
			//
			if ( !puzzle.isValid(pref) || !tryMoveWorker(pref) ) {
				return false;
			}
			//
			//	Select the box.
			//
			animator.select(xy);
		}
		//
		//	Done
		//
		return true;
	}
}
