/*
 * Copyright (c) 2008
 * Evan Teran
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both the
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the same name not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission. We make no representations about the
 * suitability this software for any purpose. It is provided "as is"
 * without express or implied warranty.
 */

#ifndef MATRIX_20051222_H_
#define MATRIX_20051222_H_

#include <cstddef>		// for std::size_t
#include <algorithm>	// for std::min
#include <stdexcept>	// fot std::out_of_range

template <class T>
class Matrix {
public:

	typedef std::size_t size_type;

	class reference {
		// only the Matrix class can create a Matrix::reference
		friend class Matrix;
		reference(T *r, size_type n) : m_Data(r), m_Size(n) {}
		
	public:
		~reference() {}		
		T &operator[](size_type y) const	{ if(y >= m_Size) { throw std::out_of_range("y >= height"); } return m_Data[y]; }
		T &operator[](size_type y)			{ if(y >= m_Size) { throw std::out_of_range("y >= height"); } return m_Data[y]; }

	private:
		T *m_Data;
		size_type m_Size;
	};
	
public:
	//------------------------------------------------------------------------------------
	// Name: Matrix()
	//------------------------------------------------------------------------------------
	Matrix() : m_Data(0), m_Width(0), m_Height(0) {
	}
	
	//------------------------------------------------------------------------------------
	// Name: Matrix(const Matrix& m)
	//------------------------------------------------------------------------------------
	Matrix(const Matrix& m) : m_Data(0), m_Width(0), m_Height(0) {
	
		// resize ourselves to match m
		resize(m.width(), m.height(), false);
		
		// if the new size is valid, copy all the elements
		if(m_Data != 0) {
			for(size_type y = 0; y < m_Height; ++y) {
				for(size_type x = 0; x < m_Width; ++x) {
					m_Data[x][y] = m[x][y];
				}
			}
		}
	}
	
	//------------------------------------------------------------------------------------
	// Name: operator=(const Matrix& m)
	//------------------------------------------------------------------------------------
	Matrix &operator=(const Matrix& m) {
		// more or less same as copy constructor
		resize(m.width(), m.height(), false);
	
		if(m_Data != 0) {
			for(size_type y = 0; y < m_Height; ++y) {
				for(size_type x = 0; x < m_Width; ++x) {
					m_Data[x][y] = m[x][y];
				}
			}
		}
	
		return *this;
	}

	//------------------------------------------------------------------------------------
	// Name: Matrix(size_type width, size_type height)
	//------------------------------------------------------------------------------------
	Matrix(size_type width, size_type height) : m_Data(0), m_Width(0), m_Height(0) {
		resize(width, height);
	}
	
	//------------------------------------------------------------------------------------
	// Name: ~Matrix()
	//------------------------------------------------------------------------------------
	~Matrix() {
		for(size_type x = 0; x < m_Width; ++x) {
			delete [] m_Data[x];
		}
		delete [] m_Data;
		m_Data = 0;
	}
	
	//------------------------------------------------------------------------------------
	// Name: operator[](size_type x)
	//------------------------------------------------------------------------------------
	reference operator[](size_type x) {
		if(x >= m_Width) {
			throw std::out_of_range("x >= width");
		} 
		return reference(m_Data[x], m_Height);
	}
	
	//------------------------------------------------------------------------------------
	// Name: reference operator[](size_type x) const
	//------------------------------------------------------------------------------------
	const reference operator[](size_type x) const  {
		if(x >= m_Width) {
			throw std::out_of_range("x >= width");
		} 
		return reference(m_Data[x], m_Height);
	}
	
	//------------------------------------------------------------------------------------
	// Name: resize(size_type width, size_type height)
	//------------------------------------------------------------------------------------
	void resize(size_type width, size_type height, bool preserve = true) {
	
		// if either the width or height is 0, we just make the whole thing empty
		if(width == 0 || height == 0) {
			
			// delete any previously existing array
			if(m_Data != 0) {
				for(size_type x = 0; x < m_Width; ++x) {
					delete [] m_Data[x];
				}
				delete [] m_Data;
				m_Data = 0;
			}
			m_Width = 0;
			m_Height = 0;
		} else {
	
			// allocate the new matrix and initialize it
			T **t = new T*[width];
			for(size_type x = 0; x < width; ++x) {
				t[x] = new T[height]();
			}

			// if it is larger in either direction, copy old values into new matrix
			if(preserve && (m_Data != 0)) {
				for(size_type y = 0; y < std::min(height, m_Height); ++y) {
					for(size_type x = 0; x < std::min(width, m_Width); ++x) {
						t[x][y] = m_Data[x][y];
					}
				}
			}

			// delete the old matrix
			if(m_Data != 0) {
				for(size_type x = 0; x < m_Width; ++x) {
					delete [] m_Data[x];
				}
				delete [] m_Data;
			}

			// make the new matrix the one we use
			m_Data = t;
			m_Width = width;
			m_Height = height;
		}
	}
	
	//------------------------------------------------------------------------------------
	// Name: width() const
	//------------------------------------------------------------------------------------
	size_type width() const { 
		return m_Width;
	}
	
	//------------------------------------------------------------------------------------
	// Name: height() const
	//------------------------------------------------------------------------------------
	size_type height() const {
		return m_Height;
	}
	
	//------------------------------------------------------------------------------------
	// Name: height() const
	//------------------------------------------------------------------------------------
	void clear() {
		if(m_Data != 0) {
			for(size_type y = 0; y < m_Height; ++y) {
				for(size_type x = 0; x < m_Width; ++x) {
					m_Data[x][y] = T();
				}
			}
		}
	}
	
private:
	T **m_Data;
	size_type m_Width;
	size_type m_Height;
	
};

#endif
