/*
 * Copyright (c) 2017, L. Adamson
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are those
 * of the authors and should not be interpreted as representing official policies,
 * either expressed or implied, of L. Adamson or DizzyDragon.net.
 * 
 */

package labyrinth.refmaterial.decoders;

import java.io.PrintStream;

public class DecodedDungeon
{
	public enum DecodedWall
	{
		CLEAR,
		BLOCKED,
		DOOR,
		SECRET_DOOR
	}
	
	public enum DecodedSpc
	{
		NOTHING( null, null ),
		STAIR_DOWN( " \\ ", "Downward Staircase" ),
		STAIR_UP( " / ", "Upward Staircase" ),
		STAIR_SPIRAL( "\\ /", "Spiral Staircase (up/down)" ),
		EXC( "EXC", "Excelsior Transporter" ),
		PIT( "PIT", "Pit" ),
		TPT( "TPT", "Teleporter" ),
		FNT( "FNT", "Fountain" ),
		ALT( "ALT", "Altar" ),
		DGN( "DGN", "Dragon's Lair" ),
		DGO( "DGO", "Dragon, Guarding the Orb (ORB in PC-DND)" ),
		ORB( "ORB", "The Orb (Magic Mirror in PC-DND)" ),
		ELV( "ELV", "Elevator" ),
		THR( "THR", "Throne" ),
		TRV( "TRV", "Treasure Vault" ),
		RCK( "RCK", "Solid Rock (Genie in PC-DND)" );
		
		final public String mapCode;
		final public String description;
		
		private DecodedSpc( String mapCode, String description )
		{
			this.mapCode = mapCode;
			this.description = description;
		}
	}
	
	final private String name;
	final private int startX;
	final private int startY;
	final private byte[] data;
	
	public DecodedDungeon( String name, Integer startX, Integer startY, byte[] data )
	{
		this.name = name;
		this.data = data;
		if( startX != null && startY != null )
		{
			this.startX = startX;
			this.startY = startY;
		}
		else
		{
			// FIXME: Calculate these!
			int calculatedStartX = 0;
			int calculatedStartY = 0;
			this.startX = calculatedStartX;
			this.startY = calculatedStartY;
		}
	}
	
	public String getName()
	{
		return name;
	}
	
	public int getStartX()
	{
		return startX;
	}
	
	public int getStartY()
	{
		return startY;
	}
	
	public boolean isValidRoom( int x, int y, int z )
	{
		if( x < 0 || x > 19 || y < 0 || y > 19 )
			return false;
		boolean notAllZeroes = false;
		for( int ty=0; ty<20; ty++ )
		{
			for( int tx=0; tx<20; tx++ )
			{
				if( getRawByte(tx,ty,z) != 0 )
				{
					notAllZeroes = true;
					break;
				}
			}
		}
		if( !notAllZeroes )
			return false;
		if( getRawWestWall( x, y, z ) == 1 && getRawNorthWall( x, y, z ) == 1 && getRawWestWall( x + 1, y, z ) == 1 && getRawNorthWall( x, y + 1, z ) == 1 && getRawSpc( x, y, z ) == 15 )
			return false;
		return true;
	}
	
	public boolean isValidLevel( int z )
	{
		boolean valid;
		// Check to see if the level is all zeroes.
		valid = false;
		for( int ty=0; ty<20; ty++ )
		{
			for( int tx=0; tx<20; tx++ )
			{
				if( getRawByte(tx,ty,z) != 0 )
				{
					valid = true;
					break;
				}
			}
		}
		if( !valid )
			return false;
		// Check to see if the level contains SPCs of all 15.
		valid = false;
		for( int ty=0; ty<20; ty++ )
		{
			for( int tx=0; tx<20; tx++ )
			{
				if( getRawSpc(tx,ty,z) != 0xf )
				{
					valid = true;
					break;
				}
			}
		}
		if( !valid )
			return false;
		// If any room is valid, then the level is valid.
		for( int y=0; y<20; y++ )
			for( int x=0; x<20; x++ )
				if( isValidRoom( x, y, z ) )
					return true;
		return false;
	}
	
