package org.vostan.banvor.model;

//import java.lang.Exception;

import java.util.LinkedList;

public class Puzzle
{
	public final static int FLOOR = 0x0;
	public final static int GOAL = 0x1;
	public final static int FLOOR_MASK = ~(GOAL | FLOOR);
	public final static int WALL = 0x2;
	public final static int BOX = 0x4;
	public final static int BINGO = BOX | GOAL;
	public final static int WORKER = 0x8;
	public final static int WORKER_FACE_DOWN = 0x00 | WORKER;
	public final static int WORKER_FACE_UP = 0x10 | WORKER;
	public final static int WORKER_FACE_LEFT = 0x20 | WORKER;
	public final static int WORKER_FACE_RIGHT = 0x30 | WORKER;
	public final static int WORKER_MASK = WORKER_FACE_RIGHT | WORKER_FACE_LEFT | WORKER_FACE_UP | WORKER_FACE_DOWN;
	public final static int SELECTED = 0x40;
	public final static int MAX_COUNT = 0x80-1;

	private final static XYPair UNSELECTED = new XYPair(-1,-1);


	private int [] board;
	private XYPair board_size = new XYPair(0,0);
	private int bingos = 0;
	private int box_count = 0;
	private XYPair worker = new XYPair(0,0);
	private XYPair selected = new XYPair(-1,-1);

	public Puzzle(){
	}
	public Puzzle(int cols, int rows, int [] b)
	{
		board_size.set(cols, rows);
		box_count = 0;
		bingos = 0;
//		board = new int[board_size.x()*board_size.y()];
		board = b;
		for( int i = 0; i < board.length; ++i ){
			if ((board[i] & BOX) == BOX){
				++box_count;
			}
			if ((board[i] & BINGO) == BINGO) {
				++bingos;
			}
			if ((board[i] & WORKER) == WORKER){
				worker = getXY(i);
			}
		}
	}
	
	//////////////////////////////////////////////////////////////////////////
	//
	//	Low level helper functions.
	//
	
	public final int getColumnCount()
	{
		return board_size.x();
	}
	public final int getRowCount()
	{
		return board_size.y();
	}
	public static int getSymCount()
	{
		return MAX_COUNT;
	}

	public final int getIndex( final XYPair c ) {
		return c.y()*board_size.x()+c.x();
	}
	public final XYPair getXY( int idx ) {
		return new XYPair(idx % board_size.x(), idx / board_size.x());
	}

	public final int getSym( XYPair c ) {
		return board[getIndex(c)];
	}
	public void setSym( final XYPair xy, int v )
	{
		board[getIndex(xy)]=v;
	}

	public static boolean isEmpty( int v )
	{
		return (v & FLOOR_MASK) == 0;
	}
	public static boolean hasBox( int v )
	{
		return (v & BOX) != 0;
	}
	public static boolean hasWorker( int v )
	{
		return (v & WORKER) != 0;
	}

	public final XYPair getWorker() {
		return new XYPair(worker);
	}

	public final boolean isSelected()
	{
		return !selected.isEqual(UNSELECTED);
	}
	public final boolean isSelected( final XYPair s ) {
		return selected.isEqual(s);
	}
	public final XYPair getSelected()
	{
		return new XYPair(selected);
	}

	public final boolean isValid( final XYPair xy ) {
		return xy.isInside(XYPair.ZERO,board_size);
	}

	public final boolean isOneStep( final XYPair xy ) {
		return isValid(xy) && XYPair.sub(worker, xy).l1_norm() == 1;
	}

	public final boolean isDone()
	{
		return bingos == box_count;
	}

	//////////////////////////////////////////////////////////////////////////
	//
	//	Move worker.
	//

	private final int worker_direction(XYPair xy) {
		//
		//	Find direction to move.
		//
		int w;
		if (worker.left().isEqual(xy)) {
			w = WORKER_FACE_LEFT;
		}
		else if (worker.right().isEqual(xy)) {
				w = WORKER_FACE_RIGHT;
		}
		else if (worker.up().isEqual(xy)) {
			w = WORKER_FACE_UP;
		}
		else {
			w = WORKER_FACE_DOWN;
		}
		return w;
	}

