package org.artisanlogiciel.games;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Collections;
import java.util.Random;

import java.io.OutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.DataInputStream;

/**
 * Model of labyrinth storing only paths not walls. wall are regenerated later
 * on based on adjacent paths. each position (x,y) stores what move can be done
 * from here to go to deeper path. a node is tagged OPEN and contained in
 * openList if all moves from its position have not been resolved a node is
 * tagged CLOSED when fully processed
 **/
public class LabyModel implements WallsProvider
{

    /**
     * WARNING don't change those values, they are used as it is for
     * optimisation
     */
    private final static short FLAGLENGTH = 7;
    private final static short CLEAR = 0; // mandatory 0 since array creation is
                                          // initialized with 0.
    public final static short HORIZONTAL = 1;
    public final static short VERTICAL = 2;
    public final static short DIRECTION = 4; // could we get rid of that to
                                              // free one bit for other purpose
                                              // ?
    public final static short POSITIVE = 8;
    public final static short NEGATIVE = 16;
    
    private final static short OPEN = 32; // can be reused once generation is
                                          // completed
    private final static short CLOSED = 64; // can be reused once generation is
                                            // completed
    public final static short LEFT = Brick.LEFT << FLAGLENGTH | DIRECTION | HORIZONTAL | NEGATIVE;
    public final static short DOWN = Brick.DOWN << FLAGLENGTH | DIRECTION | VERTICAL | POSITIVE;
    public final static short RIGHT = Brick.RIGHT << FLAGLENGTH | DIRECTION | HORIZONTAL | POSITIVE;
    public final static short UP = Brick.UP << FLAGLENGTH | DIRECTION | VERTICAL | NEGATIVE;
    public final static short ENTRY = Brick.ENTRY << FLAGLENGTH; // flag when a
                                                                  // wall should
                                                                  // be open to
                                                                  // access
                                                                  // this.
    private final static short GOAL = Brick.GOAL << FLAGLENGTH; // flag when a
                                                                // wall should
                                                                // be open to
                                                                // access this.
    public final static short SOLVED = 64 << FLAGLENGTH; // flag when solution
                                                          // is on this path.
    private final static short FREE = 128 << FLAGLENGTH; // free flag
    // remains 2 free bits ( keep one out for sign )

    private static final short[] AllDirections = { LEFT, DOWN, RIGHT, UP };

    private int width;
    private int height;
    // wall flags + status flags for each position (x,y)
    private short[][] t;
    private int depth = 0;
    /**
     * will enforce maxdepth as maximum length of path in depth first. It means
     * that after every maxdepth moves there should be crossing paths. I think
     * that to shorter maxdepth is the harder the labyrinth is ...
     **/
    private int maxdepth = 0;
    private int deepest = 0; // longest path found
    private int linearwork = 0;
    private Position deepestEnd = null;
    boolean maxreached = false;
    java.util.Random random;
    private final LinkedList<Position> openList = new LinkedList<Position>();
    // list of entries and exits.
    private final LinkedList<Position> entryExits = new LinkedList<Position>();
    private final Object coherentLock = new Object(); // before getting the lock
                                                      // and after lock release
                                                      // all is coherent ( ie
                                                      // check() is ok ),

    MazeCreationListener listener = null;


    /**
TODO
    private static MyLinkedList<P>
    {
	MyLinkedList<P> next;
	P content

	MyLinkedList<P>( P object)
	{
	    content=object;
	    next=null;
	}
		
	MyLinkedList<P> addLast(P object)
	{
	    next = new MyLinkedList(object);
	    return next;
	}
    }
    */

    public LabyModel(int width, int height, int maxdepth, Random random)
    {
        this.width = width;
        this.height = height;
        this.maxdepth = maxdepth;
        this.random = random;
        // CLEAR == 0 and array is initialized with 0s
        t = new short[width][height];
    }

