import java.awt.*;
import java.awt.image.ImageObserver;
import java.io.*;
import java.util.Vector;

public class Level {
	public static final int ELEMENT_SIZE = 16;
	public static final int ELEMENT_SHIFT = 4;	
	
	public static final int GRID_WIDTH = 16;
	public static final int GRID_HEIGHT = 15;

	private final boolean[][] grid = new boolean[ GRID_HEIGHT ][ GRID_WIDTH ];
	private final byte[][]  smells = new byte[ GRID_HEIGHT ][ GRID_WIDTH ];

	private Vector jewels = new Vector();	
	private Vector dogs   = new Vector();

	private Sprite doorSprite = null;
	private Sprite exitSprite = null;

	public boolean isSpace( int x, int y ) {
		x >>= ELEMENT_SHIFT;
		y >>= ELEMENT_SHIFT;
		
		if ( x < 0 || x >= GRID_WIDTH )
			return false;
		if ( y < 0 || y >= GRID_HEIGHT )
			return false;

		return grid[ y ][ x ];
	}

	/** Returns the value of the jewel the sprite has collided with.
     *  Removes the jewel from the level if a collision has occured.
	 **/
	public int checkJewelCollisions( Sprite sprite ) {
		for ( int i = jewels.size()-1; i >= 0; --i ) {
			Sprite s = (Sprite)jewels.elementAt( i );
			if ( sprite.onTop( s ) ) {
				jewels.removeElementAt( i );
				return 1;
			}
		}	
		return 0;
	}

	public boolean checkDogCollisions( Sprite sprite ) {
		calcSmells( sprite );
		for ( int i = dogs.size()-1; i >= 0; --i ) {
			Sprite s = (Sprite)dogs.elementAt( i );
			if ( sprite.isColliding( s ) ) {
				return true;
			}
		}	
		return false;
	}

	public void calcSmells( Sprite sprite ) {
		int x = sprite.getX();
		int y = sprite.getY();
		x >>= ELEMENT_SHIFT;
		y >>= ELEMENT_SHIFT;
		
		for ( int i = 0; i < GRID_HEIGHT; i++ ) {
			for ( int j = 0; j < GRID_WIDTH; j++ ) {
				smells[ i ][ j ] = -1;
			}
		}
		
		smells[ y ][ x ] = 0;

		for ( byte n = 1; n <= 7; n++ ) {
			
			for ( int i = 0; i < GRID_HEIGHT; i++ ) {
				for ( int j = 0; j < GRID_WIDTH; j++ ) {
					if ( grid[ i ][ j ] && smells[ i ][ j ] == -1 ) {
						if ( near( j, i, n-1 ) )
							smells[ i ][ j ] = n;
					}
				}
			}
		}	

	}

	private boolean near( int i, int j, int n ) {
		if ( i != 0 && smells[ j ][ i-1 ] == n )
			return true;
		if ( i != GRID_WIDTH-1 && smells[ j ][ i+1 ] == n )
			return true;
		if ( j != 0 && smells[ j-1 ][ i ] == n )
			return true;
		if ( j != GRID_HEIGHT-1 && smells[ j+1 ][ i ] == n )
			return true;
		return false;
	}

	/** Returns direction which is nearest to the cat via smell. **/
	public int sniff( int x, int y ) {
		x >>= ELEMENT_SHIFT;
		y >>= ELEMENT_SHIFT;

		int l = 255, r = 255, u = 255, d = 255;

		if ( x != 0 )
			l = smells[ y ][ x-1 ];
		if ( x != GRID_WIDTH-1 )
			r = smells[ y ][ x+1 ];
		if ( y != 0 )
			u = smells[ y-1 ][ x ];
		if ( y != GRID_HEIGHT-1 )
			d = smells[ y+1 ][ x ];
		
		if ( l == -1 ) l = 255;
		if ( r == -1 ) r = 255;
		if ( u == -1 ) u = 255;
		if ( d == -1 ) d = 255;

		int closest = Math.min( l, r );
		closest = Math.min( u, Math.min( d, closest ) );
		
		if ( closest == 255 ) return -1;

		if ( l == closest ) 
			return Sprite.LEFT;
		if ( r == closest ) 
			return Sprite.RIGHT;
		if ( u == closest )
			return Sprite.UP;
		
		return Sprite.DOWN;
	}

	public boolean levelDone( Sprite sprite ) {
		return jewels.size() == 0 && sprite.onTop( exitSprite );
	}

	public void tick() {
		for ( int i = dogs.size()-1; i >= 0; --i ) {
			Sprite s = (Sprite)dogs.elementAt( i );
			s.tick( this );
		}
	}