	public DecodedWall getWestWall( int x, int y, int z )
	{
		if( !isValidRoom( x, y, z ) && !isValidRoom( x - 1, y, z ) )
			return DecodedWall.CLEAR;
		return DecodedWall.values()[getRawWestWall( x, y, z )];
	}
	
	public DecodedWall getNorthWall( int x, int y, int z )
	{
		if( !isValidRoom( x, y, z ) && !isValidRoom( x, y - 1, z ) )
			return DecodedWall.CLEAR;
		return DecodedWall.values()[getRawNorthWall( x, y, z )];
	}
	
	public DecodedWall getEastWall( int x, int y, int z )
	{
		return getWestWall( x+1, y, z );
	}
	
	public DecodedWall getSouthWall( int x, int y, int z )
	{
		return getNorthWall( x, y+1, z );
	}
	
	public DecodedSpc getSpc( int x, int y, int z )
	{
		if( isValidRoom( x, y, z ) )
			return DecodedSpc.values()[getRawSpc( x, y, z )];
		return DecodedSpc.NOTHING;
	}
	
	public void printMap( int z )
	{
		printMap( z, System.out );
	}
	
	public void printMap( int z, PrintStream out )
	{
		final int size = 20;
		if( !isValidLevel(z) )
			return;
		char[][] map = new char[size*4+1][size*4+1];
		for( int y=0; y<size*4+1; y++ )
			for( int x=0; x<size*4+1; x++ )
				map[y][x] = ' ';
		for( int y=0; y<size; y++ )
		{
			for( int x=0; x<size; x++ )
			{
				if( isValidRoom(x,y,z) )
				{
					for( int yy=0; yy<5; yy++ )
						for( int xx=0; xx<5; xx++ )
							map[y*4+yy][x*4+xx] = '.';
				}
			}
		}
		for( int y=0; y<size; y++ )
		{
			for( int x=0; x<size; x++ )
			{
				if( isValidRoom(x,y,z) )
				{
					DecodedWall wall;
					wall = getNorthWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*4][x*4+0] = '#';
						map[y*4][x*4+1] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*4][x*4+2] = '-';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*4][x*4+2] = 's';
						else
							map[y*4][x*4+2] = '#';
						map[y*4][x*4+3] = '#';
						map[y*4][x*4+4] = '#';
					}
					wall = getSouthWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*4+4][x*4+0] = '#';
						map[y*4+4][x*4+1] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*4+4][x*4+2] = '-';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*4+4][x*4+2] = 's';
						else
							map[y*4+4][x*4+2] = '#';
						map[y*4+4][x*4+3] = '#';
						map[y*4+4][x*4+4] = '#';
					}
					wall = getWestWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*4+0][x*4] = '#';
						map[y*4+1][x*4] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*4+2][x*4] = '|';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*4+2][x*4] = 's';
						else
							map[y*4+2][x*4] = '#';
						map[y*4+3][x*4] = '#';
						map[y*4+4][x*4] = '#';
					}
					wall = getEastWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*4+0][x*4+4] = '#';
						map[y*4+1][x*4+4] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*4+2][x*4+4] = '|';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*4+2][x*4+4] = 's';
						else
							map[y*4+2][x*4+4] = '#';
						map[y*4+3][x*4+4] = '#';
						map[y*4+4][x*4+4] = '#';
					}
					DecodedSpc spc = getSpc( x, y, z );
					if( spc != DecodedSpc.NOTHING )
					{
						map[y*4+2][x*4+1] = spc.mapCode.charAt( 0 );
						map[y*4+2][x*4+2] = spc.mapCode.charAt( 1 );
						map[y*4+2][x*4+3] = spc.mapCode.charAt( 2 );
					}
				}
			}
		}
		out.print( "   " );
		for( int x=0; x<20; x++ )
			out.print( "  "+String.format("%02d",x) );
		out.println( "   " );
		for( int y = 0; y<map.length; y++ )
		{
			if( y%4 - 2 == 0 )
				out.print( String.format("%02d ",y/4) );
			else
				out.print( "   " );
			for( int x=0; x<map[y].length; x++ )
				out.print( map[y][x] );
			if( y%4 - 2 == 0 )
				out.print( String.format(" %02d",y/4) );
			else
				out.print( "   " );
			out.println();
		}
		out.print( "   " );
		for( int x=0; x<20; x++ )
			out.print( "  "+String.format("%02d",x) );
		out.println( "   " );
	}
		
	public void printBlockMap( int z )
	{
		printBlockMap( z, System.out );
	}
	
	public void printBlockMap( int z, PrintStream out )
	{
		final int size = 20;
		if( !isValidLevel(z) )
			return;
		char[][] map = new char[size*2+1][size*2+1];
		for( int y=0; y<size*2+1; y++ )
			for( int x=0; x<size*2+1; x++ )
				map[y][x] = ' ';
		for( int y=0; y<size; y++ )
		{
			for( int x=0; x<size; x++ )
			{
				if( isValidRoom(x,y,z) )
				{
					for( int yy=0; yy<3; yy++ )
						for( int xx=0; xx<3; xx++ )
							map[y*2+yy][x*2+xx] = '.';
				}
			}
		}
		for( int y=0; y<size; y++ )
		{
			for( int x=0; x<size; x++ )
			{
				if( isValidRoom(x,y,z) )
				{
					DecodedWall wall;
					wall = getNorthWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*2][x*2+0] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*2][x*2+1] = '+';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*2][x*2+1] = 'S';
						else
							map[y*2][x*2+1] = '#';
						map[y*2][x*2+2] = '#';
					}
					wall = getSouthWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*2+2][x*2+0] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*2+2][x*2+1] = '+';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*2+2][x*2+1] = 'S';
						else
							map[y*2+2][x*2+1] = '#';
						map[y*2+2][x*2+2] = '#';
					}
					wall = getWestWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*2+0][x*2] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*2+1][x*2] = '+';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*2+1][x*2] = 'S';
						else
							map[y*2+1][x*2] = '#';
						map[y*2+2][x*2] = '#';
					}
					wall = getEastWall(x,y,z);
					if( wall != DecodedWall.CLEAR )
					{
						map[y*2+0][x*2+2] = '#';
						if( wall == DecodedWall.DOOR )
							map[y*2+1][x*2+2] = '+';
						else if( wall == DecodedWall.SECRET_DOOR )
							map[y*2+1][x*2+2] = 'S';
						else
							map[y*2+1][x*2+2] = '#';
						map[y*2+2][x*2+2] = '#';
					}
					DecodedSpc spc = getSpc( x, y, z );
					if( spc != DecodedSpc.NOTHING )
					{
						map[y*2+1][x*2+1] = String.format( "%01x", spc.ordinal() ).charAt( 0 );
					}
				}
			}
		}
		for( int y = 0; y<map.length; y++ )
		{
			for( int x=0; x<map[y].length; x++ )
				out.print( map[y][x] );
			out.println();
		}
	}
	
