package org.vostan.sokoban;

//import java.lang.Exception;
import java.io.*;
import static java.lang.Math.*;
import android.util.Log;

import java.util.LinkedList;

import static org.vostan.sokoban.App.TAG;

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_NORTH = 0x00 | WORKER;
	public final static int WORKER_SOUTH = 0x10 | WORKER;
	public final static int WORKER_WEST = 0x20 | WORKER;
	public final static int WORKER_EAST = 0x30 | WORKER;
	public final static int WORKER_MASK = WORKER_EAST | WORKER_WEST | WORKER_SOUTH | WORKER_NORTH;
	public final static int SELECTED = 0x40;
	public final static int MAX_COUNT = 0x80-1;

	protected int columns;
	protected int rows;
	protected int box_count;

	protected int [] board;
	protected int worker_x;
	protected int worker_y;
	protected int selected_x;
	protected int selected_y;
	protected int bingos;

	public Puzzle( InputStream is )
	{
		load( is );
	}
	
	//////////////////////////////////////////////////////////////////////////
	//
	//	Low level helper functions.
	//
	
	public final int getColumnCount()
	{
		return columns;
	}
	public final int getRowCount()
	{
		return rows;
	}
	public static int getSymCount()
	{
		return MAX_COUNT;
	}
	
	public final int getIndex( int x, int y )
	{
		return y*columns+x;
	}
	public final int getX( int idx )
	{
		return idx % columns;
	}
	public final int getY( int idx )
	{
		return idx / columns;
	}

	public final int getSym( int x, int y )
	{
		return board[getIndex(x,y)];
	}
	public void setSym( int x, int y, int v )
	{
		board[getIndex(x,y)]=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 int getWorkerX()
	{
		return worker_x;
	}

	public final int getWorkerY()
	{
		return worker_y;
	}

	public final boolean isSelected()
	{
		return selected_x != -1;
	}
	
	public final boolean isSelected( int x, int y )
	{
		return selected_x == x && selected_y == y;
	}

	public final int getSelectedX()
	{
		return selected_x;
	}

	public final int getSelectedY()
	{
		return selected_y;
	}

	public final boolean isValid( int x, int y )
	{
		return ( 0 <= x && 0 <= y
			&& x < getColumnCount()
			&& y < getRowCount() );
	}

	public final boolean isOneStep( int x, int y )
	{
		return isValid(x,y) 
				&& (abs(worker_x-x )+abs(worker_y-y) == 1);
	}

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

	//////////////////////////////////////////////////////////////////////////
	//
	//	Loading.
	//
	private void load( InputStream is )
	{try{
		columns = is.read();
		rows = is.read();
		box_count = 0;
		bingos = 0;
		selected_x = -1;
		selected_y = -1;
		board = new int[columns*rows];
		byte [] b = new byte [columns*rows];
		is.read( b, 0, columns*rows);
		for( int i = 0; i < b.length; ++i )
		{
			switch( b[i] )
			{
				case 0:
					board[i] = FLOOR;
					break;
				case 1:
					board[i] = WALL;
					break;
				case 2:
					board[i] = BOX;
					++box_count;
					break;
				case 3:
					board[i] = GOAL;
					break;
				case 4:
					board[i] = BINGO;
					++box_count;
					++bingos;
					break;
				case 5:
					board[i] = WORKER;
					worker_x = getX(i);
					worker_y = getY(i);
					break;
				default:
			}
		}
	}
	catch ( Exception e )
	{
		Log.d( TAG, "load()", e);
	}}

	//////////////////////////////////////////////////////////////////////////
	//
	//	Move worker.
	//
	
	public boolean walk( int x, int y)
	{
		//
		//	Check that this is one step away.
		//
		if ( !isOneStep(x,y) )
			return false;
		//
		//	Check that this is empty space.
		//
		int v = getSym(x,y);
		if ( !isEmpty(v) )
			return false;
		//
		//	If something is selected then unselect first.
		//
		if ( isSelected() )
			unselect();
		//
		//	Find direction to move.
		//
		int worker;
		if ( worker_x < x )
			worker = WORKER_WEST;
		else if ( worker_x > x )
			worker = WORKER_EAST;
		else if ( worker_y > y )
			worker = WORKER_SOUTH;
		else
			worker = WORKER_NORTH;
		//
		//	Move worker marker from current position to asked position.
		//
		setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK);
		worker_x =x;
		worker_y =y;
		setSym(worker_x,worker_y,getSym(worker_x,worker_y)|worker);
		return true;
	}

	public boolean select( int x, int y)
	{
		//
		//	Check that this is one step away.
		//
		if ( !isOneStep(x,y) )
			return false;
		//
		//	Check that this is empty space.
		//
		int v = getSym(x,y);
		if ( !hasBox(v) )
			return false;
		//
		//	If something is selected then unselect first.
		//
		if ( isSelected() )
			unselect();
		//
		//	Find direction to move.
		//
		int worker;
		if ( worker_x < x )
			worker = WORKER_WEST;
		else if ( worker_x > x )
			worker = WORKER_EAST;
		else if ( worker_y > y )
			worker = WORKER_SOUTH;
		else
			worker = WORKER_NORTH;
		//
		//	Move worker marker from current position to asked position.
		//
		selected_x =x;
		selected_y =y;
		setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK|worker|SELECTED);
		setSym(selected_x,selected_y,getSym(selected_x,selected_y)|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_x,worker_y,getSym(worker_x,worker_y)&~SELECTED);
		setSym(selected_x,selected_y,getSym(selected_x,selected_y)&~SELECTED);
		selected_x =-1;
		selected_y =-1;
		return true;
	}

	public boolean push(int x, int y)
	{
		//
		//	If not selected then do nothing.
		//
		if ( !isSelected() )
			return false;
		//
		//	Check that we go to the selected direction.
		//
		if ( selected_x != x && selected_y != y )
			return false;
		//
		//	Check that this is a box that we move.
		//
		int v = getSym(x,y);
		if ( !hasBox(v) )
			return false;
		//
		//	Check that the next space to the box is empty.
		//w - 2*(w-x) = 2x -w
		int next_x = 2*x - worker_x;
		int next_y = 2*y - worker_y;
		int next_v = getSym(next_x,next_y);
		if ( !isEmpty(next_v) )
			return false;
		//
		//	Ok, looks we can move the box. Do it actually.
		//
		unselect();
		setSym(x,y,getSym(x,y)&~BOX);
		setSym(next_x,next_y,getSym(next_x,next_y)|BOX);
		walk(x,y);
		select(next_x,next_y);
		//
		//	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 int worker_x;
		public int worker_y;
		public int selected_x;
		public int selected_y;
		public int bingos;
		
		public State(Puzzle puzzle)
		{
			board = puzzle.board.clone();
			worker_x = puzzle.worker_x;
			worker_y = puzzle.worker_y;
			selected_x = puzzle.selected_x;
			selected_y = puzzle.selected_y;
			bingos = puzzle.bingos;
		}
		
		public void restore(Puzzle puzzle)
		{
			puzzle.board = board;
			puzzle.worker_x = worker_x;
			puzzle.worker_y = worker_y;
			puzzle.selected_x = selected_x;
			puzzle.selected_y = selected_y;
			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; 
	}
}
