/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	7DRL Development
 *
 * NAME:        grid.h ( Mage Library, C++ )
 *
 * COMMENTS:
 *	Implements a grid of POD
 *	TFW you find out you aleady had texture.h to deal with 2d maps!
 *	I knew I had something and looked for a long time before rewriting.
 *	This is *NOT* shared COW, however, so is a bit different...
 *	So I shall tell myself as I cry myself to sleep.
 */

#ifndef __grid_h__
#define __grid_h__

#include <iterator>
#include "mygba.h"
#include "ptrlist.h"
#include "vec2.h"

template <typename T>
class GRID_T
{
public:
    GRID_T() { myWidth = myHeight = 0; ::memset(&myDefVal, 0, sizeof(T)); }
    GRID_T(int w, int h) { setSize(w, h); ::memset(&myDefVal, 0, sizeof(T)); }
    ~GRID_T() {}

    void	setSize(int w, int h)
    { 
	myData.resize(w * h); 
	myWidth = w; myHeight = h;
    }

    void	constant(T val)
    { myData.constant(val); }

    void	setDefVal(T val) { myDefVal = val; }
    T		defval() const { return myDefVal; }

    void		clear(int x, int y, int w, int h, T val)
    {
	for (int i = 0; i < h; i++)
	    for (int j = 0; j < w; j++)
		set(x+j, y+i, val);
    }

    void		replace(T oldval, T newval)
    {
	int		x, y;
	FORALL_XY(x, y)
	{
	    if (get(x, y) == oldval)
		set(x, y, newval);
	}
    }

    bool	isValid(int x, int y) const
    {
	if (x < 0 || x >= width()) return false;
	if (y < 0 || y >= height()) return false;
	return true;
    }

    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()); }

    /// Operator is raw access so is not bounds checked.
    const T	&operator()(IVEC2 p) const
    { return (*this)(p.x(), p.y()); }
    const T	&operator()(int x, int y) const
    { 
	J_ASSERT(isValid(x, y));
	return myData(x + y * width()); 
    }
    T	&operator()(IVEC2 p)
    { return (*this)(p.x(), p.y()); }
    T		&operator()(int x, int y)
    { 
	J_ASSERT(isValid(x, y));
	return myData(x + y * width()); 
    }

    /// get/set are safe so return background or ignore.
    T		 get(int x, int y) const 
    { 
	if (isValid(x, y)) return (*this)(x, y);
	return myDefVal;
    }
    T		get(IVEC2 p) const
    { return get(p.x(), p.y()); }

    T		wrapget(int x, int y) const
    { wrap(x, y); return get(x, y); }
    T		wrapget(IVEC2 p) const
    { wrap(p); return get(p); }

    void	set(int x, int y, T val)
    {
	if (isValid(x, y)) (*this)(x, y) = val;
    }
    void	set(IVEC2 p, T val)
    { return set(p.x(), p.y(), val); }

    void	wrapset(int x, int y, T val)
    { wrap(x, y); set(x, y, val); }
    void	wrapset(IVEC2 p, T val)
    { wrap(p); set(p, val); }

    T		add(int x, int y, T val)
    {
	if (isValid(x, y))
	{
	    (*this)(x, y) += val;
	    return (*this)(x,y);
	}
	return myDefVal;
    }
    T		add(IVEC2 p, T val) const
    { return add(p.x(), p.y(), val); }

    T		wrapadd(int x, int y, T val)
    { wrap(x, y); return add(x, y, val); }
    T		wrapadd(IVEC2 p, T val)
    { wrap(p); return add(p, val); }

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

    void	wrapget_stencil4(float *stencil, int x, int y) const
    {
	for (int dy = -1; dy <= 2; dy++)
	{
	    for (int dx = -1; dx <= 2; dx++)
	    {
		stencil[(dy+1)*4 + dx+1] = wrapget(x+dx, y+dy);
	    }
	}
    }

    template <typename OP>
    void	foreachXY(const OP &op) const
    {
	int		x, y;
	FORALL_XY(x, y)
	{
	    op(x, y);
	}
    }
    template <typename OP>
    void	foreachXY_rand(const OP &op) const
    {
	int		sweepdir = rand_choice(8);
	foreachXY_sweep(op, sweepdir);
    }
    template <typename OP>
    void	foreachXY_sweep(const OP &op, int sweep) const
    {
	if (sweep & 4)	// x then y
	{
	    if (sweep & 1)	 // rev x
	    {
		for (int x = width(); x --> 0; )
		{
		    if (sweep & 2)	// rev y
		    {
			for (int y = height(); y --> 0;)
			    op(x, y);
		    }
		    else
		    {
			for (int y = 0, h = height(); y < h; y++)
			    op(x, y);
		    }
		}
	    }
	    else
	    {
		for (int x = 0, w = width(); x < w; x++)
		{
		    if (sweep & 1)	// rev y
		    {
			for (int y = height(); y --> 0;)
			    op(x, y);
		    }
		    else
		    {
			for (int y = 0, h = height(); y < h; y++)
			    op(x, y);
		    }
		}
	    }
	}
	else
	{
	    if (sweep & 2)	 // rev y
	    {
		for (int y = height(); y --> 0;)
		{
		    if (sweep & 1)	// rev x
		    {
			for (int x = width(); x --> 0; )
			    op(x, y);
		    }
		    else
		    {
			for (int x = 0, w = width(); x < w; x++)
			    op(x, y);
		    }
		}
	    }
	    else
	    {
		for (int y = 0, h = height(); y < h; y++)
		{
		    if (sweep & 1)	// rev x
		    {
			for (int x = width(); x --> 0; )
			    op(x, y);
		    }
		    else
		    {
			for (int x = 0, w = width(); x < w; x++)
			    op(x, y);
		    }
		}
	    }
	}
    }

    const T     *data() const { return myData.rawptr(); }
    T    	*dataNC() { return myData.rawptr(); }

protected:
    int		myWidth, myHeight;
    PTRLIST<T>  myData;
    T		myDefVal;
};

using GRID8 = GRID_T<u8>;
using GRIDI = GRID_T<int>;
using GRIDB = GRID_T<bool>;
using GRIDF = GRID_T<float>;

#endif