    public LabyModel(MazeParams params, Random random)
    {
        this.width = params.getWidth();
        this.height = params.getHeight();
        this.maxdepth = params.getMaxDepth();
        this.random = random;
        // CLEAR == 0 and array is initialized with 0s
        t = new short[width][height];
    }

    public LabyModel(String pFormat, InputStream pIn) throws IOException
    {
        parseInputStream(pFormat, pIn);
    }

    public void setMazeListener(MazeCreationListener listener)
    {
        this.listener = listener;
    }

    // FIXME FULLY BREAK RESOLVING...
    public void noWalls(int x, int y)
    {
	if ((x >= 0) && (x < width) && (y >= 0) && (y < height))
	    {		
		// t[x][y] |= DIRECTION | VERTICAL | POSITIVE | HORIZONTAL | NEGATIVE | LEFT | RIGHT | UP | DOWN;
		t[x][y] |= DIRECTION | VERTICAL | POSITIVE | HORIZONTAL | NEGATIVE;
		t[x][y] |= LEFT | RIGHT | UP | DOWN;
	    }
    }

    /* set direction existing onee are lost */
    // FIXME FULLY BREAK RESOLVING...
    public void setDirection(int x, int y, short path)
    {
	if ((x >= 0) && (x < width) && (y >= 0) && (y < height))
	    {
		t[x][y]= (short) (path | OPEN);
	    }
    }

    /** add a new direction, exiting ones are kept */
    // FIXME FULLY BREAK RESOLVING...
    public void addDirection(int x, int y, short path)
    {
	if ((x >= 0) && (x < width) && (y >= 0) && (y < height))
	    {
		t[x][y]|= (short) (path | OPEN);
	    }
    }

    // entry and exit can be outside the model boundaries a one x or one y out.
    public boolean addEntryOrExit(int x, int y)
    {
        entryExits.add(new Position(x, y));
        if ((x > 0) && (x < width) && (y > 0) && (y < height))
        {
            t[x][y] |= ENTRY;
        }
        return true;
    }