//	public int getFloor( int x, int y, int z )
//	{
//		if( isValidRoom( x, y, z ) )
//			return 1;
//		return 0;
//	}
	
	public boolean getCorner( int x, int y, int z )
	{
		if( getWestWall( x, y, z ) != DecodedWall.CLEAR || getWestWall( x, y - 1, z ) != DecodedWall.CLEAR || getNorthWall( x, y, z ) != DecodedWall.CLEAR || getNorthWall( x - 1, y, z ) != DecodedWall.CLEAR )
			return true;
		return false;
	}
	
	public int getRawByte( int x, int y, int z )
	{
		return data[( z - 1 ) * 20 * 20 + y * 20 + x];
	}
	
	public int getRawWestWall( int x, int y, int z )
	{
		if( x < 0 || x > 19 || y < 0 || y > 19 || z < 1 || z > 20 )
			return 1;
		else
			return getRawByte( x, y, z ) & 0x3;
	}
	
	public int getRawNorthWall( int x, int y, int z )
	{
		if( x < 0 || x > 19 || y < 0 || y > 19 || z < 1 || z > 20 )
			return 1;
		else
			return ( getRawByte( x, y, z ) >> 2 ) & 0x3;
	}
	
	public int getRawSpc( int x, int y, int z )
	{
		if( x < 0 || x > 19 || y < 0 || y > 19 || z < 1 || z > 20 )
			return 15;
		else
			return ( getRawByte( x, y, z ) >> 4 ) & 0xf;
	}
}