	public void paintStatic( Graphics g, ImageObserver o ) {
		Image floorImage = SpriteImages.get().getFloorImage();
		Image wallImage  = SpriteImages.get().getWallImage();

		for ( int i = 0; i < GRID_HEIGHT; i++ ) {
			boolean[] row = grid[ i ];
			for ( int j = 0; j < GRID_WIDTH; j++ ) {
				if ( !row[ j ] )
					g.drawImage( wallImage, j << ELEMENT_SHIFT, i << ELEMENT_SHIFT, o );
 				else
					g.drawImage( floorImage, j << ELEMENT_SHIFT, i << ELEMENT_SHIFT, o ); 
			}
		}
	}

	public void paint( Graphics g, ImageObserver o ) {

		/*for ( int i = 0; i < GRID_HEIGHT; i++ ) {
			byte[] row = smells[ i ];
			for ( int j = 0; j < GRID_WIDTH; j++ ) {
				byte b = row[ j ];
				float f = b/10.0f;
				if ( b == -1 ) f = 0.0f;
				else f = 1.0f - f;
				f = Math.min( f, 1.0f );
				f = Math.max( f, 0.0f );
				g.setColor( new Color( f, f, f ) );
				g.fillRect( j << ELEMENT_SHIFT, i << ELEMENT_SHIFT, ELEMENT_SIZE, ELEMENT_SIZE );
			}
		}*/


		for ( int i = jewels.size()-1; i >= 0; --i ) {
			Sprite s = (Sprite)jewels.elementAt( i );
			s.paint( g, o );
		}
		if ( jewels.size() != 0 )
			doorSprite.paint( g, o );
		else
			exitSprite.paint( g, o );

		for ( int i = dogs.size()-1; i >= 0; --i ) {
			Sprite s = (Sprite)dogs.elementAt( i );
			s.paint( g, o );
		}

	}

	public void load( Reader r, Sprite sprite ) throws IOException {
		jewels.removeAllElements();
		dogs.removeAllElements();
		doorSprite = null;

		StreamTokenizer st = new StreamTokenizer( r );
		st.ordinaryChar( '_' );
		st.ordinaryChar( '*' );
		st.ordinaryChar( '%' );
		st.ordinaryChar( '@' );
		st.ordinaryChar( '<' );
		st.ordinaryChar( '>' );
		st.ordinaryChar( '^' );
		st.ordinaryChar( 'v' );

		Image jewel = SpriteImages.get().getJewelImage();
		Image door = SpriteImages.get().getDoorImage();
		Image exit = SpriteImages.get().getExitImage();
		Image dog  = SpriteImages.get().getDogImage();

		for ( int i = 0; i < GRID_HEIGHT; i++ ) {
			for ( int j = 0; j < GRID_WIDTH; j++ ) {
				int ttype = st.nextToken();
				switch( ttype ) {
					case '*': grid[ i ][ j ] = false; break;
					case '_': grid[ i ][ j ] = true;  break;
					
					case '%': grid[ i ][ j ] = true;
					jewels.addElement( 
						new Sprite( jewel,
								   j << Level.ELEMENT_SHIFT,
								   i << Level.ELEMENT_SHIFT ) );
					break;
					
					case '@': grid[ i ][ j ] = true;
					sprite.setPosition( j << Level.ELEMENT_SHIFT,
								   	  i << Level.ELEMENT_SHIFT );
					doorSprite = 
						new Sprite( door,
								   j << Level.ELEMENT_SHIFT,
								   i << Level.ELEMENT_SHIFT );
					exitSprite = 
						new Sprite( exit,
								   j << Level.ELEMENT_SHIFT,
								   i << Level.ELEMENT_SHIFT );
					break;										
					
					case '<': grid[ i ][ j ] = true;
						dogs.addElement( 
							new DogSprite( Sprite.LEFT, dog,
										j << Level.ELEMENT_SHIFT,
								    		i << Level.ELEMENT_SHIFT ) );
					break;
					
					case '>': grid[ i ][ j ] = true;
						dogs.addElement( 
							new DogSprite( Sprite.RIGHT, dog,
										j << Level.ELEMENT_SHIFT,
								    		i << Level.ELEMENT_SHIFT ) );
					break;

					case '^': grid[ i ][ j ] = true;
						dogs.addElement( 
							new DogSprite( Sprite.UP, dog,
										j << Level.ELEMENT_SHIFT,
								    		i << Level.ELEMENT_SHIFT ) );
					break;

					case 'v': grid[ i ][ j ] = true;
						dogs.addElement( 
							new DogSprite( Sprite.DOWN, dog,
										j << Level.ELEMENT_SHIFT,
								    		i << Level.ELEMENT_SHIFT ) );
					break;

					default:
						throw new IOException( "Error line " + st.lineno() );
				}
			}
		}

		if ( doorSprite == null ) 
			throw new IOException( "Level does not have a door!" );
	}
} 