	public boolean walk(XYPair xy)
	{
		//
		//	Check that this is one step away.
		//
		if ( !isOneStep(xy) )
			return false;
		//
		//	Check that this is empty space.
		//
		int v = getSym(xy);
		if ( !isEmpty(v) )
			return false;
		//
		//	If something is selected then unselect first.
		//
		if ( isSelected() )
			unselect();
		//
		//	Move worker marker from current position to asked position.
		//
		setSym(worker,getSym(worker)&~WORKER_MASK);
		worker.set(xy);
		setSym(worker,getSym(worker)|worker_direction(xy));
		return true;
	}

	public boolean select(XYPair xy)
	{
		//
		//	Check that this is one step away.
		//
		if ( !isOneStep(xy) )
			return false;
		//
		//	Check that this is empty space.
		//
		int v = getSym(xy);
		if ( !hasBox(v) )
			return false;
		//
		//	If something is selected then unselect first.
		//
		if ( isSelected() )
			unselect();
		//
		//	Move worker marker from current position to asked position.
		//
		selected.set(xy);
		setSym(worker,getSym(worker)&~WORKER_MASK|SELECTED|worker_direction(xy));
		setSym(selected,getSym(selected)|SELECTED);
		return true;
	}

	public boolean unselect()
	{
		//
		//	If not selected then do nothing.
		//
		if ( !isSelected() )
			return false;
		//
		//	Move worker marker from current position to asked position.
		//
		setSym(worker,getSym(worker)&~SELECTED);
		setSym(selected,getSym(selected)&~SELECTED);
		selected.set(UNSELECTED);
		return true;
	}

	public boolean push(final XYPair xy)
	{
		//
		//	If not selected then do nothing.
		//
		if ( !isSelected() )
			return false;
		//
		//	Check that we go to the selected direction.
		//
		if ( !selected.isEqual(xy) )
			return false;
		//
		//	Check that this is a box that we move.
		//
		int v = getSym(xy);
		if ( !hasBox(v) )
			return false;
		//
		//	Check that the next space to the box is empty.
		//w - 2*(w-x) = 2x -w
		XYPair next = xy.mul(2).sub(worker);
		int next_v = getSym(next);
		if ( !isEmpty(next_v) )
			return false;
		//
		//	Ok, looks we can move the box. Do it actually.
		//
		unselect();
		setSym(xy,getSym(xy)&~BOX);
		setSym(next,getSym(next)|BOX);
		walk(xy);
		select(next);
		//
		//	Keep track of box count in place.
		//
		if ( (v & GOAL) != 0 )
			--bingos;
		if ( (next_v & GOAL) != 0 )
			++bingos;
		return true;
	}

	//////////////////////////////////////////////////////////////////////////
	//
	//	Undo/Redo.
	//

	protected class State
	{
		public int [] board;
		public XYPair worker = new XYPair(0,0);
		public XYPair selected = new XYPair(0, 0);
		public int bingos = 0;

		public State(Puzzle puzzle)
		{
			board = puzzle.board.clone();
			worker.set(puzzle.worker);
			selected.set(puzzle.selected);
			bingos = puzzle.bingos;
		}

		public void restore(Puzzle puzzle)
		{
			puzzle.board = board;
			puzzle.worker.set(worker);
			puzzle.selected.set(selected);
			puzzle.bingos = bingos;
		}
	}
	protected LinkedList<State> undoStack = new LinkedList<State>();

	//
	//	Save this state to be able to restore later.
	//
	public void save()
	{
		State s = new State(this);
		undoStack.addFirst(s);
	}
	//
	//	Restore state.
	//
	public void restore()
	{
		State s = undoStack.removeFirst();
		if ( s != null )
			s.restore(this);
	}
	//
	//	Check if there are items in the undo stack.
	//
	public final boolean isUndoable()
	{
		return undoStack.size() != 0;
	}
}
