/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	7 Day Band Development
 *
 * NAME:        builder.h ( 7 Day Band, C++ )
 *
 * COMMENTS:
 *	Verbish class to build ascii maps
 */

#ifndef __builder_h__
#define __builder_h__

#include "glbdef.h"
#include "vec2.h"
#include "grid.h"
#include <assert.h>

class RCT
{
public:
    int		x, y, w, h;	// [x, x+w) x [y, y+h)

    // Compatibility:
    int		width() const { return w; }
    int		height() const { return h; }

    bool	contains(int px, int py) const { return contains(IVEC2(px, py)); }
    bool	contains(IVEC2 p) const
    {
	return p.x() >= x && p.y() >= y && p.x() < (x+w) && p.y() < (y+h);
    }
    RCT		inset(int offset) const
    {
	return RCT{x+offset, y+offset, w-offset*2, h-offset*2};
    }
    bool	overlap(const RCT &o) const
    {
	if (MAX(x, o.x) > MIN(x+w, o.x+o.w))
	    return false;
	if (MAX(y, o.y) > MIN(y+h, o.y+o.h))
	    return false;
	return true;
    }
    IVEC2	center() const
    {
	return IVEC2{x+w/2, y+h/2};
    }
    // Why bother with a style guide if you can just have it all?
    IVEC2	centre() const { return center(); }

    void	doUnion(const RCT &o)
    {
	// Unioning with empty does nothing.
	if (o.w == 0 || o.h == 0)
	    return;
	// If we are empty, union should replace (our x,y should not matter)
	if (w == 0 || h == 0)
	{
	    *this = o;
	    return;
	}
	int minx = MIN(x, o.x);
	int maxx = MAX(x+w, o.x+o.w);
	int miny = MIN(y, o.y);
	int maxy = MAX(y+h, o.y+o.h);

	x = minx;
	y = miny;
	w = maxx - minx;
	h = maxy - miny;
    }

    void	doIsect(const RCT &o)
    {
	int minx = MAX(x, o.x);
	int maxx = MIN(x+w, o.x+o.w);
	int miny = MAX(y, o.y);
	int maxy = MIN(y+h, o.y+o.h);

	x = minx;
	y = miny;
	w = maxx - minx;
	if (w < 0) w = 0;
	h = maxy - miny;
	if (h < 0) h = 0;
    }

};


struct WALLCUT;

class BUILDER
{
public:
    BUILDER(u8 *data, GRIDI *metadata, int width, int height, int stride, int depth);
    void		setGoalTiles(int goal) { myGoalTiles = goal; }

    void		build(ROOMTYPE_NAMES type);

    void		buildRogue();
    void		buildCave();
    void		buildBigRoom();
    void		buildMaze();
    void		buildMage();
    void		buildLabyrinth();
    void		buildPalace();
    void		buildCaveRooms();
    void		buildFace(ROOMTYPE_NAMES type);
    void		buildWilderness(const TERRAIN_NAMES *terrain);
    void		buildOvermap();


    // Construct an individual room
    void		buildRoomMaze(RCT rct, u8 wall, u8 floor);
    void		buildRoomCave(RCT rct, u8 wall, u8 floor);

    bool		connectRooms(const RCT &a, const RCT &b);

    int			width() const { return myW; }
    int			height() const { return myH; }

    RCT			bounds() const { return RCT{0, 0, width(), height()}; }

    bool		isValid(int x, int y) const { return (x >= 0 && x < myW && y >= 0 && y < myH); }
    bool		isValid(IVEC2 p) const { return isValid(p.x(), p.y()); }
    u8			get(int x, int y) const
			{ J_ASSERT(isValid(x, y)); return myData[x+y*myStride]; }
    u8			get(IVEC2 p) const
			{ return get(p.x(), p.y()); }
    u8			wrapget(int x, int y) const
			{ wrap(x, y); return myData[x+y*myStride]; }
    u8			wrapget(IVEC2 p) const
			{ wrap(p); return get(p.x(), p.y()); }
    void		set(int x, int y, u8 val)
			{ J_ASSERT(isValid(x, y)); myData[x+y*myStride] = val; }
    void		set(IVEC2 p, u8 val)
			{ set(p.x(), p.y(), val); }
    void		clear(u8 val)
			{ clear(0, 0, myW, myH, val); }
    void		clear(int x, int y, int w, int h, u8 val);
    void		clear(RCT rct, u8 val)
			{ clear(rct.x, rct.y, rct.w, rct.h, val); }

