package com.planet_ink.coffee_mud.WebMacros.grinder;

import com.planet_ink.coffee_web.interfaces.*;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.GridZones.XYVector;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.net.*;
import java.util.*;
import java.net.URLEncoder;

/*
   Copyright 2003-2022 Bo Zimmerman

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

	   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
public class GrinderFlatMap
{
	protected List<GrinderRoom>			areaMap		= null;
	protected Map<String, GrinderRoom>	hashRooms	= null;
	private GrinderRoom[][]				grid		= null;

	protected int		Xbound		= 0;
	protected int		Ybound		= 0;
	protected int		Ystart		= 0;
	protected int		Xstart		= 0;
	protected Area		area		= null;
	protected boolean	debug		= false;
	protected int[]		boundsXYXY	= null;

	public GrinderFlatMap()
	{
	}

	public GrinderFlatMap(final Area A, final int[] xyxy)
	{
		area=A;
		areaMap=new Vector<GrinderRoom>();
		hashRooms=new Hashtable<String,GrinderRoom>();
		Enumeration<Room> r=A.getProperMap();
		Room R=null;
		boundsXYXY=xyxy;

		final boolean thinArea=CMath.bset(A.flags(),Area.FLAG_THIN);
		if(thinArea)
		{
			final RoomnumberSet currentSet=A.getCachedRoomnumbers();
			String roomID=null;
			//RoomnumberSet loadRooms=(RoomnumberSet)CMClass.getCommon("DefaultRoomnumberSet");
			for(final Enumeration<String> e=A.getProperRoomnumbers().getRoomIDs();e.hasMoreElements();)
			{
				// this makes sure that, even though this is
				// an unloaded room, it is ALSO actually needed
				// for mapping.
				roomID=e.nextElement();
				if((currentSet==null)||(!currentSet.contains(roomID)))
				{
					if(area instanceof GridZones)
					{
						if(xyxy!=null)
						{
							final XYVector thisXY=((GridZones)area).getRoomXY(roomID);
							if(thisXY==null)
								continue;
							if((thisXY.x<xyxy[0])
							||(thisXY.y<xyxy[1])
							||(thisXY.x>xyxy[2])
							||(thisXY.y>xyxy[3]))
								continue;
						}
						final GrinderRoom GR=new GrinderRoom(roomID);
						areaMap.add(GR);
						hashRooms.put(GR.roomID,GR);
					}
					else
						CMLib.map().getRoom(roomID);
				}
			}
			r=A.getProperMap();
		}

		// appropriate thin rooms are already added at this point.
		// if this is a fat gridzone, or there are proper rooms
		// left, now is the time to siphon out the ones we need.
		if((area instanceof GridZones)&&(xyxy!=null))
		{
			final Vector<Room> finalSet=new Vector<Room>();
			for(;r.hasMoreElements();)
			{
				R=r.nextElement();
				if(R.roomID().length()>0)
				{
					final XYVector thisXY=((GridZones)area).getRoomXY(R.roomID());
					if(thisXY==null)
						continue;
					if((thisXY.x<xyxy[0])
					||(thisXY.y<xyxy[1])
					||(thisXY.x>=xyxy[2])
					||(thisXY.y>=xyxy[3]))
					{
						// force the old ones to expire asap
						if((thinArea)&&(R.expirationDate()>0))
							R.setExpirationDate(System.currentTimeMillis());
						continue;
					}
					if((thinArea)&&(R.expirationDate()>0))
						R.setExpirationDate(System.currentTimeMillis()+(10*60*1000));
					finalSet.add(R);
				}
			}
			r=finalSet.elements();
		}
		if((area instanceof GridZones)&&(boundsXYXY==null))
		{
			boundsXYXY=new int[4];
			boundsXYXY[0]=0;
			boundsXYXY[1]=0;
			boundsXYXY[2]=((GridZones)area).xGridSize();
			boundsXYXY[3]=((GridZones)area).yGridSize();
		}

		// no matter what, r is the way to go.
		// for thin rooms, the thin-unloaded are already in areaMap and hashRooms
		// for thin grid zones, the *correct* thin-unloaded rooms are also ready
		// for grid zones, r has had the inappropriate rooms siphoned out
		// all thats left to hash are the APPROPRIATE fat rooms!
		for(;r.hasMoreElements();)
		{
			R=r.nextElement();
			if(R.roomID().length()>0)
			{
				final GrinderRoom GR=new GrinderRoom(R);
				areaMap.add(GR);
				hashRooms.put(GR.roomID,GR);
			}
		}
		//for(int a=0;a<areaMap.size();a++)
		//	((GrinderRoom)areaMap.elementAt(a)).score=scoreRoom((GrinderRoom)areaMap.elementAt(a), "X!X!X", new int[2], new HashSet<String>(), new HashSet<String>());
	}

	public double getDistanceFrom(final int[] xy1,final int[] xy2)
	{
		return Math.sqrt( ( ((double)(xy1[0]-xy2[0])) * ((double)(xy1[0]-xy2[0])) )
						+ ( ((double)(xy1[1]-xy2[1])) * ((double)(xy1[1]-xy2[1])) ) );
	}

	public void rebuildGrid()
	{
		if((areaMap==null)||(hashRooms==null))
			return;

		// very happy special case
		if(area instanceof GridZones)
		{
			Xbound=(boundsXYXY[2]-boundsXYXY[0]);
			Ybound=(boundsXYXY[3]-boundsXYXY[1]);
			for(int i=areaMap.size()-1;i>=0;i--)
			{
				final GrinderRoom room=areaMap.get(i);
				final XYVector myxy=((GridZones)area).getRoomXY(room.roomID);
				if(myxy==null)
					continue;
				if((myxy.x<boundsXYXY[0])||(myxy.y<boundsXYXY[1])||(myxy.x>=boundsXYXY[2])||(myxy.y>=boundsXYXY[3]))
					areaMap.remove(room);
				else
				{
					room.xy=new int[2];
					room.xy[0]=myxy.x-boundsXYXY[0];
					room.xy[1]=myxy.y-boundsXYXY[1];
				}
			}
		}
		else
			placeRooms();

		grid=new GrinderRoom[Xbound+1][Ybound+1];
		for(int y=0;y<areaMap.size();y++)
		{
			final GrinderRoom room=areaMap.get(y);
			grid[room.xy[0]][room.xy[1]]=room;
		}
	}

	public void placeRooms()
	{
		if((areaMap==null)||(hashRooms==null)||(area instanceof GridZones))
			return;

		final List<List<GrinderRoom>> sets=new Vector<List<GrinderRoom>>();
		final HashSet<String> roomsDone=new HashSet<String>();
		boolean didSomething=true;

		final List<int[]> allDirections=new XArrayList<int[]>();
		allDirections.add(getDirectionSet(Directions.NUM_DIRECTIONS()-1, 0, -1));
		allDirections.add(getDirectionSet(0, Directions.NUM_DIRECTIONS()-1, 1));
		allDirections.add(getDirectionSet(Directions.NUM_DIRECTIONS()/2, (Directions.NUM_DIRECTIONS()/2)-1, 1));
		/*
		for(final int dir : new int[] {1, -1})
		{
			for(int startD=0;startD<Directions.NUM_DIRECTIONS();startD++)
			{
				int endD=startD-dir;
				if(endD<0)
					endD=Directions.NUM_DIRECTIONS()-1;
				else
				if(endD>=Directions.NUM_DIRECTIONS())
					endD=0;
				final int[] theDirs=this.getDirectionSet(startD, endD, dir);
				allDirections.add(theDirs);

			}
		}
		*/
		final boolean[] allWays=new boolean[] {false, true};

		// first, cluster the rooms WITHOUT positioning them
		final List<GrinderRoom> finalCluster=new Vector<GrinderRoom>();
		while((roomsDone.size()<areaMap.size())&&(didSomething))
		{
			didSomething=false;
			List<GrinderRoom> bestSet=null;
			boolean bestTwoWay=false;
			int[] bestDirs=null;
			for(int i=0;i<areaMap.size();i++)
			{
				final GrinderRoom room=areaMap.get(i);
				if(roomsDone.contains(room.roomID))
					continue;
				final List<List<GrinderRoom>> allSets=new ArrayList<List<GrinderRoom>>();
				final Map<List<GrinderRoom>,Boolean> allSetWays=new HashMap<List<GrinderRoom>,Boolean>();
				if(bestSet != null)
					allSetWays.put(bestSet, Boolean.valueOf(bestTwoWay));
				final Map<List<GrinderRoom>,int[]> allSetDirs=new HashMap<List<GrinderRoom>,int[]>();
				for(final boolean useTwoWay : allWays)
				{
					final Boolean useTwoWayB=Boolean.valueOf(useTwoWay);
					for(final int[] dirs : allDirections)
					{
						final List<GrinderRoom> V=buildCluster(hashRooms, room, roomsDone,false,useTwoWay,dirs);
						allSets.add(V);
						allSetWays.put(V, useTwoWayB);
						allSetDirs.put(V, dirs);
					}
				}
				final int[] vDirsUsed;
				final boolean twoWayUsed;
				final List<GrinderRoom> V;
				final Comparator<List<GrinderRoom>> roomSetCompare=new Comparator<List<GrinderRoom>>()
				{
					@Override
					public int compare(final List<GrinderRoom> o1, final List<GrinderRoom> o2)
					{
						final int o110 = 0;//o1.size()/10;
						final int o210 = 0;//o2.size()/10;
						if(o1.size()+o110<o2.size()-o210)
							return -1;
						if(o1.size()-o110>o2.size()+210)
							return 1;
						final boolean useTwo1=allSetWays.get(o1).booleanValue();
						final boolean useTwo2=allSetWays.get(o2).booleanValue();
						if(useTwo1)
						{
							if(useTwo2)
								return 0;
							return 1;
						}
						else
						{
							if(useTwo2)
								return -1;
							return 0;
						}
					}
				};
				Collections.sort(allSets,roomSetCompare);
				V=allSets.get(allSets.size()-1);
				twoWayUsed=allSetWays.get(V).booleanValue();
				vDirsUsed=allSetDirs.get(V);
				if(bestSet==null)
				{
					bestSet=V;
					bestTwoWay=twoWayUsed;
					bestDirs=vDirsUsed;
				}
				else
				if(roomSetCompare.compare(bestSet, V)<0)
				{
					bestSet=V;
					bestTwoWay=twoWayUsed;
					bestDirs=vDirsUsed;
				}
			}
			if(bestSet!=null)
			{
				if(bestSet.size()==1)
					finalCluster.add(bestSet.get(0));
				else
				{
					final GrinderRoom winnerR=bestSet.get(0);
					buildCluster(hashRooms, winnerR, roomsDone,true,bestTwoWay,bestDirs);
					sets.add(bestSet);
				}
				for(int v=0;v<bestSet.size();v++)
					roomsDone.add(bestSet.get(v).roomID);
				bestSet=null;
				didSomething=true;
			}
		}
		// find leftover rooms and make them their own cluster
		for(int a=0;a<areaMap.size();a++)
		{
			if(!roomsDone.contains(areaMap.get(a).roomID))
				finalCluster.add(areaMap.get(a));
		}
		if(finalCluster.size()>0)
		{
			final boolean[][] miniGrid=new boolean[finalCluster.size()+1][finalCluster.size()+1];
			final int[] midXY=new int[2];
			midXY[0]=(int)Math.round(Math.floor((finalCluster.size()+1.0)/2.0));
			midXY[1]=(int)Math.round(Math.floor((finalCluster.size()+1.0)/2.0));
			finalCluster.get(0).xy=midXY.clone();
			miniGrid[midXY[0]][midXY[1]]=true;
			for(int f=1;f<finalCluster.size();f++)
			{
				int[] bestCoords=new int[]{miniGrid.length-1,miniGrid.length-1};
				for(int x=0;x<miniGrid.length;x++)
				{
					for(int y=0;y<miniGrid[x].length;y++)
					{
						if((!miniGrid[x][y])
						&&(getDistanceFrom(bestCoords,midXY)>getDistanceFrom(new int[]{x,y},midXY)))
							bestCoords=new int[]{x,y};
					}
				}
				miniGrid[bestCoords[0]][bestCoords[1]]=true;
				finalCluster.get(f).xy=bestCoords.clone();
			}
			for(int f=0;f<finalCluster.size();f++)
			{
				finalCluster.get(f).xy[0]*=2;
				finalCluster.get(f).xy[1]*=2;
			}
			sets.add(finalCluster);
		}
		clusterSet(sets);
	}

	public void placeRoomsII()
	{
		if((areaMap==null)||(hashRooms==null)||(area instanceof GridZones))
			return;

		final List<List<GrinderRoom>> sets=new Vector<List<GrinderRoom>>();
		final Set<String> roomsDone=new HashSet<String>();
		boolean didSomething=true;
		// first, cluster the rooms WITHOUT positioning them
		final List<GrinderRoom> finalCluster=new Vector<GrinderRoom>();
		while((roomsDone.size()<areaMap.size())&&(didSomething))
		{
			didSomething=false;
			for(int i=0;i<areaMap.size();i++)
			{
				final GrinderRoom room=areaMap.get(i);
				if(roomsDone.contains(room.roomID))
					continue;
				final List<GrinderRoom> V=scoreRoomII(hashRooms, room, roomsDone);
				if((V!=null)&&(V.size()>0))
				{
					if(V.size()==1)
						finalCluster.add(V.get(0));
					else
						sets.add(V);
					didSomething=true;
				}
			}
		}
		// find leftover rooms and make them their own cluster
		for(int a=0;a<areaMap.size();a++)
		{
			if(!roomsDone.contains(areaMap.get(a).roomID))
				finalCluster.add(areaMap.get(a));
		}
		if(finalCluster.size()>0)
		{
			final boolean[][] miniGrid=new boolean[finalCluster.size()+1][finalCluster.size()+1];
			final int[] midXY=new int[2];
			midXY[0]=(int)Math.round(Math.floor((finalCluster.size()+1.0)/2.0));
			midXY[1]=(int)Math.round(Math.floor((finalCluster.size()+1.0)/2.0));
			finalCluster.get(0).xy=midXY.clone();
			miniGrid[midXY[0]][midXY[1]]=true;
			for(int f=1;f<finalCluster.size();f++)
			{
				int[] bestCoords=new int[]{miniGrid.length-1,miniGrid.length-1};
				for(int x=0;x<miniGrid.length;x++)
				{
					for(int y=0;y<miniGrid[x].length;y++)
					{
						if((!miniGrid[x][y])
						&&(getDistanceFrom(bestCoords,midXY)>getDistanceFrom(new int[]{x,y},midXY)))
							bestCoords=new int[]{x,y};
					}
				}
				miniGrid[bestCoords[0]][bestCoords[1]]=true;
				finalCluster.get(f).xy=bestCoords.clone();
			}
			for(int f=0;f<finalCluster.size();f++)
			{
				finalCluster.get(f).xy[0]*=2;
				finalCluster.get(f).xy[1]*=2;
			}
			sets.add(finalCluster);
		}
		clusterSet(sets);
	}

	public void clusterSet(final List<List<GrinderRoom>> sets)
	{
		// figure out width height, and xy bounds
		// store them in a vector parallel to each
		final Vector<int[]> sizeInfo=new Vector<int[]>(sets.size());
		GrinderRoom R=null;
		for(int s=0;s<sets.size();s++)
		{
			final List<GrinderRoom> set=sets.get(s);
			R=(set.get(0));
			final int[] minXY=new int[]{R.xy[0],R.xy[1]};
			final int[] maxXY=new int[]{R.xy[0],R.xy[1]};
			for(int r=1;r<set.size();r++)
			{
				R=(set.get(r));
				if(R.xy[0]<minXY[0])
					minXY[0]=R.xy[0];
				if(R.xy[1]<minXY[1])
					minXY[1]=R.xy[1];

				if(R.xy[0]>maxXY[0])
					maxXY[0]=R.xy[0];
				if(R.xy[1]>maxXY[1])
					maxXY[1]=R.xy[1];
			}
			final int[] widthHeightXY=new int[2];
			widthHeightXY[0]=maxXY[0]-minXY[0];
			widthHeightXY[1]=maxXY[1]-minXY[1];
			for(int r=0;r<set.size();r++)
			{
				R=(set.get(r));
				R.xy[0]-=minXY[0];
				R.xy[1]-=minXY[1];
			}
			sizeInfo.add(widthHeightXY);
		}

		// now cluster them into a top-level grid.. we'll trim the grid later.
		// yes, i know there must be a more efficient way...
		@SuppressWarnings("unchecked")
		final List<GrinderRoom>[][] grid=new List[sets.size()+1][sets.size()+1];
		final int[] midXY=new int[2];
		midXY[0]=(int)Math.round(Math.floor((sets.size()+1.0)/2.0));
		midXY[1]=(int)Math.round(Math.floor((sets.size()+1.0)/2.0));
		grid[midXY[0]][midXY[1]]=sets.get(0);
		int mostLeft=midXY[0];
		int mostTop=midXY[1];
		for(int a=1;a<sets.size();a++)
		{
			int[] bestCoords=new int[]{grid.length-1,grid.length-1};
			for(int x=0;x<grid.length;x++)
			{
				for(int y=0;y<grid[x].length;y++)
				{
					if((grid[x][y]==null)
					&&(getDistanceFrom(bestCoords,midXY)>getDistanceFrom(new int[]{x,y},midXY)))
						bestCoords=new int[]{x,y};
				}
			}
			if(grid[bestCoords[0]][bestCoords[1]]!=null)
			{
				Log.errOut("GFlagMap","Lost a chunk of rooms to bad spiral positioning!!!");
				while(grid[bestCoords[0]][bestCoords[1]]!=null)
				{
					bestCoords[0]=CMLib.dice().roll(1,grid.length-1,-1);
					bestCoords[1]=CMLib.dice().roll(1,grid.length-1,-1);
				}
			}

			grid[bestCoords[0]][bestCoords[1]]=sets.get(a);
			if(bestCoords[0]<mostLeft)
				mostLeft=bestCoords[0];
			if(bestCoords[1]<mostTop)
				mostTop=bestCoords[1];
		}
		// JUST DO XS FIRST!!
		List<GrinderRoom> set=null;
		int rightMostX=0;
		int nextRightMostX=0;
		for(int x=mostLeft;x<grid.length;x++)
		{
			for(int y=0;y<grid[x].length;y++)
			{
				set=grid[x][y];
				if(set!=null)
				{
					for(int r=0;r<set.size();r++)
					{
						R=set.get(r);
						R.xy[0]+=rightMostX;
						if(R.xy[0]>nextRightMostX)
							nextRightMostX=R.xy[0];
					}
				}
			}
			rightMostX=nextRightMostX+2;
		}
		// now do YS
		int bottomMostY=0;
		int nextBottomMostY=0;
		for(int y=mostTop;y<grid[0].length;y++)
		{
			for (final List<GrinderRoom>[] element : grid)
			{
				set=element[y];
				if(set!=null)
				{
					for(int r=0;r<set.size();r++)
					{
						R=set.get(r);
						R.xy[1]+=bottomMostY;
						if(R.xy[1]>nextBottomMostY)
							nextBottomMostY=R.xy[1];
					}
				}
			}
			bottomMostY=nextBottomMostY+2;
		}
		Xbound=nextRightMostX;
		Ybound=nextBottomMostY;
	}

	public void rePlaceRooms()
	{
		if(areaMap==null)
			return;
		grid=null;
		rebuildGrid();
		hashRooms=null;
	}

	public GrinderRoom getRoom(final String ID)
	{
		if((hashRooms!=null)&&(hashRooms.containsKey(ID)))
			return hashRooms.get(ID);

		if(areaMap!=null)
		{
			for(int r=0;r<areaMap.size();r++)
			{
				final GrinderRoom room=areaMap.get(r);
				if(room.roomID.equalsIgnoreCase(ID))
					return room;
			}
		}
		return null;
	}

	protected final static int CLUSTERSIZE=3;

	public boolean anythingThatDirection(final GrinderRoom room, final int direction)
	{
		final GrinderDir D=room.doors[direction];
		if((D==null)||(D.room.length()==0))
			return false;
		return true;
	}

	public List<GrinderRoom> scoreRoomII(final Map<String,GrinderRoom> H, final GrinderRoom room, final Set<String> roomsDone)
	{
		final HashSet<String> coordsDone=new HashSet<String>();
		coordsDone.add(0+"/"+0);
		roomsDone.add(room.roomID);

		final List<GrinderRoom> V=new Vector<GrinderRoom>();
		V.add(room);
		int startHere=0;
		room.xy=new int[2];
		GrinderRoom R2=null;
		GrinderRoom R3=null;
		while(startHere!=V.size())
		{
			int s=startHere;
			final int size=V.size();
			startHere=size;
			for(;s<size;s++)
			{
				final GrinderRoom R=V.get(s);
				for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
				{
					if((R.doors[d]!=null)
					&&(R.doors[d].room!=null)
					&&(R.doors[d].room.length()>0)
					&&(!roomsDone.contains(R.doors[d].room))
					&&(!roomsDone.contains(R.doors[d].room)))
					{
						R2=H.get(R.doors[d].room);
						if(R2==null)
							continue;
						R2.xy=newXY(R.xy,d);
						if(coordsDone.contains(R2.xy[0]+"/"+R2.xy[1]))
						{
							boolean adjust=false;
							for(int v=0;v<V.size();v++)
							{
								adjust=false;
								R3=V.get(v);
								switch(d)
								{
								case Directions.NORTH:
									adjust = (R3.xy[1] <= R2.xy[1]);
									break;
								case Directions.SOUTH:
									adjust = (R3.xy[1] >= R2.xy[1]);
									break;
								case Directions.EAST:
									adjust = (R3.xy[0] >= R2.xy[0]);
									break;
								case Directions.WEST:
									adjust = (R3.xy[0] <= R2.xy[0]);
									break;
								case Directions.NORTHEAST:
									adjust = (R3.xy[1] <= R2.xy[1]) || (R3.xy[0] >= R2.xy[0]);
									break;
								case Directions.NORTHWEST:
									adjust = (R3.xy[1] <= R2.xy[1]) || (R3.xy[0] <= R2.xy[0]);
									break;
								case Directions.SOUTHEAST:
									adjust = (R3.xy[1] <= R2.xy[1]);
									break;
								case Directions.SOUTHWEST:
									adjust = (R3.xy[1] <= R2.xy[1]);
									break;
								case Directions.UP:
									adjust = (R3.xy[1] <= R2.xy[1]);
									break;
								case Directions.DOWN:
									adjust = (R3.xy[1] >= R2.xy[1]);
									break;
								}
								if(adjust)
								{
									//coordsDone.remove(R3.xy[0]+"/"+R3.xy[1]);
									R3.xy=newXY(R3.xy,d);
									coordsDone.add(R3.xy[0]+"/"+R3.xy[1]);
								}
							}
						}
						roomsDone.add(R2.roomID);
						coordsDone.add(R2.xy[0]+"/"+R2.xy[1]);
						V.add(R2);
					}
				}
			}
		}
		return V;
	}

	protected int[] getDirectionSet(final int start, final int end, final int dir)
	{
		final int[] directionsToDo=new int[Directions.NUM_DIRECTIONS()];
		int d=start;
		int x=0;
		while(d != end)
		{
			directionsToDo[x]=d;
			d += dir;
			if(d<0)
				d=Directions.NUM_DIRECTIONS()-1;
			else
			if(d>=Directions.NUM_DIRECTIONS())
				d=0;
			x++;
		}
		directionsToDo[x]=end;
		if(x<directionsToDo.length-1)
			Log.errOut("GrinderFlatMap Direction Building FAIL!");
		return directionsToDo;
	}

	public List<GrinderRoom> buildCluster(final Map<String,GrinderRoom> fullMapH, final GrinderRoom coreRoom,
										  final HashSet<String> outerRoomsDone, final boolean finalPosition,
										  final boolean doTwoWay, final int[] directionsToDo)
	{
		final HashSet<String> coordsDone=new HashSet<String>();
		coordsDone.add(0+"/"+0);

		final HashSet<String> innerRoomsDone=new HashSet<String>();
		innerRoomsDone.add(coreRoom.roomID);

		final HashMap<String,int[]> xys=new HashMap<String,int[]>();
		int[] xy=new int[2];
		if(finalPosition)
			coreRoom.xy=xy;
		xys.put(coreRoom.roomID,xy);

		final List<GrinderRoom> roomClusterV=new ArrayList<GrinderRoom>();
		roomClusterV.add(coreRoom);
		int startHere=0;
		while(startHere!=roomClusterV.size())
		{
			int s=startHere;
			final int size=roomClusterV.size();
			startHere=size;
			for(;s<size;s++)
			{
				final GrinderRoom R=roomClusterV.get(s);
				xy=xys.get(R.roomID);
				for(final int d : directionsToDo)
				{
					if((d!=Directions.UP)
					&&(d!=Directions.DOWN)
					&&(R.doors[d]!=null)
					&&(R.doors[d].room!=null)
					&&(R.doors[d].room.length()>0)
					&&(!innerRoomsDone.contains(R.doors[d].room))
					&&(!outerRoomsDone.contains(R.doors[d].room)))
					{
						final GrinderRoom R2=fullMapH.get(R.doors[d].room);
						if(R2==null)
							continue;
						if(doTwoWay)
						{
							final int opD=Directions.getOpDirectionCode(d);
							if((R2.doors[opD]==null)
							||(R2.doors[opD].room==null)
							||(!R2.doors[opD].room.equalsIgnoreCase(R.roomID)))
								continue;
						}
						final int[] xy2=newXY(xy,d);
						xys.put(R2.roomID,xy2);
						if(!coordsDone.contains(xy2[0]+"/"+xy2[1]))
						{
							if(finalPosition)
								R2.xy=xy2;
							innerRoomsDone.add(R2.roomID);
							coordsDone.add(xy2[0]+"/"+xy2[1]);
							roomClusterV.add(R2);
						}
					}
				}
			}
		}
		return roomClusterV;
	}

	public StringBuffer getHTMLTable(final HTTPRequest httpReq)
	{
		final StringBuffer buf=new StringBuffer("");
		buf.append("<TABLE WIDTH="+((Xbound+1)*130)+" BORDER=0 CELLSPACING=0 CELLPADDING=0>");
		for(int l=0;l<5;l++)
		{
			buf.append("<TR HEIGHT=1>");
			for(int x=Xstart;x<=Xbound;x++)
				buf.append("<TD WIDTH=20><BR></TD><TD WIDTH=30><BR></TD><TD WIDTH=30><BR></TD><TD WIDTH=30><BR></TD><TD WIDTH=20><BR></TD>");
			buf.append("</TR>");
		}
		for(int y=Ystart;y<=Ybound;y++)
		{
			// up=nwes
			// down=sewn
			for(int l=0;l<5;l++)
			{
				buf.append("<TR HEIGHT=20>");
				for(int x=Xstart;x<=Xbound;x++)
				{
					final GrinderRoom GR=grid[x][y];
					if(GR==null)
						buf.append("<TD COLSPAN=5"+((boundsXYXY!=null)?" ID=X"+(x+boundsXYXY[0])+"_"+(y+boundsXYXY[1]):"")+"><BR></TD>");
					else
					{
						int up=-1;
						int down=-1;
						if(GR.doors[Directions.UP]!=null)
							up=findRelGridDir(GR,GR.doors[Directions.UP].room);
						if(GR.doors[Directions.DOWN]!=null)
							down=findRelGridDir(GR,GR.doors[Directions.DOWN].room);
						if(up<0)
						{
							if(down==Directions.NORTH)
								up=Directions.EAST;
							else
								up=Directions.NORTH;
						}
						if(down<0)
						{
							if(up==Directions.SOUTH)
								down=Directions.WEST;
							else
								down=Directions.SOUTH;
						}
						switch(l)
						{
						case 0: // north, up
							{
							buf.append("<TD>"+getDoorLabelGif(Directions.NORTHWEST,GR,httpReq)+"</TD>");
							buf.append("<TD><BR></TD>");
							buf.append("<TD>"+getDoorLabelGif(Directions.NORTH,GR,httpReq)+"</TD>");
							String alt="<BR>";
							if(up==Directions.NORTH)
								alt=getDoorLabelGif(Directions.UP,GR,httpReq);
							if(down==Directions.NORTH)
								alt=getDoorLabelGif(Directions.DOWN,GR,httpReq);
							buf.append("<TD>"+alt+"</TD>");
							buf.append("<TD>"+getDoorLabelGif(Directions.NORTHEAST,GR,httpReq)+"</TD>");
							}
							break;
						case 1: // west, east
							{
							buf.append("<TD><BR></TD>");
							buf.append("<TD COLSPAN=3 ROWSPAN=3 VALIGN=TOP ");
							buf.append(roomColorStyle(GR));
							buf.append(">");
							String roomID=GR.roomID;
							if(roomID.startsWith(area.Name()+"#"))
								roomID=roomID.substring(roomID.indexOf('#'));
							try
							{
								buf.append("<a name=\""+URLEncoder.encode(GR.roomID,"UTF-8")+"\" href=\"javascript:RC('"+GR.roomID+"');\"><FONT SIZE=-1><B>"+roomID+"</B></FONT></a><BR>");
							}
							catch(final java.io.UnsupportedEncodingException e)
							{
								Log.errOut("GrinderMap","Wrong Encoding");
							}
							buf.append("<FONT SIZE=-2>("+CMClass.classID(GR.room())+")<BR>");
							String displayText=GR.room().displayText();
							if(displayText.length()>20)
								displayText=displayText.substring(0,20)+"...";
							buf.append(displayText+"</FONT></TD>");
							buf.append("<TD><BR></TD>");
							}
							break;
						case 2: // nada
							buf.append("<TD>"+getDoorLabelGif(Directions.WEST,GR,httpReq)+"</TD>");
							buf.append("<TD>"+getDoorLabelGif(Directions.EAST,GR,httpReq)+"</TD>");
							break;
						case 3: // alt e,w
							{
							String alt="<BR>";
							if(up==Directions.WEST)
								alt=getDoorLabelGif(Directions.UP,GR,httpReq);
							if(down==Directions.WEST)
								alt=getDoorLabelGif(Directions.DOWN,GR,httpReq);
							buf.append("<TD>"+alt+"</TD>");
							alt="<BR>";
							if(up==Directions.EAST)
								alt=getDoorLabelGif(Directions.UP,GR,httpReq);
							if(down==Directions.EAST)
								alt=getDoorLabelGif(Directions.DOWN,GR,httpReq);
							buf.append("<TD>"+alt+"</TD>");
							}
							break;
						case 4: // south, down
							{
							buf.append("<TD>"+getDoorLabelGif(Directions.SOUTHWEST,GR,httpReq)+"</TD>");
							buf.append("<TD><BR></TD>");
							buf.append("<TD>"+getDoorLabelGif(Directions.SOUTH,GR,httpReq)+"</TD>");
							String alt="<BR>";
							if(up==Directions.SOUTH)
								alt=getDoorLabelGif(Directions.UP,GR,httpReq);
							if(down==Directions.SOUTH)
								alt=getDoorLabelGif(Directions.DOWN,GR,httpReq);
							buf.append("<TD>"+alt+"</TD>");
							buf.append("<TD>"+getDoorLabelGif(Directions.SOUTHEAST,GR,httpReq)+"</TD>");
							}
							break;
						}
					}
				}
				buf.append("</TR>");
			}
		}
		buf.append("</TABLE>");
		return buf;
	}

	protected String roomColorStyle(final GrinderRoom GR)
	{
		final Room R=GR.room();
		switch (R.domainType())
		{
		case Room.DOMAIN_INDOORS_AIR:
			return ("BGCOLOR=\"#FFFFFF\"");
		case Room.DOMAIN_INDOORS_MAGIC:
			return ("BGCOLOR=\"#996600\"");
		case Room.DOMAIN_INDOORS_METAL:
			return ("BGCOLOR=\"#996600\"");
		case Room.DOMAIN_INDOORS_CAVE:
			return ("BGCOLOR=\"#CC99FF\"");
		case Room.DOMAIN_INDOORS_STONE:
			if((R.phyStats().weight()>2)&&(R.maxRange()>4))
				return ("BGCOLOR=\"#CCCCCC\"");
			return ("BGCOLOR=\"#CC00FF\"");
		case Room.DOMAIN_INDOORS_UNDERWATER:
			if((R.phyStats().weight()>2)&&(R.maxRange()>4))
				return ("BGCOLOR=\"#CCCCCC\"");
			return ("BGCOLOR=\"#6666CC\"");
		case Room.DOMAIN_INDOORS_WATERSURFACE:
			if(!R.ID().startsWith("Shallow"))
				return ("BGCOLOR=\"#3399CC\"");
			else
				return ("BGCOLOR=\"#AAAAFF\"");
		case Room.DOMAIN_INDOORS_WOOD:
			return ("BGCOLOR=\"#999900\"");
		case Room.DOMAIN_OUTDOORS_AIR:
			return ("BGCOLOR=\"#FFFFFF\"");
		case Room.DOMAIN_OUTDOORS_CITY:
			return ("BGCOLOR=\"#CCCCCC\"");
		case Room.DOMAIN_OUTDOORS_SPACEPORT:
			return ("BGCOLOR=\"#CCCCCC\"");
		case Room.DOMAIN_OUTDOORS_DESERT:
			return ("BGCOLOR=\"#FFFF66\"");
		case Room.DOMAIN_OUTDOORS_SEAPORT:
		case Room.DOMAIN_INDOORS_SEAPORT:
		case Room.DOMAIN_INDOORS_CAVE_SEAPORT:
			return ("BGCOLOR=\"#FFFF66\"");
		case Room.DOMAIN_OUTDOORS_HILLS:
			return ("BGCOLOR=\"#99CC33\"");
		case Room.DOMAIN_OUTDOORS_JUNGLE:
			return ("BGCOLOR=\"#669966\"");
		case Room.DOMAIN_OUTDOORS_MOUNTAINS:
			return ("BGCOLOR=\"#996600\"");
		case Room.DOMAIN_OUTDOORS_PLAINS:
			if(R.ID().endsWith("Road"))
			{
				final int length=R.basePhyStats().weight();
				if(length < 5)
					return ("BGCOLOR=\"#CCCCCC\"");
				else
				if(length < 10)
					return ("BGCOLOR=\"#AAAAAA\"");
				else
				if(length < 15)
					return ("BGCOLOR=\"#888888\"");
				else
				if(length < 20)
					return ("BGCOLOR=\"#888888\"");
				else
					return ("BGCOLOR=\"#666666\"");
			}
			return ("BGCOLOR=\"#00FF00\"");
		case Room.DOMAIN_OUTDOORS_ROCKS:
			return ("BGCOLOR=\"#996600\"");
		case Room.DOMAIN_OUTDOORS_SWAMP:
			return ("BGCOLOR=\"#006600\"");
		case Room.DOMAIN_OUTDOORS_UNDERWATER:
			return ("BGCOLOR=\"#6666CC\"");
		case Room.DOMAIN_OUTDOORS_WATERSURFACE:
			if(!R.ID().startsWith("Shallow"))
				return ("BGCOLOR=\"#3399CC\"");
			else
				return ("BGCOLOR=\"#AAAAFF\"");
		case Room.DOMAIN_OUTDOORS_WOODS:
			return ("BGCOLOR=\"#009900\"");
		default:
			return ("BGCOLOR=\"#CCCCFF\"");
		}
	}

	protected GrinderRoom getRoomInDir(final GrinderRoom room, final int d)
	{
		GrinderRoom GR=null;
		int[] xy=newXY(room.xy,d);
		if((xy[0]>=0)&&(xy[1]>=0)
		&&(xy[0]<grid.length)&&(xy[1]<grid[xy[0]].length))
		{
			GR=grid[xy[0]][xy[1]];
			if(GR!=null)
				return GR;
			xy=newXY(xy,d);
		}
		return null;
	}

	protected int findRelGridDir(final GrinderRoom room, final String roomID)
	{
		for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
		{
			final GrinderRoom possRoom=getRoomInDir(room,d);
			if((possRoom!=null)&&(possRoom.roomID.equals(roomID)))
				return d;
		}
		return -1;
	}

	protected String getDoorLabelGif(final int d, final GrinderRoom room, final HTTPRequest httpReq)
	{
		if((room==null)
		||(room.doors==null)
		||(d>=room.doors.length))
			return "";

		String dirLetter=""+CMLib.directions().getDirectionChar(d);
		final GrinderDir dir=room.doors[d];
		GrinderRoom roomPointer=null;
		if((dir==null)||(dir.room.length()==0))
		{
			final Exit exit=(dir==null)?null:dir.exit;
			if(exit != null)
				return "<a href=\"javascript:EC('"+CMLib.directions().getDirectionName(d)+"','"+room.roomID+"');\"><IMG BORDER=0 SRC=\"images/UE"+dirLetter+".gif\"></a>";
			else
				return "<a href=\"javascript:EC('"+CMLib.directions().getDirectionName(d)+"','"+room.roomID+"');\"><IMG BORDER=0 SRC=\"images/E"+dirLetter+".gif\"></a>";
		}
		else
		if((d==Directions.UP)||(d==Directions.DOWN))
		{
			final int actualDir=findRelGridDir(room,dir.room);
			if(actualDir>=0)
				roomPointer=getRoomInDir(room,actualDir);
		}
		else
			roomPointer=getRoomInDir(room,d);

		final String dirName=CMLib.directions().getDirectionName(d);
		if((dir.room.length()>0)&&((roomPointer==null)||(!roomPointer.roomID.equals(dir.room))))
			dirLetter+="R";
		final String theRest=".gif\" BORDER=0 ALT=\""+dirName+" to "+dir.room+"\"></a>";
		final Exit exit=dir.exit;
		if(exit==null)
			return "<a href=\"javascript:CNEX('"+dirName+"','"+room.roomID+"','"+dir.room+"');\"><IMG SRC=\"images/U"+dirLetter+theRest;
		else
		if(exit.hasADoor())
			return "<a href=\"javascript:CEX('"+dirName+"','"+room.roomID+"','"+dir.room+"');\"><IMG SRC=\"images/D"+dirLetter+theRest;
		else
			return "<a href=\"javascript:CEX('"+dirName+"','"+room.roomID+"','"+dir.room+"');\"><IMG SRC=\"images/O"+dirLetter+theRest;
	}

	public int[] newXY(int[] xy, final int dir)
	{
		xy=xy.clone();
		switch(dir)
		{
		case Directions.NORTH:
			xy[1]--;
			break;
		case Directions.SOUTH:
			xy[1]++;
			break;
		case Directions.EAST:
			xy[0]++;
			break;
		case Directions.WEST:
			xy[0]--;
			break;
		case Directions.NORTHEAST:
			xy[1]--;
			xy[0]++;
			break;
		case Directions.NORTHWEST:
			xy[1]--;
			xy[0]--;
			break;
		case Directions.SOUTHEAST:
			xy[1]++;
			xy[0]++;
			break;
		case Directions.SOUTHWEST:
			xy[1]++;
			xy[0]--;
			break;
		case Directions.UP:
			xy[1]--;
			break;
		case Directions.DOWN:
			xy[1]++;
			break;
		}
		return xy;
	}

	public StringBuffer getHTMLMap(final HTTPRequest httpReq)
	{
		return getHTMLMap(httpReq, 4);
	}

	// this is much like getHTMLTable, but tiny rooms for world map viewing. No exits or ID's for now.
	public StringBuffer getHTMLMap(final HTTPRequest httpReq, final int roomSize)
	{
		final StringBuffer buf = new StringBuffer("");
		buf.append("<TABLE WIDTH=" + ( (Xbound + 1) * roomSize) +
				   " BORDER=0 CELLSPACING=0 CELLPADDING=0>");
		buf.append("<TR HEIGHT=" + roomSize + ">");
		for (int x = 0; x <= Xbound; x++)
			buf.append("<TD WIDTH=" + roomSize + " HEIGHT=" + roomSize +"></TD>");
		buf.append("</TR>");
		for (int y = 0; y <= Ybound; y++)
		{
			buf.append("<TR HEIGHT=" + roomSize + ">");
			for (int x = 0; x <= Xbound; x++)
			{
				final GrinderRoom GR = grid[x][y];
				final String tdins=(boundsXYXY!=null)?" ID=X"+(x+boundsXYXY[0])+"_"+(y+boundsXYXY[1]):"";
				if (GR == null)
					buf.append("<TD"+tdins+"></TD>");
				else
				{
					buf.append("<TD"+tdins+" ");
					if(!GR.isRoomGood())
						buf.append("BGCOLOR=BLACK");
					else
						buf.append(roomColorStyle(GR));
					buf.append("></TD>");
				}
			}
			buf.append("</TR>");
		}
		buf.append("</TABLE>");
		return buf;
	}
}