    public void debugOut()
    {
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                out4XY(x, y);
            }
            System.out.print('\n');
        }
    }

    public void out4XY(int x, int y)
    {
        int direction = t[x][y];
        if ((direction & OPEN) == OPEN)
        {
            System.out.print("?");
        }
        else if ((direction & CLOSED) == CLOSED)
        {
            System.out.print(".");
        }
        else
        {
            System.out.print(" ");
        }
        // don't display information about short.
        direction &= ~OPEN;
        direction &= ~GOAL;
        direction &= ~CLOSED;
        switch (direction)
        {
        case LEFT: // left
            System.out.print("<-");
            break;
        case DOWN: // down
            System.out.print("vv");
            break;
        case RIGHT: // right
            System.out.print("->");
            break;
        case UP: // up
            System.out.print("^^");
            break;
        case HORIZONTAL: // -
            System.out.print("--");
            break;
        case VERTICAL: // |
            System.out.print("||");
            break;
        case CLEAR:
            System.out.print("  ");
            break;
        case CLOSED:
            System.out.print("00");
            break;
        case RIGHT | LEFT:
            System.out.print("--");
            break;
        case UP | DOWN:
            System.out.print("||");
            break;
        case RIGHT | UP:
            System.out.print("^>");
            break;
        case RIGHT | DOWN:
            System.out.print("v>");
            break;
        case LEFT | DOWN:
            System.out.print("<v");
            break;
        case LEFT | UP:
            System.out.print("<^");
            break;
        case LEFT | RIGHT | UP:
            System.out.print("LL");
            break;
        case LEFT | RIGHT | DOWN:
            System.out.print("TT");
            break;
        case LEFT | DOWN | UP:
            System.out.print("<|");
            break;
        case RIGHT | DOWN | UP:
            System.out.print("|>");
            break;
        case LEFT | RIGHT | UP | DOWN:
            System.out.print("++");
            break;
        case OPEN:
            System.out.print("??");
            break;
        case GOAL:
            System.out.print("**");
            break;
        default:
            System.out.print("..");
        }

        if (((t[x][y] & RIGHT) == RIGHT) || ((x + 1 < width) && ((t[x + 1][y] & LEFT) == LEFT)))
        {
            System.out.print("-");
        }
        else
        {
            System.out.print("H");
        }

    }

    public String outHorizWall2XY(int x, int y)
    {
        String freeway = " ";
        if ((t[x][y] & SOLVED) == SOLVED)
        {
            freeway = "*";
        }
        if ((x < 0) || (x > width))
        {
            return " H";
        }
        if ((y < 0) || (y >= height))
        {
            return "HH";
        }

        if (((t[x][y] & DOWN) == DOWN) || ((y + 1 < height) && ((t[x][y + 1] & UP) == UP)))
        {
            return freeway + "H";
        }
        else
        {
            return "HH";
        }
    }

    public String out2XY(int x, int y)
    {
        // can check for entry exits.

        if ((y < 0) || (y >= height) || (x < 0) || (x > width))
        {
            return " H";
        }
        String low = "";
        int direction = t[x][y];
        String freeway = " ";
        if ((t[x][y] & SOLVED) == SOLVED)
        {
            freeway = "*";
        }
        // don't display information about short.
        direction &= ~OPEN;
        direction &= ~GOAL;
        direction &= ~CLOSED;
        direction &= ~SOLVED;
        switch (direction)
        {
        case LEFT:
        case DOWN:
        case RIGHT:
        case UP:
            low = freeway;
            break;
        case HORIZONTAL: // -
            low = " ";
            break;
        case VERTICAL: // |
            low = " ";
            break;
        case CLEAR:
            low = " ";
            break;
        case CLOSED:
            low = "0";
            break;
        case RIGHT | LEFT:
        case UP | DOWN:
        case RIGHT | UP:
        case RIGHT | DOWN:
        case LEFT | DOWN:
        case LEFT | UP:
        case LEFT | RIGHT | UP:
        case LEFT | RIGHT | DOWN:
        case LEFT | DOWN | UP:
        case RIGHT | DOWN | UP:
        case LEFT | RIGHT | UP | DOWN:
            low = ".";
            break;
        case OPEN:
            low = "?";
            break;
        case GOAL:
            low = "*";
            break;
        default:
            low = ".";
        }

        for (Position exit : entryExits)
        {
            if ((exit.getX() == x + 1) && (exit.getY() == y))
            {
                low = low + ">";
                return low;
            }
        }

        if (((t[x][y] & RIGHT) == RIGHT) || ((x + 1 < width) && ((t[x + 1][y] & LEFT) == LEFT)))
        {
            low = low + freeway;
        }
        else
        {
            low = low + "H";
        }

        return low;
    }

    public LabyMap toLabyMap()
    {
        synchronized (coherentLock)
        {
            Brick[][] brickMap = new Brick[width][height];
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    brickMap[x][y] = new Brick(out2XY(x, y), outHorizWall2XY(x, y), getWalls(x, y));
                }
            }
            return new LabyMap(brickMap, (LinkedList<Position>) entryExits.clone());
        }
    }

    public int getWidth()
    {
        return this.width;
    }

    public int getHeight()
    {
        return this.height;
    }

    public LinkedList<Position> getOpenList()
    {
        return openList;
    }

    public void reset()
    {
        depth = 0;
        computeOpenList();
	resetResolving();
    }

    public void resetResolving()
    {
	// don't keep solved path
	for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
		t[x][y] &= ~(SOLVED);		
            }
        }

    }

    public void fullReset()
    {
        depth = 0;
        openList.clear();
        maxreached = false;
        deepest = 0;
        deepestEnd = null;
        // clear open
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
		// resetCell(x,y);
		t[x][y] &= ~( OPEN | SOLVED);
		
            }

        }
    }

    /**
     * check model coherency and fix if model needed a fix it means that
     * algorithm has a problem
     * 
     * @return true if model is ok and no fix was applied.
     **/
    public boolean check()
    {
        boolean check = true;
        synchronized (coherentLock)
        {
            // node in OPEN should be tagged OPEN and not CLOSED.
            for (Position p : openList)
            {
                int x = p.getX();
                int y = p.getY();
                if (isFlagSet(t[x][y],OPEN))
                {
                    check = false;
                    t[x][y] |= OPEN;
                }
                if (isFlagSet(t[x][y],CLOSED))
                {
                    check = false;
                    t[x][y] &= ~CLOSED;
                }
            }
            // should do reverse : every node tagged OPEN should be in open
            // list.
            // a little more cpu consuming...
        }
        return check;
    }

    public void computeOpenList()
    {
        openList.clear();
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                if (isFlagSet(t[x][y],CLOSED))
                {
                    t[x][y] &= ~OPEN;
                }
                if (isFlagSet(t[x][y],OPEN) || (t[x][y] == CLEAR))
                {
                    openList.add(new Position(x, y));
                }
            }
        }
    }

    public void generateWithEntry(int x, int y)
    {

        openList.add(new Position(x, y));
        while (!openList.isEmpty())
        {
            step(0); // fixme depth not set

            synchronized (coherentLock)
            {
                linearwork++;

                if (linearwork % maxdepth == 0)
                {
                    coherentLock.notifyAll();
                    if (listener != null)
                    {
                        listener.changed(null, null, this);
                    }
                    // should not continue in depth first...
                    if (!openList.isEmpty())
                    {
                        // insert head as next position to pick up.
                        Position current = openList.removeFirst();
                        openList.add(current);
                    }
                }
            }

        }

        if (deepestEnd != null)
        {
            t[deepestEnd.getX()][deepestEnd.getY()] |= GOAL;
        }
    }

    public int getDepth()
    {
        return depth;
    }

    public Position getDeepestEnd()
    {
        return deepestEnd;
    }

    public int getDeepestPath()
    {
        return deepest;
    }

    public boolean maxReached()
    {
        return maxreached;
    }

    /**
     * @param p
     *            Position
     * @return true if newly added , false if already open.
     **/
    private boolean open(boolean first, Position p)
    {
        int x = p.getX();
        int y = p.getY();
        if ((t[x][y] & OPEN) != OPEN)
        {
            t[x][y] |= OPEN;
            openList.addLast(p);
            return true;
        }
        return false;
    }

    public final static boolean isFlagSet(short check, short flag)
    {
	return ((check & flag) == flag);
    }

    public final short getCell(Position p)
    {
	return t[p.getX()][p.getY()];
    }

    public final void updateCell(Position p, short flags)
    {
	t[p.getX()][p.getY()] |= flags;
    }

    public final static short getReverseDirection(int index)
    {
	return AllDirections[(index + 2) % 4];
    }

    /** return direction to use to be closer to 'to' from 'from' */
    public final static short getDirection(Position from, Position to)
    {
	short pointingdirection = DIRECTION;
	if ( from.equals(to) )
	    {
		return pointingdirection;
	    }
	if ( from.getX() < to.getX() )
	    {
		pointingdirection |= RIGHT;
	    }
	else
	if ( from.getX() > to.getX() )
	    {
		pointingdirection |= LEFT;
	    }
	if ( from.getY() < to.getY() )
	    {
		pointingdirection |= DOWN;
	    }
	else
	if ( from.getY() > to.getY() )
	    {
		pointingdirection |= UP;
	    }
	return pointingdirection;
    }
    
    /**
     * resolve this labrynth using internal representation
     * initial (x,y) is exit, will return list of positions from start (0,0) to end (x,y)
     **/
    public LinkedList<DirectionPosition> resolve(int x, int y, MazeResolutionListener rlistener)
    {
	long safeguard = width * height;
        int newx = 0;
        int newy = 0;

	resetResolving();

	// list of alternate paths
        LinkedList<LinkedList<DirectionPosition>> altpath = new LinkedList<>();

	// list of positions from start to end
        LinkedList<DirectionPosition> backpath = new LinkedList<DirectionPosition>();
	// position that point to (x,y).
	DirectionPosition found = new DirectionPosition((short) 0,new Position(x, y));
	// entry
	Position entry = new Position(0, 0);

        while (! found.getPosition().equals(entry) )
        {
	    Position last = found.getPosition();
            backpath.addFirst(found);
	    found = null;

            // should find from all adjacent cells (directions) one that point to this.	    
	    {
		// didx is index of four cell adjacent to this (x,y)
		for (int didx = 0; didx < 4; didx++)
		    {
			int delta = 0;
			short direction = AllDirections[didx];
			short reversedirection = getReverseDirection(didx);
			short pointingdirection = DIRECTION;

			if (isFlagSet(direction,POSITIVE))
			    {
				delta = 1;
				pointingdirection |= NEGATIVE;
			    }
			else
			    {
				delta = -1;
				pointingdirection |= POSITIVE;
			    }

			if (isFlagSet(direction,HORIZONTAL))
			    {
				newx = last.getX() + delta;
				newy = last.getY();
				pointingdirection |= HORIZONTAL;
			    }
			else
			    {
				newy = last.getY() + delta;
				newx = last.getX();
				pointingdirection |= VERTICAL;
			    }

			// internal GUARD.
			if (! isFlagSet(reversedirection,pointingdirection))
			    {
				System.out.println("Internal ERROR. Please check AllDirections order "
						   + (reversedirection & pointingdirection) + " " + pointingdirection);
				return backpath;
			    }

			Position p = new Position(newx,newy);
			if ((newx >= 0) && (newy >= 0) && (newx < width) && (newy < height))
			    {
				if (isFlagSet(getCell(p),reversedirection))
				    {
					if (found != null)
					    {
						// there is already a potential solution in adjacent cell of last.
						System.out.println("alternate " + p + " from " + last + "/" + safeguard);
						// could be a unique parent of two paths...
						// but search from other entry/exits than generated
						// from
						// ie entry(0,0) exit(width-1,height-1) not yet
						// implemented.
						{						    
						    if (! isFlagSet(getCell(p), SOLVED ))
							{
							    LinkedList<DirectionPosition> cp = (LinkedList<DirectionPosition>) backpath.clone();
							    DirectionPosition altfound = new DirectionPosition(reversedirection,p);
							    cp.addFirst(altfound);
							    altpath.addLast(cp);
							    rlistener.notifySearchError("record alternate path " + p.toString());
							}
						    else
							{
							    // this was already solved, might be a loop.
							    if (rlistener != null)
								{
								    rlistener.notifySearchError("Loop " + last.toString() + " has two parents " + found.toString() + " "
												+ p.toString());
								}
							    // continue;
							}
						}
					    }
					else
					    {
						if (! isFlagSet(getCell(p), SOLVED ))
						    {
							// this is first potential solution in adjacent cell of last.
							System.out.println("check " + p + " from " + last + "/" + safeguard);
							
							found = new DirectionPosition(reversedirection,p);
							if (rlistener != null)
							    {
								rlistener.notifySearch(found);
							    }
						    }
						else
						    {
							// was already solved.
							System.out.println("already solved " + p + " from " + last + "/" + safeguard);
						    }
					    }
					// support multiple pathes
				    }
				else
				    {
					System.out.println("not reachable " + p  + " from " + last + "/" + safeguard);
				    }
			    }
			else
			    {
				System.out.println("p outofbounds " + p + "/" + safeguard);
			    }
		    }
		if (found == null)
		    {
			if ( ! altpath.isEmpty() )
			    {
				// new possible backpath
				backpath = altpath.removeFirst();
				found = backpath.removeFirst();
				if (rlistener != null)
				    {
					rlistener.notifySearchError("try alternate path " + found.toString());
					rlistener.notifySearch(found);
				    }
			    }
		    }
	    }
	    if (found == null)
		{
		    if ( ! altpath.isEmpty() )
			{
			    rlistener.notifySearchError("No path found BUT ALTPATH !" );
			}
		    rlistener.notifySearchError("No path found !");
		    break;
		}
            // System.out.println(found);
	    if (isFlagSet(getCell(found.getPosition()), SOLVED ))
		{
		    System.out.println("[INFO] position already solved" +  found.toString() + " *length:" + backpath.size());
		}
	    else
		{
		    updateCell(found.getPosition(),SOLVED);
		}

	    
	    safeguard --;
	    if ( safeguard < 0 )
		{
		    rlistener.notifySearchError("Path too long ( or long overflow ) for width*height:" + (width*height) + " length:" + backpath.size());
		    break;
		}
        }
        if (rlistener != null)
        {
            rlistener.notifyCompletion(backpath);
        }
        return backpath;
    }

    /**
     * @returns wheter process closed current node.
     **/
    public boolean step(int depth)
    {
        boolean complete = false;
        Position current = null;

        synchronized (coherentLock)
        {

            if (!openList.isEmpty())
            {
                // last : in depth before.
                current = openList.getLast();
            }
            else
            {
                return true;
            }

            if (current != null)
            {
                int x = current.getX();
                int y = current.getY();

                // should find all free positions...
                ArrayList<Short> freeDirection = new ArrayList<Short>();

                for (short direction : AllDirections)
                {
                    int delta = 0;
                    int newx = -1;
                    int newy = -1;

                    if ((direction & POSITIVE) == POSITIVE)
                    {
                        delta = 1;
                    }
                    else
                    {
                        delta = -1;
                    }

                    if ((direction & HORIZONTAL) == HORIZONTAL)
                    {
                        newx = x + delta;
                        newy = y;
                    }
                    else
                    {
                        newy = y + delta;
                        newx = x;
                    }

                    if ((newx >= 0) && (newy >= 0) && (newx < width) && (newy < height))
                    {
                        if ((t[newx][newy]) == CLEAR)
                        {
                            freeDirection.add(new Short(direction));
                        }
                    }
                }

                if (!freeDirection.isEmpty())
                {
                    // control random using our own pseudorandom
                    short direction = 0;
                    if (freeDirection.size() > 1)
                    {
                        direction = freeDirection.get(random.nextInt(freeDirection.size()));
                    }
                    else
                    {
                        direction = freeDirection.get(0);
                        Position last = openList.removeLast();
                        if (last != current)
                        {
                            // GUARD, should not happen ( or multi-thread access
                            // to model error )
                            System.err.println("INTERNAL ERROR 3");
                            return false;
                        }
                    }

                    int delta = 0;
                    int newx = -1;
                    int newy = -1;

                    if ((direction & POSITIVE) == POSITIVE)
                    {
                        delta = 1;
                    }
                    else
                    {
                        delta = -1;
                    }

                    if ((direction & HORIZONTAL) == HORIZONTAL)
                    {
                        newx = x + delta;
                        newy = y;
                    }
                    else
                    {
                        newy = y + delta;
                        newx = x;
                    }

                    Position target = new Position(newx, newy, current.getDepth() + 1);
                    open(false, target);
                    if ((t[x][y] & DIRECTION) == DIRECTION)
                    {
                        t[x][y] |= direction;
                    }
                    else
                    {
                        t[x][y] = direction;
                    } // not a 'direction' ... is it necessary to check ?
                    if (freeDirection.size() > 1)
                    {
                        // keep it open at the very same place, previous open
                        // did add another node to inspect.
                        return false;
                    }
                    // else this was the unique direction , object already
                    // removed ( since if we try to remove it now it is not the
                    // last )

                    // can proceed to close
                }
                else
                {
                    // no free direction remaining => closing
                    Position last = openList.removeLast();
                    if (last != current)
                    {
                        // GUARD, should not happen.
                        System.err.println("INTERNAL ERROR 3");
                        return false;
                    }
                }
                complete = true;
                t[x][y] &= ~OPEN;
                t[x][y] |= CLOSED;
                if (current.getDepth() > deepest)
                {
                    deepest = depth;
                    deepestEnd = current;
                }
            } // current != null;
        } // synchronized
        return complete;
    }

    public short getWalls(int x, int y)
    {
        short walls = 0;
        for (short direction : AllDirections)
        {
            if (hasWallInDirection(x, y, direction))
            {
                walls |= (direction >> FLAGLENGTH);
            }
        }
        return walls;
    }

    public short getPath(int x, int y)
    {
        short path = 0;
	if (( x < width ) && ( y < height ))
	    {
		path = t[x][y];
	    }
	return path;
    }

    /**
     * is there a wall in that direction ?
     **/
    public boolean hasWallInDirection(int x, int y, short direction)
    {
        int newx = 0;
        int newy = 0;
        int delta = 0;
        // best way to find reversedirection ?
        int reversedirection = 0;

        // is this direction on the path ? yes => no wall
        if ((t[x][y] & direction) == direction)
        {
            return false;
        }

        // is adjacent tile in direction pointing in reverse direction ? yes =>
        // no wall
        if ((direction & POSITIVE) == POSITIVE)
        {
            delta = 1;
        }
        else
        {
            delta = -1;
        }

        if ((direction & HORIZONTAL) == HORIZONTAL)
        {
            newx = x + delta;
            newy = y;
            reversedirection = (direction == RIGHT) ? LEFT : RIGHT;
        }
        else
        {
            newy = y + delta;
            newx = x;
            reversedirection = (direction == UP) ? DOWN : UP;
        }

        if ((newx >= 0) && (newy >= 0) && (newx < width) && (newy < height))
        {
            return !isFlagSet(t[newx][newy],(short)reversedirection);
        }
        else
        {
            // outside boundaries.
            // TODO CHECK exits.
            return true;
        }
    }

    public void streamOut(String pFormat, OutputStream pOut) throws IOException
    {
        if ((pFormat == null) || (pFormat.equals("raw")))
        {
            // first raw format, not smart.
            DataOutputStream dataOut = new DataOutputStream(pOut);
            // should dump this to stream.
            dataOut.write(new byte[] { (byte) 'L', (byte) 'A', (byte) 'B', (byte) '0' });
            dataOut.writeInt(getWidth());
            dataOut.writeInt(getHeight());
            dataOut.flush();
            for (int y = 0; y < getHeight(); y++)
            {
                for (int x = 0; x < getWidth(); x++)
                {
                    dataOut.writeShort(t[x][y]);
                }
            }
            dataOut.flush();
        }
        else
        {
            throw new IOException("Format " + pFormat + " Not yet implemented.");
        }
    }

    private void streamIn(String pFormat, InputStream pIn) throws IOException
    {
        throw new IOException("Use correct constructor.");
    }

    private void parseInputStream(String pFormat, InputStream pIn) throws IOException
    {
        if ((pFormat == null) || (pFormat.equals("raw")))
        {
            byte[] header = new byte[4];
            DataInputStream in = new DataInputStream(pIn);
            in.read(header);
            int rwidth = in.readInt();
            int rheight = in.readInt();
            width = rwidth;
            height = rheight;
            maxdepth = maxdepth;
            random = null;
            // SHOULD CHECK max width and max height...
            // CLEAR == 0 and array is initialized with 0s
            t = new short[width][height];
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    t[x][y] = in.readShort();
                }
            }
            // should be at end of stream ? Not necessary can stream multiple
            // labs ( or tiling ).
        }
        else
        {
            throw new IOException("Format " + pFormat + " Not yet implemented.");
        }

    }

}
