package com.planet_ink.coffee_mud.Locales;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.smtp.SMTPserver;
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.Commands.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 com.planet_ink.coffee_mud.Libraries.interfaces.*;

import java.util.*;

/*
   Copyright 2004-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 StdThinGrid extends StdRoom implements GridLocale
{
	@Override
	public String ID()
	{
		return "StdThinGrid";
	}

	protected String[] 			descriptions=new String[0];
	protected String[] 			displayTexts=new String[0];
	protected SVector<CrossExit>gridexits=new SVector<CrossExit>(1);
	protected int 				xsize=5;
	protected int 				ysize=5;
	protected int 				yLength=1;
	protected Exit 				ox=null;
	protected final SVector<ThinGridEntry>
								rooms=new SVector<ThinGridEntry>();
	protected static boolean 	tickStarted=false;

	public StdThinGrid()
	{
		super();
		_roomID=getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
	}

	@Override
	protected void cloneFix(final Room E)
	{
		super.cloneFix(E);
		if(E instanceof StdThinGrid)
		{
			descriptions=((StdThinGrid)E).descriptions.clone();
			displayTexts=((StdThinGrid)E).displayTexts.clone();
			gridexits=((StdThinGrid)E).gridexits.copyOf();
		}
	}

	protected static class ThinGridEntryConverter implements Converter<ThinGridEntry, Room>
	{
		public static ThinGridEntryConverter INSTANCE = new ThinGridEntryConverter();

		@Override
		public Room convert(final ThinGridEntry obj)
		{
			return obj.room;
		}
	}

	protected static class ThinGridEntry
	{
		public Room room;
		public XYVector xy;

		public ThinGridEntry(final Room R, final int x, final int y)
		{
			room = R;
			xy = new XYVector(x, y);
		}
	}

	@Override
	public String getGridChildLocaleID()
	{
		return "StdRoom";
	}

	@Override
	public int xGridSize()
	{
		return xsize;
	}

	@Override
	public int yGridSize()
	{
		return ysize;
	}

	@Override
	public int getGridSize()
	{
		return xsize * ysize;
	}

	@Override
	public void setXGridSize(final int x)
	{
		if(x>0)
			xsize=x;
	}

	@Override
	public void setYGridSize(final int y)
	{
		if(y>0)
			ysize=y;
		yLength=Integer.toString(ysize).length();
	}

	@Override
	public void destroy()
	{
		super.destroy();
		Room R=null;
		for(int i=rooms.size()-1;i>=0;i--)
		{
			R=rooms.elementAt(i).room;
			if(R!=null)
				R.destroy();
		}
		synchronized(rooms)
		{
			rooms.clear();
		}
		descriptions=new String[0];
		displayTexts=new String[0];
		gridexits=new SVector<CrossExit>(1);
	}

	@Override
	public void setDescription(String newDescription)
	{
		super.setDescription(newDescription);
		final Vector<String> descriptions=new Vector<String>();
		int x=newDescription.toUpperCase().indexOf("<P>");
		while(x>=0)
		{
			final String s=newDescription.substring(0,x).trim();
			if(s.length()>0)
				descriptions.addElement(s);
			newDescription=newDescription.substring(x+3).trim();
			x=newDescription.toUpperCase().indexOf("<P>");
		}
		if(newDescription.length()>0)
			descriptions.addElement(newDescription);
		this.descriptions = descriptions.toArray(new String[0]);
	}

	@Override
	public void setDisplayText(String newDisplayText)
	{
		super.setDisplayText(newDisplayText);
		final Vector<String> displayTexts=new Vector<String>();
		int x=newDisplayText.toUpperCase().indexOf("<P>");
		while(x>=0)
		{
			final String s=newDisplayText.substring(0,x).trim();
			if(s.length()>0)
				displayTexts.addElement(s);
			newDisplayText=newDisplayText.substring(x+3).trim();
			x=newDisplayText.toUpperCase().indexOf("<P>");
		}
		if(newDisplayText.length()>0)
			displayTexts.addElement(newDisplayText);
		this.displayTexts = displayTexts.toArray(new String[0]);
	}

	@Override
	public Room prepareRoomInDir(final Room fromRoom, final int direction)
	{
		if(amDestroyed)
		{
			final Room R=CMLib.map().getRoom(roomID());
			if(R!=null)
				return R.prepareRoomInDir(fromRoom,direction);
			return super.prepareRoomInDir(fromRoom,direction);
		}
		return getAltRoomFrom(fromRoom,direction);
	}

	// already synchronized by callers -- do not synchronize
	private int properRoomIndex(final int x, final int y)
	{
		if(rooms.size()==0)
			return 0;
		int start=0;
		int end=rooms.size()-1;
		int comp=0;
		final long total=((long)x<<31)+y;
		long comptotal=0;
		int mid=0;
		int tries = -1;
		ThinGridEntry entry;
		while((start<=end)&&(tries++<rooms.size()))
		{
			mid=(end+start)/2;
			try
			{
				entry = rooms.elementAt(mid);
				comptotal=((long)entry.xy.x<<31)+entry.xy.y;
				comp=comptotal>total?1:(comptotal==total)?0:-1;
				if(comp==0)
					return mid;
				else
				if(comp>0)
					end=mid-1;
				else
					start=mid+1;
			}
			catch(final java.lang.IndexOutOfBoundsException e)
			{
				comp=0;
				mid=0;
				start=0;
				end=rooms.size()-1;
			}
		}
		if(end<0)
			return 0;
		if(start>=rooms.size())
			return rooms.size()-1;
		return mid;
	}

	private void addSortedRoom(final Room R, final int x, final int y)
	{
		if((x<0)||(y<0)||(y>=yGridSize())||(x>=xGridSize())||(R==null))
			return ;
		synchronized(rooms)
		{
			final long total=((long)x<<31)+y;
			final int pos=properRoomIndex(x,y);
			final ThinGridEntry entry = new ThinGridEntry(R,x,y);
			if(pos>=rooms.size())
			{
				rooms.addElement(entry);
				return;
			}
			final long comptotal=((long)rooms.elementAt(pos).xy.x<<31)+rooms.elementAt(pos).xy.y;
			final int comp=comptotal>total?1:(comptotal==total)?0:-1;
			if(comp==0)
				return;
			if(comp>0)
				rooms.add(pos,entry);
			else
			if(pos==rooms.size()-1)
				rooms.add(entry);
			else
				rooms.add(pos+1,entry);
		}
	}

	private Room getSortedRoom(final int x, final int y)
	{
		if(rooms.size()==0)
			return null;
		synchronized(rooms)
		{
			final int pos=properRoomIndex(x,y);
			final ThinGridEntry entry = rooms.elementAt(pos);
			if((entry.xy.x==x)&&(entry.xy.y==y))
				return entry.room;
		}
		return null;
	}

	protected Room getGridRoomIfExists(final int x, final int y)
	{
		final Room R=getSortedRoom(x,y);
		if(R!=null)
			R.setExpirationDate(System.currentTimeMillis()+WorldMap.ROOM_EXPIRATION_MILLIS);
		return R;
	}

	@Override
	public Room prepareGridLocale(final Room fromRoom, final Room toRoom, final int direction)
	{
		final XYVector xy=getRoomXY(fromRoom);
		if((xy!=null)&&(xy.x>=0)&&(xy.x<xGridSize())&&(xy.y>=0)&&(xy.y<yGridSize()))
			fillExitsOfGridRoom(fromRoom,xy.x,xy.y);
		return fromRoom.rawDoors()[direction];
	}

	@Override
	public Room getGridChild(final int x, final int y)
	{
		return getMakeGridRoom(x,y);
	}

	@Override
	public Room getGridChild(final XYVector xy)
	{
		return getGridChild(xy.x,xy.y);
	}

	protected Room getMakeSingleGridRoom(final int x, final int y)
	{
		if((x<0)||(y<0)||(y>=yGridSize())||(x>=xGridSize()))
			return null;

		Room R=getGridRoomIfExists(x,y);
		if(R==null)
		{
			synchronized(rooms)
			{
				R=getGridRoomIfExists(x,y);
				if(R!=null)
					return R;
				R=CMClass.getLocale(getGridChildLocaleID());
				if(R==null)
					return null;
				R.setGridParent(this);
				R.setRoomID("");
				R.setDisplayText(displayText());
				R.setDescription(description());
				int c=-1;
				if(displayTexts!=null)
				if(displayTexts.length>0)
				{
					c=CMLib.dice().roll(1,displayTexts.length,-1);
					R.setDisplayText(displayTexts[c]);
				}
				if(descriptions!=null)
				if(descriptions.length>0)
				{
					if((c<0)||(c>descriptions.length)||(descriptions.length!=displayTexts.length))
						c=CMLib.dice().roll(1,descriptions.length,-1);
					R.setDescription(descriptions[c]);
				}

				for(int a=0;a<numEffects();a++)
					R.addEffect((Ability)fetchEffect(a).copyOf());
				if(behaviors != null)
				{
					for(final Behavior B : behaviors)
						R.addBehavior((Behavior)B.copyOf());
				}
				R.setExpirationDate(System.currentTimeMillis()+WorldMap.ROOM_EXPIRATION_MILLIS);
				addSortedRoom(R,x,y);
				R.setArea(getArea());
				//R.giveASky(0);
			}
		}
		return R;
	}

	@Override
	public XYVector getRoomXY(final Room room)
	{
		if((room.getGridParent()!=this)
		&&(room.getGridParent()!=null))
			return null;
		try
		{
			for(final ThinGridEntry entry : rooms)
			{
				if(entry.room==room)
					return entry.xy;
			}
		}
		catch(final Exception x)
		{
		}
		return null;
	}

	@Override
	public XYVector getRoomXY(final String roomID)
	{
		final Room room=CMLib.map().getRoom(roomID);
		if(room==null)
			return null;
		return getRoomXY(room);
	}

	protected void fillExitsOfGridRoom(final Room R, final int x, final int y)
	{
		if((x<0)||(y<0)||(y>=yGridSize())||(x>=xGridSize()))
			return;

		synchronized(R.basePhyStats())
		{
			final int mask=R.basePhyStats().sensesMask();
			if(CMath.bset(mask,PhyStats.SENSE_ROOMGRIDSYNC))
				return;
			R.basePhyStats().setSensesMask(mask|PhyStats.SENSE_ROOMGRIDSYNC);
		}

		// the adjacent rooms created by this method should also take
		// into account the possibility that they are on the edge.
		// it does NOT
		if(ox==null)
			ox=CMClass.getExit("Open");
		Room R2=null;
		if(y>0)
		{
			R2=getMakeSingleGridRoom(x,y-1);
			if(R2!=null)
				linkRoom(R,R2,Directions.NORTH,ox,ox);
		}
		else
		if((rawDoors()[Directions.NORTH]!=null)&&(exits[Directions.NORTH]!=null))
			linkRoom(R,rawDoors()[Directions.NORTH],Directions.NORTH,exits[Directions.NORTH],exits[Directions.NORTH]);

		if(x>0)
		{
			R2=getMakeSingleGridRoom(x-1,y);
			if(R2!=null)
				linkRoom(R,R2,Directions.WEST,ox,ox);
		}
		else
		if((rawDoors()[Directions.WEST]!=null)&&(exits[Directions.WEST]!=null))
			linkRoom(R,rawDoors()[Directions.WEST],Directions.WEST,exits[Directions.WEST],exits[Directions.WEST]);
		if(y<(yGridSize()-1))
		{
			R2=getMakeSingleGridRoom(x,y+1);
			if(R2!=null)
				linkRoom(R,R2,Directions.SOUTH,ox,ox);
		}
		else
		if((rawDoors()[Directions.SOUTH]!=null)&&(exits[Directions.SOUTH]!=null))
			linkRoom(R,rawDoors()[Directions.SOUTH],Directions.SOUTH,exits[Directions.SOUTH],exits[Directions.SOUTH]);

		if(x<(xGridSize()-1))
		{
			R2=getMakeSingleGridRoom(x+1,y);
			if(R2!=null)
				linkRoom(R,R2,Directions.EAST,ox,ox);
		}
		else
		if((rawDoors()[Directions.EAST]!=null)&&(exits[Directions.EAST]!=null))
			linkRoom(R,rawDoors()[Directions.EAST],Directions.EAST,exits[Directions.EAST],exits[Directions.EAST]);

		if(Directions.NORTHEAST<Directions.NUM_DIRECTIONS())
		{
			if((y>0)&&(x>0))
			{
				R2=getMakeSingleGridRoom(x-1,y-1);
				if(R2!=null)
					linkRoom(R,R2,Directions.NORTHWEST,ox,ox);
			}
			else
			if((rawDoors()[Directions.NORTHWEST]!=null)&&(exits[Directions.NORTHWEST]!=null))
				linkRoom(R,rawDoors()[Directions.NORTHWEST],Directions.NORTHWEST,exits[Directions.NORTHWEST],exits[Directions.NORTHWEST]);

			if((x>0)&&(y<(yGridSize()-1)))
			{
				R2=getMakeSingleGridRoom(x-1,y+1);
				if(R2!=null)
					linkRoom(R,R2,Directions.SOUTHWEST,ox,ox);
			}
			else
			if((rawDoors()[Directions.SOUTHWEST]!=null)&&(exits[Directions.SOUTHWEST]!=null))
				linkRoom(R,rawDoors()[Directions.SOUTHWEST],Directions.SOUTHWEST,exits[Directions.SOUTHWEST],exits[Directions.SOUTHWEST]);

			if((x<(xGridSize()-1))&&(y>0))
			{
				R2=getMakeSingleGridRoom(x+1,y-1);
				if(R2!=null)
					linkRoom(R,R2,Directions.NORTHEAST,ox,ox);
			}
			else
			if((rawDoors()[Directions.NORTHEAST]!=null)&&(exits[Directions.NORTHEAST]!=null))
				linkRoom(R,rawDoors()[Directions.NORTHEAST],Directions.NORTHEAST,exits[Directions.NORTHEAST],exits[Directions.NORTHEAST]);
			if((x<(xGridSize()-1))&&(y<(yGridSize()-1)))
			{
				R2=getMakeSingleGridRoom(x+1,y+1);
				if(R2!=null)
					linkRoom(R,R2,Directions.SOUTHEAST,ox,ox);
			}
			else
			if((rawDoors()[Directions.SOUTHEAST]!=null)&&(exits[Directions.SOUTHEAST]!=null))
				linkRoom(R,rawDoors()[Directions.SOUTHEAST],Directions.SOUTHEAST,exits[Directions.SOUTHEAST],exits[Directions.SOUTHEAST]);
		}

		for(int d=0;d<gridexits.size();d++)
		{
			final CrossExit EX=gridexits.elementAt(d);
			try
			{
				if((EX.out)&&(EX.x==x)&&(EX.y==y))
				{
					switch(EX.dir)
					{
					case Directions.NORTH:
						if(EX.y==0)
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					case Directions.SOUTH:
						if(EX.y==yGridSize()-1)
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					case Directions.EAST:
						if(EX.x==xGridSize()-1)
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					case Directions.WEST:
						if(EX.x==0)
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					case Directions.NORTHEAST:
						if((EX.y==0)&&(EX.x==xGridSize()-1))
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					case Directions.SOUTHWEST:
						if((EX.y==yGridSize()-1)&&(EX.x==0))
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					case Directions.NORTHWEST:
						if((EX.y==0)&&(EX.x==0))
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					case Directions.SOUTHEAST:
						if((EX.y==yGridSize()-1)&&(EX.x==xGridSize()-1))
							tryFillInExtraneousExternal(EX,ox,R);
						break;
					}
				}
			}
			catch(final Exception e)
			{
			}
		}
		synchronized(R.basePhyStats())
		{
			R.basePhyStats().setSensesMask(CMath.unsetb(R.basePhyStats().sensesMask(),PhyStats.SENSE_ROOMGRIDSYNC));
		}
	}

	public void tryFillInExtraneousExternal(final CrossExit EX, Exit ox, final Room linkFrom)
	{
		if(EX==null)
			return;
		Room linkTo=CMLib.map().getRoom(EX.destRoomID);
		if((linkTo!=null)&&(linkTo.getGridParent()!=null))
			linkTo=linkTo.getGridParent();
		if((linkTo!=null)&&(linkFrom.rawDoors()[EX.dir]!=linkTo))
		{
			if(ox==null)
				ox=CMClass.getExit("Open");
			linkFrom.rawDoors()[EX.dir]=linkTo;
			linkFrom.setRawExit(EX.dir,ox);
		}
	}

	protected Room getMakeGridRoom(final int x, final int y)
	{
		if((x<0)||(y<0)||(y>=yGridSize())||(x>=xGridSize()))
			return null;

		final Room R=getMakeSingleGridRoom(x,y);
		if(R==null)
			return null;
		fillExitsOfGridRoom(R,x,y);
		return R;
	}

	@Override
	public Iterator<CrossExit> outerExits()
	{
		return gridexits.iterator();
	}

	@Override
	public void delOuterExit(final CrossExit x)
	{
		gridexits.remove(x);
	}

	@Override
	public void addOuterExit(final CrossExit x)
	{
		gridexits.addElement(x);
	}

	public Room getAltRoomFrom(Room loc, final int direction)
	{
		if((loc==null)||(direction<0))
			return null;
		final int opDirection=Directions.getOpDirectionCode(direction);

		final String roomID=CMLib.map().getExtendedRoomID(loc);
		for(int d=0;d<gridexits.size();d++)
		{
			final CrossExit EX=gridexits.elementAt(d);
			if((!EX.out)
			&&(EX.destRoomID.equalsIgnoreCase(roomID))
			&&(EX.dir==direction)
			&&(EX.x>=0)&&(EX.y>=0)&&(EX.x<xGridSize())&&(EX.y<yGridSize()))
				return getMakeGridRoom(EX.x,EX.y);
		}

		final Room oldLoc=loc;
		if(loc.getGridParent()!=null)
			loc=loc.getGridParent();
		if((oldLoc!=loc)
		&&(loc instanceof GridLocale))
		{
			final XYVector xy=((GridLocale)loc).getRoomXY(oldLoc);
			if(xy != null)
			{
				if((xy.x>=0)&&(xy.y>=0))
				{
					switch(opDirection)
					{
					case Directions.EAST:
						if((((GridLocale)loc).yGridSize()==yGridSize()))
							return getMakeGridRoom(xGridSize()-1,xy.y);
						break;
					case Directions.WEST:
						if((((GridLocale)loc).yGridSize()==yGridSize()))
							return getMakeGridRoom(0,xy.y);
						break;
					case Directions.NORTH:
						if((((GridLocale)loc).xGridSize()==xGridSize()))
							return getMakeGridRoom(xy.x,0);
						break;
					case Directions.NORTHWEST:
						return getMakeGridRoom(0,0);
					case Directions.SOUTHEAST:
						return getMakeGridRoom(xGridSize()-1,yGridSize()-1);
					case Directions.NORTHEAST:
						return getMakeGridRoom(xGridSize()-1,0);
					case Directions.SOUTHWEST:
						return getMakeGridRoom(0,yGridSize()-1);
					case Directions.SOUTH:
						if((((GridLocale)loc).xGridSize()==xGridSize()))
							return getMakeGridRoom(xy.x,yGridSize()-1);
						break;
					}
				}
			}
		}
		int x=0;
		int y=0;
		switch(opDirection)
		{
		case Directions.NORTH:
			x=xGridSize()/2;
			break;
		case Directions.SOUTH:
			x=xGridSize()/2;
			y=yGridSize()-1;
			break;
		case Directions.EAST:
			x=xGridSize()-1;
			y=yGridSize()/2;
			break;
		case Directions.WEST:
			y=yGridSize()/2;
			break;
		case Directions.NORTHWEST:
			x=0;
			y=0;
			break;
		case Directions.NORTHEAST:
			x=xGridSize()-1;
			y=0;
			break;
		case Directions.SOUTHWEST:
			x=0;
			y=yGridSize()-1;
			break;
		case Directions.SOUTHEAST:
			x=xGridSize()-1;
			y=yGridSize()-1;
			break;
		case Directions.UP:
			x=xGridSize()/2;
			y=yGridSize()/2;
			break;
		case Directions.DOWN:
			x=xGridSize()/2;
			y=yGridSize()-1;
			break;
		}
		return getMakeGridRoom(x,y);
	}

	@Override
	public List<Room> getAllRooms()
	{
		getRandomGridChild();
		return new ConvertingList<ThinGridEntry,Room>(rooms,ThinGridEntryConverter.INSTANCE);
	}

	@Override
	public List<Room> getAllRoomsFilled()
	{
		if(rooms.size()==0)
			getRandomGridChild();
		final Iterator<Room> r=new ConvertingList<ThinGridEntry,Room>(rooms,ThinGridEntryConverter.INSTANCE).iterator();
		final Vector<Room> V=new Vector<Room>();
		Room R=null;
		for(;r.hasNext();)
		{
			R=r.next();
			try
			{
				if(!V.contains(R))
				{
					V.addElement(R);
					for(final Room R2 : R.getSky())
					{
						if(R2 instanceof GridLocale)
						{
							for(final Room R3 : ((GridLocale)R2).getAllRoomsFilled())
							{
								if(!V.contains(R3))
									V.add(R3);
							}
						}
						else
						if(!V.contains(R2))
							V.add(R2);
					}
				}
			}
			catch(final Exception e)
			{
			}
		}
		return V;
	}

	@Override
	public Iterator<Room> getExistingRooms()
	{
		return new ConvertingIterator<ThinGridEntry,Room>(rooms.iterator(),ThinGridEntryConverter.INSTANCE);
	}

	protected Room alternativeLink(final Room room, final Room defaultRoom, final int dir)
	{
		if(room.getGridParent()==this)
		for(int d=0;d<gridexits.size();d++)
		{
			final CrossExit EX=gridexits.elementAt(d);
			try
			{
				if((EX.out)&&(EX.dir==dir)
				&&(getGridRoomIfExists(EX.x,EX.y)==room))
				{
					final Room R=CMLib.map().getRoom(EX.destRoomID);
					if(R!=null)
					{
						if(R.getGridParent()!=null)
							return R.getGridParent();
						return R;
					}
				}
			}
			catch(final Exception e)
			{
			}
		}
		return defaultRoom;
	}

	protected void halfLink(final Room room, final Room loc, final int dirCode, Exit o)
	{
		if(room==null)
			return;
		if(loc==null)
			return;
		if(room.rawDoors()[dirCode]!=null)
		{
			if(room.rawDoors()[dirCode].getGridParent()==null)
				return;
			if(room.rawDoors()[dirCode].getGridParent().isMyGridChild(room.rawDoors()[dirCode]))
				return;
			room.rawDoors()[dirCode]=null;
		}
		if(o==null)
			o=CMClass.getExit("Open");
		room.rawDoors()[dirCode]=alternativeLink(room,loc,dirCode);
		room.setRawExit(dirCode,o);
	}

	protected void linkRoom(final Room room, final Room loc, final int dirCode, Exit o, Exit ao)
	{
		if(loc==null)
			return;
		if(room==null)
			return;
		final int opCode=Directions.getOpDirectionCode(dirCode);
		final Room dirR=room.rawDoors()[dirCode];
		if(dirR!=null)
		{
			final GridLocale dirRparentR=dirR.getGridParent();
			if(dirRparentR==null)
				return;
			if(dirRparentR.isMyGridChild(dirR))
				return;
			room.rawDoors()[dirCode]=null;
		}
		if(o==null)
			o=CMClass.getExit("Open");
		room.rawDoors()[dirCode]=alternativeLink(room,loc,dirCode);
		room.setRawExit(dirCode,o);
		final Room opR=loc.rawDoors()[opCode];
		if(opR!=null)
		{
			final GridLocale opRgR=opR.getGridParent();
			if(opRgR==null)
				return;
			if(opRgR.isMyGridChild(opR))
				return;
			loc.rawDoors()[opCode]=null;
		}
		loc.rawDoors()[opCode]=alternativeLink(loc,room,opCode);
		if(ao==null)
		{
			if((room.getGridParent()==loc.getGridParent())
			&&(loc.rawDoors()[opCode]==room))
				ao=o;
			else
				ao=CMClass.getExit("Open");
		}
		loc.setRawExit(opCode,ao);
	}

	@Override
	public void buildGrid()
	{
		clearGrid(null);
	}

	@Override
	public boolean isMyGridChild(final Room loc)
	{
		if(loc==null)
			return false;
		if(loc.getGridParent()==this)
			return true;
		if(loc.getGridParent()!=null)
			return false;
		try
		{
			for(final ThinGridEntry entry : rooms)
			{
				if(loc == entry.room)
					return true;
			}
		}
		catch (final Exception e)
		{
		} // optimization
		return false;
	}

	@Override
	public void clearGrid(final Room bringBackHere)
	{
		try
		{
			final WorldMap mapper=CMLib.map();
			for(final ThinGridEntry entry : rooms)
				mapper.emptyRoom(entry.room,bringBackHere,true);
			for(final ThinGridEntry entry : rooms)
				entry.room.destroy();
			synchronized(rooms)
			{
				rooms.clear();
			}
		}
		catch (final Exception e)
		{
			Log.debugOut("StdThinGrid", e);
		}
	}

	@Override
	public String getGridChildCode(final Room loc)
	{
		try
		{
			for(final ThinGridEntry entry : rooms)
			{
				if(entry.room==loc)
					return roomID()+"#("+entry.xy.x+","+entry.xy.y+")";
			}
		}
		catch(final Exception x)
		{
		}
		return "";
	}

	@Override
	public int getGridChildX(final Room loc)
	{
		try
		{
			for(final ThinGridEntry entry : rooms)
				if(entry.room==loc)
					return entry.xy.x;
		}
		catch(final Exception x)
		{
		}
		return -1;
	}

	@Override
	public Room getRandomGridChild()
	{
		final int x=CMLib.dice().roll(1,xGridSize(),-1);
		final int y=CMLib.dice().roll(1,yGridSize(),-1);
		final Room R=getMakeGridRoom(x,y);
		if(R==null)
			Log.errOut("StdThinGrid",roomID()+" failed to get a random child!");
		return R;
	}

	@Override
	public int getGridChildY(final Room loc)
	{
		try
		{
			for(final ThinGridEntry entry : rooms)
			{
				if(entry.room==loc)
					return entry.xy.y;
			}
		}
		catch(final Exception x)
		{
		}
		return -1;
	}

	@Override
	public Room getGridChild(final String childCode)
	{
		if(childCode.equalsIgnoreCase(roomID()))
			return this;
		if(!childCode.toUpperCase().startsWith(roomID().toUpperCase()+"#("))
			return null;
		final int len=roomID().length()+2;
		final int comma=childCode.indexOf(',',len);
		if(comma<0)
			return null;
		final int x=CMath.s_int(childCode.substring(len,comma));
		final int y=CMath.s_int(childCode.substring(comma+1,childCode.length()-1));
		return getMakeGridRoom(x,y);
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		if((msg.targetMinor()==CMMsg.TYP_EXPIRE)
		&&(msg.target() instanceof Room))
		{
			final Room R=(Room)msg.target();
			if(R.getGridParent()==this)
			{
				if((roomID().length()>0)&&(getArea()!=null))
					getArea().delProperRoomnumber(getGridChildCode(R));
				try
				{
					synchronized(rooms)
					{
						for(final ThinGridEntry entry : rooms)
						{
							if(entry.room==R)
								rooms.remove(entry);
						}
					}
					for(final ThinGridEntry entry : rooms)
					{
						for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
						{
							if(entry.room.rawDoors()[d]==R)
							{
								entry.room.rawDoors()[d]=null;
								entry.room.setRawExit(d,null);
							}
						}
					}
				}
				catch(final Exception x)
				{
				}
			}
		}
		super.executeMsg(myHost,msg);
	}

	@Override
	public boolean okMessage(final Environmental myHost, final CMMsg msg)
	{
		if(!super.okMessage(myHost,msg))
			return false;

		if(msg.targetMinor()==CMMsg.TYP_ENTER)
		{
			if(msg.target()==this)
			{
				final MOB mob=msg.source();
				if((mob.location()!=null)&&(mob.location().roomID().length()>0))
				{
					int direction=-1;
					for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
					{
						if(mob.location().getRoomInDir(d)==this)
							direction=d;
					}
					if(direction<0)
					{
						mob.tell(L("Some great evil is preventing your movement that way."));
						return false;
					}
					msg.modify(msg.source(),
							   getAltRoomFrom(mob.location(),direction),
							   msg.tool(),
							   msg.sourceCode(),
							   msg.sourceMessage(),
							   msg.targetCode(),
							   msg.targetMessage(),
							   msg.othersCode(),
							   msg.othersMessage());
				}
			}
		}
		return true;
	}

	private final static String[] MYCODES={"XSIZE","YSIZE"};

	@Override
	public String getStat(final String code)
	{
		switch(getStdGridCodeNum(code))
		{
		case 0:
			return Integer.toString(xGridSize());
		case 1:
			return Integer.toString(yGridSize());
		default:
			return super.getStat(code);
		}
	}

	@Override
	public void setStat(final String code, final String val)
	{
		switch(getStdGridCodeNum(code))
		{
		case 0:
			setXGridSize(CMath.s_parseIntExpression(val));
			break;
		case 1:
			setYGridSize(CMath.s_parseIntExpression(val));
			break;
		default:
			super.setStat(code, val);
			break;
		}
	}

	protected int getStdGridCodeNum(final String code)
	{
		for(int i=0;i<MYCODES.length;i++)
		{
			if(code.equalsIgnoreCase(MYCODES[i]))
				return i;
		}
		return -1;
	}

	private static String[] codes=null;

	@Override
	public String[] getStatCodes()
	{
		return (codes != null) ? codes : (codes =  CMProps.getStatCodesList(CMParms.appendToArray(STDCODES, MYCODES),this));
	}
}
