/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	Jacob's Matrix Development
 *
 * NAME:        map.h ( Jacob's Matrix, C++ )
 *
 * COMMENTS:
 */

#ifndef __map__
#define __map__

#include <iostream>
using namespace std;
#include <memory>
#include <unordered_map>

#include "ptrlist.h"
#include "thread.h"
#include "glbdef.h"
#include "gamedef.h"
#include "texture.h"
#include "indextree.h"
#include "vec2.h"
#include "speed.h"

class POS;
class ROOM;
class MOB;
class ITEM;
class MAP;
class FRAGMENT;
class DISPLAY;
class SCRPOS;
class MOBDELAY;
class MESSAGE_PACKET;
class MULTIVERSE;

typedef PTRLIST<ROOM *> ROOMLIST;
typedef PTRLIST<MOB *> MOBLIST;
typedef PTRLIST<ITEM *> ITEMLIST;
typedef PTRLIST<POS> POSLIST;
typedef PTRLIST<FRAGMENT *> FRAGMENTLIST;
typedef PTRLIST<MOBDELAY> MOBDELAYLIST;

// Number of distance map caches we maintain.
#define DISTMAP_CACHE	10
#define DISTMAP_CACHE_ALLOC	DISTMAP_CACHE

using SCRPOS_PTR = std::shared_ptr<SCRPOS>;

using MAP_PTR = std::shared_ptr<MAP>;

using MAPLIST = PTRLIST<MAP_PTR>;
using MAPFROMPOS = std::unordered_map<POS, MAPLIST>;
using INTFROMPOS = std::unordered_map<POS, int>;

using MULTIVERSE_PTR = std::shared_ptr<MULTIVERSE>;



// The position is the only way to access elements of the map
// because it can adjust for orientation, etc.
// Functions that modify the underlying square are const if they
// leave this unchanged...
class POS
{
public:
    POS();

    POS  	goToCanonical(int x, int y) const;

    /// Equality ignores angles.
    bool 	operator==(const POS &cmp) const
    {	return cmp.myX == myX && cmp.myY == myY && cmp.myRoomId == myRoomId; }
    bool 	operator!=(const POS &cmp) const
    {	return cmp.myX != myX || cmp.myY != myY || cmp.myRoomId != myRoomId; }

    POS		left() const { return delta(-1, 0); }
    POS		up() const { return delta(0, -1); }
    POS		down() const { return delta(0, 1); }
    POS		right() const { return delta(1, 0); }

    int		angle() const { return myAngle; }

    POS		delta4Direction(int angle) const;
    POS		delta(int dx, int dy) const;

    // Returns invalid if none possible:
    POS		randAdjacent(bool checkmob = true, bool checkpassable = true) const;

    POS		rotate(int angle) const;
    void	setAngle(int angle);

    bool	valid() const;

    /// Compute number of squares to get to goal, 8 way metric.
    /// This is an approximation, ignoring obstacles
    int		dist(POS goal) const;

    ROOMTYPE_NAMES roomType() const;
    const ROOMTYPE_DEF &roomDefn() const { return glb_roomtypedefs[roomType()]; }

    /// Returns an estimate of what should be delta()d to this
    /// to move closer to goal.  Again, an estimate!  Traditionally
    /// just SIGN.
    void	dirTo(POS goal, int &dx, int &dy) const;

    /// Returns the offset to goal from this.  Ie, the comlete amount
    /// to delta() to get to the goal.  An estimate, returns false if
    /// cannot compute.
    bool	deltaTo(POS goal, int &dx, int &dy) const;

    /// Returns the first mob along the given vector from here.
    MOB		*traceBullet(int range, int dx, int dy, int *rangeleft=0) const;

    /// Returns the last valid pos before we hit a wall, if stop
    /// before wall set.  Otherwise returns the wall hit.
    POS		 traceBulletPos(int range, int dx, int dy, bool stopbeforewall, bool stopatmob = true) const;

    /// Triggers a ray of a given length including this location.
    /// Call back with OP to everything in length.
    /// Return true if any callback returns true.
    /// If enterop is false, doesn't attempt to enter the pos.
    /// Propagation stops if passop fails.
    template <typename OP, typename ENTEROP, typename PASSOP>
    bool	ray(int range, int dx, int dy, MOB *caster, u8 sym, ATTR_NAMES attr, const OP &op, const ENTEROP &enterop, const PASSOP &passop) const
    {
	bool		anyop = false;
	POS		p = *this;

	while (range--)
	{
	    if (!enterop(p))
		break;
	    p.postEvent(EVENTTYPE_FORESYM, sym, attr);
	    anyop |= op(p);
	    if (!passop(p))
		break;
	    p = p.delta(dx, dy);
	}
	return anyop;
    }

    /// Triggers a cone of a given radius from this location.
    /// Call back with OP to everything in radius.
    /// Return true if any callback returns true.
    /// Propagation stops if canpass fails.
    template <typename OP, typename PASSOP>
    bool	cone(int dx, int dy, MOB *caster, int rad, u8 sym, ATTR_NAMES attr, const OP &op, const PASSOP &passop) const
    {
	// Boy do I hate C++ templates not supporting proper exports.
	int		pdx = dy, pdy = -dx;

	POSLIST		wavefront;
	bool		anyhit = false;

	wavefront.append(*this);
	for (int r = 0; r < rad; r++)
	{
	    POSLIST	nextwave;

	    auto add_to_wave = [&](POS p)
	    {
		if (p.valid() && !nextwave.contains(p))
		    nextwave.append(p);
	    };
	    for (auto && pos : wavefront)
	    {
		// Ignore if it is stopped.
		if (!passop(pos))
		    continue;

		if (!pos.valid())
		    continue;
		pos.postEvent(EVENTTYPE_FORESYM, sym, attr);

		if (op(pos))
		    anyhit = true;

		// Propagate...
		if (dx && dy)
		{
		    //  *
		    //  **
		    //  ***
		    // @
		    add_to_wave(pos.delta(dx, 0));
		    add_to_wave(pos.delta(0, dy));
		}
		else
		{
		    //    *
		    //   **
		    // @***
		    //   **
		    //    *
		    POS		np = pos.delta(dx, dy);
		    add_to_wave(np);
		    add_to_wave(np.delta(pdx, pdy));
		    add_to_wave(np.delta(-pdx, -pdy));
		}
	    }
	    wavefront = nextwave;
	}
	return anyhit;
    }

    /// Triggers a fireball of given radius at this location.  Calls back
    /// with OP to everything in radius.  Returns true if any of the call
    /// backs returned true.  (Usually signifies someone was hit...)
    /// 
    template <typename OP>
    bool	 fireball(MOB *caster, int rad, u8 sym, ATTR_NAMES attr, const OP &op) const
    {
	// Boy do I hate C++ templates not supporting proper exports.
	int		dx, dy;

	POS		target;
	bool		anyhit = false;

	for (dy = -rad+1; dy <= rad-1; dy++)
	{
	    for (dx = -rad+1; dx <= rad-1; dx++)
	    {
		target = delta(dx, dy);
		if (target.valid())
		{
		    target.postEvent(EVENTTYPE_FORESYM, sym, attr);

		    anyhit |= op(target);
		}
	    }
	}
	return anyhit;
    }

    void	makeNoise(int loudness) const;

    /// Dumps to message description of this square
    void	 describeSquare(bool blind) const;
    /// prompt then chamber descr then .
    void	 describeChamber(const char * prompt) const;

    /// Dumps to message about the fireball, provided center is in
    /// fov and non-trivial
    void	 reportFireball(int radius, const char *fireball) const;
    /// Likewise for cones.
    void	 reportCone(int dx, int dy, int range, const char *fireball) const;
    /// Likewise for rays.
    void	 reportRay(int dx, int dy, const char *fireball) const;

    // Dumps a message from our location if in fov.
    void	 formatAndReport(const char *msg) const;
    void	 formatAndReport(BUF buf) const { formatAndReport(buf.buffer()); }
    void 	 formatAndReport(const char *msg, const MOB *subject) const;
    void 	 formatAndReport(BUF buf, const MOB *subject) const { formatAndReport(buf.buffer(), subject); }
    void 	 formatAndReport(const char *msg, const ITEM *subject) const;
    void 	 formatAndReport(BUF buf, const ITEM *subject) const { formatAndReport(buf.buffer(), subject); }

    // Report mapped exits and visible mobs & items.
    // Returns chamber type.  If between chambers, CHAMBER_NONE.
    CHAMBER_NAMES scanChamber(POSLIST &exits, POSLIST &moblocs, POSLIST &itemlocs) const;
    CHAMBER_NAMES chamber() const;

    /// Draws the given bullet to the screen.
    void	 displayBullet(int range, int dx, int dy, u8 sym, ATTR_NAMES attr, bool stopatmob) const;

    void	 postEvent(EVENTTYPE_NAMES type, u8 sym, ATTR_NAMES attr) const;
    void	 postEvent(EVENTTYPE_NAMES type, u8 sym, ATTR_NAMES attr, const char *shout) const;

    /// Returns current distance map here.
    int		 getDistance(int mapnum) const;
    void	 setDistance(int mapnum, int dist) const;

    TILE_NAMES	tile() const;
    void	setTile(TILE_NAMES tile) const;
    MAPFLAG_NAMES		flag() const;
    void	setFlag(MAPFLAG_NAMES flag, bool state) const;

    int		smoke() const;
    void	setSmoke(int smoke);

    TERRAIN_NAMES terrain() const;

    /// prepares a square to be floored, ie, wall all surrounding
    /// and verify no portals.  Returns false if fails, note
    /// some walls may be created.
    bool	prepSquareForDestruction() const;

    /// Finds the room we are in and opens exits in all four directions,
    /// forcibly.
    bool	forceConnectNeighbours() const;

    /// Digs out this square
    /// Returns false if undiggable.  Might also fail if adjacent
    /// true squares aren't safe as we don't want to dig around portals,
    /// etc
    bool	digSquare() const;

    /// Applies fire to this square.
    /// Anything on it that is burnable will burn.
    /// Return true if anything interesting happened.
    bool        burnSquare() const;

    /// Dumps water on this square.
    /// True if interest happens.
    bool        soakSquare(POTION_NAMES liquid) const;

    /// Shatters anything on the square
    /// True if interesting.
    bool	shatterSquare() const;

    /// Restores the square
    /// True if itneresting
    bool	restoreSquare() const;

    // Send items here on a joyride, return true if any moved.
    bool	teleportItems() const;

    // Transform these items, return true if any changed.
    bool	polymorphItems() const;

    // Does not include blindness:
    bool	isFOV() const { return flag() & MAPFLAG_FOV ? true : false; }
    bool	isFOVCache() const { return flag() & MAPFLAG_FOVCACHE ? true : false; }
    bool	visibleFromAvatar() const;
    bool	isTrap() const { return flag() & MAPFLAG_TRAP ? true : false; }
    void	clearTrap() { setFlag(MAPFLAG_TRAP, false); }

    bool	isMapped() const { return flag() & MAPFLAG_MAPPED ? true: false; }

    void        mapCurrentLevel() const;

    MOB 	*mob() const;
    ITEM 	*item() const;
    int	 	 allItems(ITEMLIST &items) const;

    int		 metaData() const;		// -1 for invalid.
    void	 setMetaData(int meta) const;

    MAP		*map() const { return myMap; }

    // Query functoins
    const TILE_DEF &defn() const { return defn(tile()); }
    static const TILE_DEF &defn(TILE_NAMES tile) { return *GAMEDEF::tiledef(tile); }

    bool	 isPassable() const { return defn().ispassable; }
    bool	 isBijective() const { return isPassable() || defn().isdoorway; }

    int		 depth() const;

    void	 setMap(MAP *map) { myMap = map; if (!map) myRoomId = -1; }

    void	 save(ostream &os) const;
    // In place load
    void	 load(istream &is);

    // A strange definition of const..
    void	 removeMob(MOB *mob) const;
    void	 addMob(MOB *mob) const;
    void	 replaceMob(MOB *orig, MOB *newmob) const;
    void	 moveMob(MOB *mob, POS oldpos) const;
    void	 removeItem(ITEM *item) const;
    ITEM	*splitStack(ITEM *item) const;
    // DO NOT ALL THIS, use move() on item.
    // Why?  Because it may delete the item.
    void	 addItem(ITEM *item) const;

    int 	 getAllMobs(MOBLIST &list) const;
    int 	 getAllItems(ITEMLIST &list) const;

    // Returns colour of the room we are in.  Do not stash this
    // pointer.
    ATTR_NAMES	 room_color() const;
    u8		 room_symbol() const;
    u8		 colorblind_symbol(u8 oldsym) const;
    int		*jacobian() const;

    // Use with much discretion!
    int		 roomId() const { return myRoomId; }

    // You can use again, if you are wise.
    int		 roomSeed() const;
    int		 getRoomDepth() const;
    ROOM	*rawRoom() const { return room(); }
    size_t	 hash() const;

    int          getAllRoomTiles(TILE_NAMES tile, POSLIST &list) const;

    // Never use
    int		 privateX() const { return myX; }
    int		 privateY() const { return myY; }

    ///
    /// Filters
    ///
    static auto		filterAll() { return [](POS p) -> bool
				    { return true; }; }
    static auto		filterValid() { return [](POS p) -> bool
				    { return p.valid(); }; }
    static auto		filterNone() { return [](POS p) -> bool
				    { return false; }; }
    static auto		filterPassable() { return [](POS p) -> bool
				    { return p.isPassable(); }; }
    static auto		filterNoMob() { return [](POS p) -> bool
				    { return !p.mob(); }; }


private:
    ROOM	*room() const;
    // Transforms dx & dy into our local facing.
    void	 rotateToLocal(int &dx, int &dy) const;
    // And back to world.
    void	 rotateToWorld(int &dx, int &dy) const;
    // Same sense as rotateToLocal:
    // (Welcome to transform hell!)
    static void	 rotateByAngle(int angle, int &dx, int &dy);

    // Null if not on a map.
    int		 myRoomId;
    MAP		*myMap;

    // The room relative coords
    int			 myX, myY;
    // Which way we face in the room.
    int			 myAngle;

    friend class ROOM;
    friend class FRAGMENT;
    friend class MAP;
    friend class MULTIVERSE;
};

namespace std
{
    template <>
    class hash<POS>
    {
    public:
	size_t operator()(const POS &pos) const
	{
	    return pos.hash();
	}
    };
}

/// Note that portals are directed.  mySrc is assuemd to have angle of 0
/// as the turning effect is stored in myDst's angle.
class PORTAL
{
public:
    void	 save(ostream &os) const
    { mySrc.save(os);  myDst.save(os); }
    static PORTAL load(istream &is)
    {
	PORTAL		result;
	result.mySrc.load(is);
	result.myDst.load(is);
	return result;
    }

    POS		mySrc, myDst;
};

class ROOM
{
public:
    ROOM();
    ROOM(const ROOM &room);
    ROOM &operator=(const ROOM &room);

    ~ROOM();

    int		width() const { return myWidth; }
    int		height() const { return myHeight; }

    static bool link(ROOM *a, int dira, ROOM *b, int dirb);
    static bool buildPortal(POS a, int dira, POS b, int dirb, bool settile=true);

    // mySrc is the portal square to launch from, myDst is the
    // destination square to land on
    POS		findProtoPortal(int dir);

    // Convert all of our portals back into proto portals so
    // we can relink
    void	revertToProtoPortal();

    POS		findUserProtoPortal(int dir);

    ROOMTYPE_NAMES	type() const { return myType; }
    const ROOMTYPE_DEF &defn() const { return glb_roomtypedefs[type()]; }

    POS		findCentralPos() const;
    // Finds passable location near the start, but not nearer than
    // startrad.
    POS		spiralSearch(POS start, int startrad = 0, bool avoidmob=true) const;

    // Finds location satisfying OP(POS)->bool, starting at the start
    // Returns invalid if nothing passed op. Op may be called successfully
    // more than once as we pick one from the last radius to avoid bias.
    template <typename OP>
    POS		spiralSearch(POS start, int startrad, int maxrad, const OP &op) const;

    int 	getAllTiles(TILE_NAMES tile, POSLIST &list) const;

    int		stairDistance() const;

    void	save(ostream &os) const;
    static ROOM	*load(istream &is);

    int		getDepth() const { return myDepth; }
    int		getId() const { return myId; }
    u8		getSymbol() const { return mySymbol; }
    void	setSymbol(u8 sym) { mySymbol = sym; }

    int		getSeed() const { return mySeed; }

    int		atlasX() const { return myAtlasX; }
    int		atlasY() const { return myAtlasY; }

    int		toLinear(POS p) const
    { return toLinear(p.myX, p.myY); }
    int		toLinear(int x, int y) const
    { return y * width() + x; }
    POS		fromLinear(int linear) const
    {
	return buildPos(linear % width(), linear / width());
    }

    bool	forceConnectNeighbours();

    MAP		*map() const { return myMap; }

    // Calls op(POS p) on each square.
    template <typename OP>
    void	eachPos(const OP &op) const
    {
	for (int x = 0; x < width(); x++)
	{
	    for (int y = 0; y < height(); y++)
	    {
		POS p = buildPos(x, y);
		op(p);
	    }
	}
    }

    // Random walkable without mob:
    POS			 getRandomPos(POS p, int *n) const;
private:
    // These all work in the room's local coordinates.
    TILE_NAMES		getTile(int x, int y) const;
    void		setTile(int x, int y, TILE_NAMES tile);
    MAPFLAG_NAMES	getFlag(int x, int y) const;

    void		setFlag(int x, int y, MAPFLAG_NAMES flag, bool state);
    void		setAllFlags(MAPFLAG_NAMES flag, bool state);
    void		mergeFlags(MAPFLAG_NAMES flag, ROOM *src);

    int			getMetaData(int x, int y) const;
    void		setMetaData(int x, int y, int metadata);
    int			getSmoke(int x, int y) const;
    void		setSmoke(int x, int y, int smoke);
    void		updateMaxSmoke();
    void		simulateSmoke(int colour);
    int			maxSmoke() const { return myMaxSmoke; }

    int			getDistance(int mapnum, int x, int y) const;
    void		setDistance(int mapnum, int x, int y, int dist);
    void		clearDistMap(int mapnum);

    bool		xyInRoom(IVEC2 pos) const
    {
	if (pos.x() < 0 || pos.y() < 0) return false;
	if (pos.x() >= width()) return false;
	if (pos.y() >= height()) return false;
	return true;
    }
    bool		xyInRoom(POS pos) const
    {
	if (pos.myX < 0 || pos.myY < 0) return false;
	if (pos.myX >= width()) return false;
	if (pos.myY >= height()) return false;
	return true;
    }

    PORTAL		*getPortal(int x, int y) const;

    // Remvoes the portal that lands at dst.
    void		 removePortal(POS dst);

    // Removes all portals in this room
    void		 removeAllPortals();
    // Removes all portals out of this roomtype
    void		 removeAllForeignPortals();
    // Removes all portals heading into the given type.
    void		 removeAllPortalsTo(ROOMTYPE_NAMES type);

    void	 	 getAllItems(int x, int y, ITEMLIST &list) const;

    void		 setMap(MAP *map);

    void		 resize(int w, int h);

    POS			 buildPos(int x, int y) const;
    POS			 buildPos(IVEC2 xy) const { return buildPos(xy.x(), xy.y()); }
    POS			 getRandomTile(TILE_NAMES tile, POS p, int *n) const;
    int			 getVisibleExits(POSLIST &list) const;
    POS			 findCloseUnexplored(POS p) const;

    void		 deleteContents();

    int			 getConnectedRooms(ROOMLIST &list) const;

    void		 rotateEntireRoom(int angle);

    int			myId;
    PTRLIST<PORTAL>	myPortals;

    int			myAtlasX, myAtlasY;
    int			mySeed;

    // 0: Disconnected.  1: Connected.  2: Limbo.  3: Implicit connection
    enum
    {
	DISCONNECTED,
	CONNECTED,
	UNDECIDED,
	IMPLICIT
    };
    u8			myConnectivity[4];

    int			 myWidth, myHeight;
    TEXTURE8		 myTiles;
    TEXTURE8		 myFlags;
    TEXTURE8		 mySmoke;
    TEXTUREI		 myMetaData;
    u8			 myConstantFlags;
    u8			 myMaxSmoke;
    TEXTUREI		 myDists;
    MAP			*myMap;

    ROOMTYPE_NAMES	 myType;
    int			 myDepth;
    u8			 mySymbol;

    // Maybe null after load!
    const FRAGMENT	*myFragment;

    friend class POS;
    friend class FRAGMENT;
    friend class MAP;
};

// Stores the fact that a mob is being delayed and where it wants
// to go.
class MOBDELAY
{
public:
    MOBDELAY() { myMob = 0; myDx = 0; myDy = 0; }
    explicit MOBDELAY(int foo) { myMob = 0; myDx = 0; myDy = 0; }
    MOBDELAY(MOB *mob, int dx, int dy)
    { myMob = mob; myDx = dx; myDy = dy; }

    MOB		*myMob;
    int		 myDx, myDy;
};

class MAP
{
public:
    explicit MAP(int depth, DISPLAY *display);
    explicit MAP(istream &is);
    MAP(const MAP &map);
    MAP &operator=(const MAP &map);

    ~MAP();

    static void		 	 buildLevelList();

    MOB				*avatar() const { return myAvatar; }
    void			 setAvatar(MOB *mob) { myAvatar = mob; }

    MULTIVERSE			*verse() const { return myVerse; }
    void			 setVerse(MULTIVERSE *verse) { myVerse = verse; }

    void			 doHeartbeat();
    void			 doMoveNPC();

    // Rebuilds this map in place with the new data.
    void			 moveAvatarToDepth(int newdepth);
    bool			 moveMobToDepth(MOB *mob, int newdepth);

    // Wipes all items and mobs.
    void			 destroyAllItemsAndMobs();

    int				 getNumMobs() const;
    int				 getAllMobs(MOBLIST &list) const;
    int				 getVisibleMobs(MOBLIST &list) const;
    int				 getAllMobsOfType(MOBLIST &list, MOB_NAMES type) const;
    int				 getNumVisibleMobsOfType(MOB_NAMES type) const;
    int				 getAllItems(ITEMLIST &list) const;
    int				 getVisibleItems(ITEMLIST &list) const;

    void			 setAllFlags(MAPFLAG_NAMES flag, bool state);

    int			 	 buildDistMap(POS center);

    int				 timeToApocalypse() const;
    float			 apocalypsePercent() const;
    bool			 apocalypseActive() const { return myApocalypseActive; }
    void			 avertApocalypse();
    void			 restartApocalypse();

    static ROOMTYPE_NAMES		 transitionRoom(ROOMTYPE_NAMES lastroom);

    // Adds a mob to our dead list.
    // This way we can delay actual desctruction to a safe time.
    void			 addDeadMob(MOB *mob);

    // Notes that this mob wants to move in this direction but
    // there is a mob in the way.  The collision resolution subsystem
    // should re-run the AI if it can clear out its destination.
    // If delays are disabled, returns false.  Otherwise true to
    // mark this ai complete.
    bool			 requestDelayedMove(MOB *mob, int dx, int dy);

    int				 getId() const { return myUniqueId; }

    int		getMobCount(MOB_NAMES mob) const { if (mob >= myMobCount.entries()) return 0; return myMobCount(mob); }

    static void			 init();

    // Computes the delta to add to a to get to b.
    // Returns false if unable to compute it.  Things outside of FOV
    // are flakey as we don't cache them as I'm very, very, lazy.
    // And I already burned all the free cycles in the fire sim.
    bool			 computeDelta(POS a, POS b, int &dx, int &dy);

    // Reinitailizes FOV around avatar.
    void			 rebuildFOV();
    // Frees the FOV cache
    void			 clearFOV();

    // Ors together a flag from the other map, but only where
    // rooms match topology.
    void			 mergeFlags(MAPFLAG_NAMES flag, MAP *src);

    // Cache all the item position distances as we will likely
    // want them soon enough
    void			 cacheItemPos();

    void			 buildReflexivePortal(POS pos, int dir);
    void			 buildUserPortal(POS pos, int pnum, int dir,
						int avatardir);

    // Final dungeon collapse
    void			 triggerOrcInvasion();

    // Returns a central location in a random room of that type.
    POS				 findRoomOfType(ROOMTYPE_NAMES roomtype) const;

    // Returns a room at the given atlas location, null if it doesn't
    // exist.
    ROOM			*findAtlasRoom(int x, int y) const;

    // Returns a location in astral plane.
    POS				 astralLoc() const;

    // Finds matching item from uid, assuming it is on the ground.
    ITEM			*findItem(int uid) const;

    // Finds matching mob from a uid.
    MOB				*findMob(int uid) const;
    MOB				*findMobByType(MOB_NAMES type) const;

    // Only searches current room.
    POS			  	 findCloseUnexploredOnFloor(POS pos) const;

    POS			 	 getRandomTile(TILE_NAMES tile) const;
    POS			 	 getRandomTileOnFloor(POS pos, TILE_NAMES tile) const;
    // Finds passable location near the start, but not nearer than
    // startrad.
    POS		spiralSearch(POS start, int startrad = 0, bool avoidmob=true) const;

    int				 getVisibleExits(POSLIST &list) const;

    // Do not use.
    DISPLAY			*getDisplay() const { return myDisplay; }
    void			 setDisplay(DISPLAY *disp) { myDisplay = disp; }

    void			 save(ostream &os) const;

    int				 getTime() const;
    PHASE_NAMES			 getPhase() const;

    static ATTR_NAMES		 depthColor(int depth, bool invert);

    void	expandActiveRooms(int roomid, int depth);

    // Transfer all mobs and items from this world to goal world.
    void	transferToWorld(MAP *dest);

    int		mobLetterTimeStamp() const { return myMobLetterTimeStamp; }
    void	setMobLetterTimeStamp(int time) { myMobLetterTimeStamp = time; }
    int		mobLastSeen(int idx) const
    {
	if (idx >= 0 && idx < 26) return myMobLastSeen[idx];
	J_ASSERT(0);
	return -1;
    }
    void	setMobLastSeen(int idx, int seen)
    {
	if (idx >= 0 && idx < 26) myMobLastSeen[idx] = seen;
	else { J_ASSERT(0); }
	return;
    }

    bool	itemIded(ITEM_NAMES item) const
    { return myItemIded.safeIndexAnywhere(item); }
    void	setItemIded(ITEM_NAMES item, bool state)
    { myItemIded.indexAnywhere(item) = state; }

    int		itemMagicClass(ITEM_NAMES item) const
    { return myItemMagicClass.safeIndexAnywhere(item); }
    void	setItemMagicClass(ITEM_NAMES item, int val)
    { myItemMagicClass.indexAnywhere(item) = val; }

protected:
    // You need to delete contents first!
    void	rebuild(int depth, MOB *avatar, DISPLAY *display, bool climbeddown);

    int				allocDistMap(POS center);
    int				lookupDistMap(POS center) const;
    void			clearDistMap(int map);

    void			buildSquare(ROOMTYPE_NAMES roomtype,
				    ROOM *entrance, int enterdir,
				    ROOM *&exit, int &exitdir,
				    int difficulty,
				    int depth,
				    int w, int h);

    // Remove all dead mobs from our mob list.
    void			reapMobs();

    void			updateChainOfCommand(MOBLIST &officers);
    void			updateComSet();

    void			getCanonicalCoord(POS pos, int &x, int &y) const;
    POS				buildFromCanonical(int x, int y) const;

    // Reconsider what you are doing:
    POS				buildPos(int x, int y, int roomid) const;

    // Register or move mobs, called from POS.
    void			removeMob(MOB *mob, POS pos);
    void			addMob(MOB *mob, POS pos);
    void			moveMob(MOB *mob, POS pos, POS oldpos);
    void	 		replaceMob(MOB *orig, MOB *newmob);

    // Register or move items.  Called from POS
    void	 		removeItem(ITEM *item, POS pos);
    void	 		addItem(ITEM *item, POS pos);

    // Search our mob list, ideally has accel structure in it.
    // (This is where conversion to canonical and mob tree should have
    // gone)
    MOB				*findMobByLoc(ROOM *room, int x, int y) const;
    int				 findAllMobsByLoc(MOBLIST &list, ROOM *room, int x, int y) const;
    ITEM			*findItemByLoc(ROOM *room, int x, int y) const;
    int				 findAllItemsByLoc(ITEMLIST &list, ROOM *room, int x, int y) const;

    void			deleteContents();

    MOB				*findAvatar();

    int				myUniqueId;

    POS				myDistMapCache[DISTMAP_CACHE_ALLOC];
    mutable double		myDistMapWeight[DISTMAP_CACHE_ALLOC];

    POS				myUserPortal[2];
    int				myUserPortalDir[2];

    ROOMLIST			myRooms;

    MOBLIST			myDeadMobs;
    MOBLIST			myLiveMobs;
    ITEMLIST			myItems;

    MOB				*myAvatar;

    // The map as seen from our FOV.
    SCRPOS_PTR			 myFOVCache;

    DISPLAY			*myDisplay;

    bool			 myAllowDelays;
    MOBDELAYLIST		 myDelayMobList;
    PTRLIST<int> 		 myMobCount;

    static FRAGMENTLIST		 theFragRooms[NUM_ROOMTYPES];

    // Mob settings:
    int				 myMobLetterTimeStamp = 0;
    int				 myMobLastSeen[26];

    // Item settings:
    PTRLIST<bool>		 myItemIded;
    PTRLIST<int>	         myItemMagicClass;

    // Tracking mob types in fov
    PTRLIST<int>		 myMobsInFOV;

    SPEED			 mySpeed;
    bool			 myApocalypseActive = true;
    int				 myTimeOfApocalypse = 0;

    MULTIVERSE			*myVerse = 0;

    friend class FRAGMENT;
    friend class ROOM;
    friend class POS;
    friend class MULTIVERSE;
};

// There is an argument for a perfectly continuous probablity field
// which we then sample after diffusing; but this has some issues:
// 1) Practically, we lose the correlations that form spooky action
//    at distance on collapse.
// 2) While Everett's model is tempting, I think its implication of
//    infinite data is fundamentally wrong.  There is no reason to 
//    suspect anything has infinite resolution, quantum itself should
//    disabuse us of that, so why assume infinite resolution in the
//    number of potential worlds?
// This ties into the idea that so-called "real" numbers should instead
// be "random" numbers, ie, that collapse is constant and is basically
// occuring as we run out of storage/processing for the entangled state.
// The ablity to have some states cohere longer is because we are carefully
// isolating their gain of information so they don't exceed the threshold,
// which we interpret as thermal noise....
// In this world the noise level is way higher, so we have a greater rate
// of collapse, nicely modelled by the hard cap on number of worlds we
// monitor.

class MULTIVERSE
{
public:
     MULTIVERSE();
    ~MULTIVERSE();

    void			 save() const;
    // May fail so end up with nullptrs.
    bool 			 load();

    // This is the canonical map, it freezes during defocus
    // so is the positions prior to defocus.
    MAP_PTR			 map() const
    { return myMap; }
    void			 setMap(MAP_PTR map)
    { 
	myMap = map; 
	if (myMap) myPhase = myMap->getPhase(); 
    }

    float			 mapDensity(POS pos) const;
    float			 mapDensityRaw(POS pos) const;
    float			 mobDensity(POS pos) const;
    float			 mobDensityRaw(POS pos) const;
    float			 mobDensityGiven(POS pos, POS given) const;

    void			 avatarHealth(POS pos, PTRLIST<float> &probperhealth) const;

    void			 uniqueMap();
    int				 getId() { return myUniqueId; }

    void			 releaseMaps()
    {
	myMap.reset();
	myMapsFromPos.clear();
    }

    void			 allMaps(MAPLIST &list) const
    {
	for (auto && kv : myMapsFromPos)
	{
	    list.append(kv.second);
	}
	if (!list.entries())
	    list.append(myMap);
    }

    void			 appendMap(POS pos, MAP_PTR map,
					    int maxmapperpos);

    void			 cullWorlds(int maxworldperpos);

    void			 focusWorld(int x, int y, int room);

    PHASE_NAMES			 phase() const { return myPhase; }

protected:
    PHASE_NAMES			 myPhase;
    int				 myUniqueId;
    MAP_PTR			 myMap;
    MAPFROMPOS			 myMapsFromPos;
    INTFROMPOS			 myMapsAtPos;
};

#endif
