#include <Core/Core.h>
using namespace Upp;

#define ASSERT_BOUNDS(i, limit) ASSERT(i >= 0 && i < limit)

template <class T>
class Grid2D : public MoveableAndDeepCopyOption< Grid2D<T> >
{
public:
	void Dimension(int cx, int cy) 					{ return Dimension(Size(cx, cy)); }
	void Dimension(int cx, int cy, const T& init) 	{ return Dimension(Size(cx, cy), init); }
	void Dimension(Size sz);		
	void Dimension(Size sz, const T& init)			{ Dimension(sz); Fill(init); }

	void		Fill(const T& v);
	void 		Clear()								{ Free(); }
	
	const T*	operator[](int i)					{ return Col(i); }
	const T* 	operator[](int i) const				{ return Col(i); }
	T&			operator()(int i, int j)			{ return Get(i, j); }		
	T&			operator()(int i, int j) const		{ return Get(i, j); }		
	
	T&			Get(int i, int j) const				{ ASSERT_BOUNDS(j, size.cy); return Col(i)[j]; }
	const T*		GetColumn(int i) const				{ return Col(i); }
	T&			Set(int i, int j, const T& v) const { Get(i, j) = v; return v; }
protected:
	T* 			Col(int i) const;

	void     	Pick(pick_ Grid2D<T>& g);	
	void     	Free();
	void     	__DeepCopy(const Grid2D& src);
public:
	Grid2D() : size(0, 0), alloc(NULL) { }

	// Pick assignment & copy. Picked source can only do Clear(), ~Vector(), operator=, operator <<=
	Grid2D(pick_ Grid2D& g)          { Pick(g); }
	void operator=(pick_ Grid2D& g)  { Free(); Pick(g); }
	bool IsPicked() const            { return size.cx < 0; }

	// Deep copy
	Grid2D(const Grid2D& g, int)     { __DeepCopy(g); }	
	~Grid2D()						 { 
		Free(); 
		return; 
		T t(**alloc);        	// T must have transfer constructor (also GCC 4.0 bug workaround)
		AssertMoveable(&t);  	// T must be moveable
	}
	
private:
	Size size;
	T** alloc;
};

template <class T>
void Grid2D<T>::Dimension(Size sz)
{
	if (alloc) Free();
	alloc = new T*[sz.cx];
	for (int i =0; i < sz.cx; i++)
		alloc[i] = new T[sz.cy];
	size = sz;
}

template <class T>
void Grid2D<T>::Fill(const T& v)
{
	ASSERT(!IsPicked());
	for (int i =0; i < size.cx; i++)
		for (int j = 0; j < size.cy; j++)
			alloc[i][j] = v;
}

template <class T>
T* Grid2D<T>::Col(int i) const
{
	ASSERT(alloc);
	ASSERT_BOUNDS(i, size.cx);	
	return alloc[i];
}

template <class T>
void Grid2D<T>::Pick(pick_ Grid2D<T>& g)
{
	alloc = g.alloc;
	size = g.size;
	g.alloc = NULL;
	g.size = Size(-1, -1);
}

template <class T>
void Grid2D<T>::Free()
{
	if (!IsPicked() && alloc) {
		for (int i = 0; i < size.cx; i++)
			delete[] alloc[i];
		delete[] alloc;
	}
	alloc = NULL;
	size = Size(0, 0);
}

template <class T>
void Grid2D<T>::__DeepCopy(const Grid2D& src)
{
	ASSERT(!src.IsPicked());
	if (src.alloc) {
		Dimension(src.size);
		for (int i = 0; i < size.cx; i++)
			memcpy(alloc[i], src.alloc[i], size.cy*sizeof(T));
	}
}