    // OP = [](IVEC2 loc, bool border)
    // Fills given rectangle, border is set to true if it is on the border.
    // Will clip to valid bounds.
    template <typename OP>
    void		drawBorderedRect(RCT r, const OP &op);

    void		replace(u8 orig, u8 newval) { replace(bounds(), orig, newval); }
    void		replace_chance(u8 orig, u8 newval, int chance) { replace_chance(bounds(), orig, newval, chance); }
    void		replace(RCT rct, u8 orig, u8 newval);
    void		replace_chance(RCT rct, u8 orig, u8 newval, int chance);

    RCT			rand_rect(int minw, int minh, int maxw, int maxh) const;

    bool		findRandom(u8 val, int &x, int &y) const;
    bool		findRandom(RCT rct, u8 val, int &x, int &y) const;
			
    void		savemap(const char *fname) const;

    void		cutWall(int basex, int basey, int tilesize,
				u8 floorsym, u8 doorsym,
				const WALLCUT &cut); // randomdoor
    void		cutWall(int basex, int basey, int tilesize,
				u8 floorsym, u8 doorsym,
				const WALLCUT &cut, bool usedoor);

    // Run forest growth logic.
    void		growBushes();
    void		growForests();

    // ADds mobs, stairs, etc.
    bool		dressLevel();
    bool		dressLevel(int nmob, int nitem);

    void		wrap(int &x, int &y) const
    { x = x % width();  if (x < 0) x += width();
      y = y % height(); if (y < 0) y += height(); }
    void		wrap(IVEC2 &p) const
    { wrap(p.x(), p.y()); }

    // OP = [](IVEC2 loc) -> bool, false to stop excavating early.
    // op is not called on start location.
    template <typename OP>
    void		drunkExcavate(IVEC2 startpos, int dist, const OP &op);
    // op is not called on start or end location
    template <typename OP>
    void		drunkExcavateBetween(IVEC2 startpos, IVEC2 endpos, int drunkness, const OP &op);

    // filter = [](IVEC2 loc) -> bool, finds largest rect where bool is
    // true starting at start pos.  May be empty if startpos invalid.
    // No implicit bounds checking.
    template <typename FILTER>
    RCT			findLargestRoom(IVEC2 startpos, const FILTER &filter) const;

    // Returns true if all squares pass the filter 
    template <typename FILTER>
    bool		testRect(RCT test, const FILTER &filter) const;

    // filter = [](IVEC2 loc) -> bool, finds largestrect where filter is
    // true along each boundary.  Ie, start with full bounds and shrink
    // so long as we don't reject anything which filter is true.
    template <typename FILTER>
    RCT		 	boundingBox(const FILTER &filter) const;


protected:
    u8		*myData;
    GRIDI	*myMetaData;
    int		 myW, myH, myStride;
    int	 	 myDepth;
    int		 myGoalTiles;
};

template <typename FILTER>
RCT
BUILDER::boundingBox(const FILTER &filter) const
{
    RCT		bbox = bounds();

    // Try to reduce each side...
    for (int x = bbox.x, endx = bbox.x + bbox.w; x < endx; x++)
    {
	bool	allfail = true;
	for (int y = bbox.y; y < bbox.y+bbox.h; y++)
	{
	    if (filter(IVEC2{x,y}))
	    {
		allfail = false;
		break;
	    }
	}
	if (!allfail)
	    break;
	bbox.x = x;
	bbox.w = endx - x;
    }

    for (int x = bbox.x+bbox.w, endx = bbox.x; x --> endx;)
    {
	bool	allfail = true;
	for (int y = bbox.y; y < bbox.y+bbox.h; y++)
	{
	    if (filter(IVEC2{x,y}))
	    {
		allfail = false;
		break;
	    }
	}
	if (!allfail)
	    break;
	bbox.w = x - bbox.x + 1;
    }

    for (int y = bbox.y, endy = bbox.y + bbox.h; y < endy; y++)
    {
	bool	allfail = true;
	for (int x = bbox.x; x < bbox.x+bbox.w; x++)
	{
	    if (filter(IVEC2{x,y}))
	    {
		allfail = false;
		break;
	    }
	}
	if (!allfail)
	    break;
	bbox.y = y;
	bbox.h = endy - y;
    }

    for (int y = bbox.y+bbox.h, endy = bbox.y; y --> endy;)
    {
	bool	allfail = true;
	for (int x = bbox.x; x < bbox.x+bbox.w; x++)
	{
	    if (filter(IVEC2{x,y}))
	    {
		allfail = false;
		break;
	    }
	}
	if (!allfail)
	    break;
	bbox.h = y - bbox.y + 1;
    }

    return bbox;
}

#endif
