/*
*
* Template Numerical Toolkit (TNT): Two-dimensional numerical array
*
* Mathematical and Computational Sciences Division
* National Institute of Technology,
* Gaithersburg, MD USA
*
*
* This software was developed at the National Institute of Standards and
* Technology (NIST) by employees of the Federal Government in the course
* of their official duties. Pursuant to title 17 Section 105 of the
* United States Code, this software is not subject to copyright protection
* and is in the public domain. NIST assumes no responsibility whatsoever for
* its use by other parties, and makes no guarantees, expressed or implied,
* about its quality, reliability, or any other characteristic.
*
*/



#ifndef TNT_ARRAY2D_H
#define TNT_ARRAY2D_H

#include <cstdlib>
#include <iostream>
#ifdef TNT_BOUNDS_CHECK
#include <assert.h>
#endif

namespace TNT
{

/**
	Tempplated two-dimensional, numerical array which
	looks like a conventional C multiarray. 
	Storage corresponds to C (row-major) ordering.
	Elements are accessed via A[i][j] notation. 
	
	<p>
	Array assignment is by reference (i.e. shallow assignment).
	That is, B=A implies that the A and B point to the
	same array, so modifications to the elements of A
	will be reflected in B. If an independent copy
	is required, then B = A.copy() can be used.  Note
	that this facilitates returning arrays from functions
	without relying on compiler optimizations to eliminate
	extensive data copying.

	<p>
	The indexing and layout of this array object makes
	it compatible with C and C++ algorithms that utilize
	the familiar C[i][j] notation.  This includes numerous
	textbooks, such as Numercial Recipes, and various
	public domain codes.

	<p>
	This class employs its own garbage collection via
	the use of reference counts.  That is, whenever
	an internal array storage no longer has any references
	to it, it is destoryed.
*/
template <class T>
class Array2D 
{


  private:
    T** v_;                  
	int m_;
    int n_;
    int *ref_count_;

	void initialize_(int m, int n);
    void copy_(T* p, const T*  q, int len) const;
    void set_(const T& val);
    void destroy_();
	inline const T* begin_() const;
	inline T* begin_();

  public:

    typedef         T   value_type;

	       Array2D();
	       Array2D(int m, int n);
	       Array2D(int m, int n,  T *a);
	       Array2D(int m, int n, const T &a);
    inline Array2D(const Array2D &A);
	inline Array2D & operator=(const T &a);
	inline Array2D & operator=(const Array2D &A);
	inline Array2D & ref(const Array2D &A);
	       Array2D copy() const;
		   Array2D & inject(const Array2D & A);
	inline T* operator[](int i);
	inline const T* operator[](int i) const;
	inline int dim1() const;
	inline int dim2() const;
	inline int ref_count() const;
               ~Array2D();


};


/**
	Copy constructor. Array data is NOT copied, but shared.
	Thus, in Array2D B(A), subsequent changes to A will
	be reflected in B.  For an indepent copy of A, use
	Array2D B(A.copy()), or B = A.copy(), instead.
*/
template <class T>
Array2D<T>::Array2D(const Array2D<T> &A) : v_(A.v_), m_(A.m_), 
	n_(A.n_), ref_count_(A.ref_count_)
{
	(*ref_count_)++;
}



/**
	Create a new (m x n) array, WIHOUT initializing array elements.
	To create an initialized array of constants, see Array2D(m,n,value).

	<p>
	This version avoids the O(m*n) initialization overhead and
	is used just before manual assignment.

	@param m the first (row) dimension of the new matrix.
	@param n the second (column) dimension of the new matrix.
*/
template <class T>
Array2D<T>::Array2D(int m, int n) : v_(0), m_(m), n_(n), ref_count_(0)
{
	initialize_(m,n);
	ref_count_ = new int;
	*ref_count_ = 1;
}



/**
	Create a new (m x n) array,  initializing array elements to
	constant specified by argument.  Most often used to
	create an array of zeros, as in A(m, n, 0.0).

	@param m the first (row) dimension of the new matrix.
	@param n the second (column) dimension of the new matrix.
	@param val the constant value to set all elements of the new array to.
*/
template <class T>
Array2D<T>::Array2D(int m, int n, const T &val) : v_(0), m_(m), n_(n) ,
	ref_count_(0)
{
	initialize_(m,n);
	set_(val);
	ref_count_ = new int;
	*ref_count_ = 1;

}

/**
	Create a new (m x n) array,  as a view of an existing one-dimensional
	array stored in <b>C order</b>, i.e. right-most dimension varying fastest.  
	(Often referred to as "row-major" ordering.)
	Note that the storage for this pre-existing array will
	never be garbage collected by the Array2D class.

	@param m the first (row) dimension of the new matrix.
	@param n the second (column) dimension of the new matrix.
	@param a the one dimensional C array to use as data storage for
		the array. 
*/
template <class T>
Array2D<T>::Array2D(int m, int n, T *a) : v_(0), m_(m), n_(n) ,
	ref_count_(0)
{
	T* p = a;
	v_ = new T*[m];
	for (int i=0; i<m; i++)
	{
		v_[i] = p;
		p += n;
	}
	ref_count_ = new int;
	*ref_count_ = 2;		/* this avoid destorying original data. */

}


/**
	Used for A[i][j] indexing.  The first [] operator returns
	a conventional pointer which can be dereferenced using the
	same [] notation.  
	
	If TNT_BOUNDS_CHECK macro is define, the left-most index (row index)
	is checked that it falls within the array bounds (via the
	assert() macro.) Note that bounds checking can occur in
	the row dimension, but the not column, since
	this is just a C pointer.
*/
template <class T>
inline T* Array2D<T>::operator[](int i) 
{ 
#ifdef TNT_BOUNDS_CHECK
	assert(i >= 0);
	assert(i < m_);
#endif

return v_[i]; 

}

template <class T>
inline const T* Array2D<T>::operator[](int i) const { return v_[i]; }

/**
	Assign all elemnts of A to a constant scalar.
*/
template <class T>
Array2D<T> & Array2D<T>::operator=(const T &a)
{
	set_(a);
	return *this;
}
/**
	Create a new of existing matrix.  Used in B = A.copy()
	or in the construction of B, e.g. Array2D B(A.copy()), 
	to create a new array that does not share data.

*/
template <class T>
Array2D<T> Array2D<T>::copy() const
{
	Array2D A(m_, n_);
	copy_(A.begin_(), begin_(), m_*n_);

	return A;
}


/**
	Copy the elements to from one array to another, in place.
	That is B.inject(A), both A and B must conform (i.e. have
	identical row and column dimensions).

	This differs from B = A.copy() in that references to B
	before this assignment are also affected.  That is, if
	we have 
	<pre>
	Array2D A(m,n);
	Array2D C(m,n);
	Array2D B(C);        // elements of B and C are shared. 

</pre>
	then B.inject(A) affects both and C, while B=A.copy() creates
	a new array B which shares no data with C or A.

	@param A the array from elements will be copied
	@return an instance of the modifed array. That is, in B.inject(A),
	it returns B.  If A and B are not conformat, no modifications to 
	B are made.

*/
template <class T>
Array2D<T> & Array2D<T>::inject(const Array2D &A)
{
	if (A.m_ == m_ &&  A.n_ == n_)
		copy_(begin_(), A.begin_(), m_*n_);

	return *this;
}





/**
	Create a reference (shallow assignment) to another existing array.
	In B.ref(A), B and A shared the same data and subsequent changes
	to the array elements of one will be reflected in the other.
	<p>
	This is what operator= calls, and B=A and B.ref(A) are equivalent
	operations.

	@return The new referenced array: in B.ref(A), it returns B.
*/
template <class T>
Array2D<T> & Array2D<T>::ref(const Array2D<T> &A)
{
	if (this != &A)
	{
		(*ref_count_) --;
		if ( *ref_count_ < 1 )
		{
			destroy_();
		}

		m_ = A.m_;
		n_ = A.n_;
		v_ = A.v_;
		ref_count_ = A.ref_count_;

		(*ref_count_) ++ ;
		
	}
	return *this;
}

/**
	B = A is shorthand notation for B.ref(A).
*/
template <class T>
Array2D<T> & Array2D<T>::operator=(const Array2D<T> &A)
{
	return ref(A);
}

/**
	@return the size of the first dimension of the array, i.e.
	the number of rows.
*/
template <class T>
inline int Array2D<T>::dim1() const { return m_; }

/**
	@return the size of the second dimension of the array, i.e.
	the number of columns.
*/
template <class T>
inline int Array2D<T>::dim2() const { return n_; }


/**
	@return the number of arrays that share the same storage area
	as this one.  (Must be at least one.)
*/
template <class T>
inline int Array2D<T>::ref_count() const
{
	return *ref_count_;
}

template <class T>
Array2D<T>::~Array2D()
{
	(*ref_count_) --;

	if (*ref_count_ < 1)
		destroy_();
}

/* private internal functions */

template <class T>
void Array2D<T>::initialize_(int m, int n)
{


	T* p = new T[m*n];
	v_ = new T*[m];
	for (int i=0; i<m; i++)
	{
		v_[i] = p;
		p+=n;
	}
	m_ = m;
	n_ = n;
}

template <class T>
void Array2D<T>::set_(const T& a)
{
	T *begin = &v_[0][0];
	T *end = begin+ m_*n_;

	for (T* p=begin; p<end; p++)
		*p = a;

}

template <class T>
void Array2D<T>::copy_(T* p, const T* q, int len) const
{
	T *end = p + len;
	while (p<end )
		*p++ = *q++;

}

template <class T>
void Array2D<T>::destroy_()
{

	if (v_ != 0)
	{
		delete[] (v_[0]);
		delete[] (v_);
	}

	if (ref_count_ != 0)
		delete ref_count_;
}

/**
	@returns location of first element, i.e. A[0][0] (mutable).
*/
template <class T>
const T* Array2D<T>::begin_() const { return &(v_[0][0]); }

/**
	@returns location of first element, i.e. A[0][0] (mutable).
*/
template <class T>
T* Array2D<T>::begin_() { return &(v_[0][0]); }

/**
	Create a null (0x0) array.  
*/
template <class T>
Array2D<T>::Array2D() : v_(0), m_(0), n_(0) 
{
	ref_count_ = new int;
	*ref_count_ = 1;
}





} /* namespace TNT */

#endif
/* TNT_ARRAY2D_H */

