/*
 * 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_

//#define CHECKED_INDEXING

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


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(Matrix &owner, size_type x) : m_Owner(owner), m_X(x) {}
		
	public:
#ifdef CHECKED_INDEXING
		T &operator[](size_type y) const	{ if(y >= m_Owner.m_Height) { throw std::out_of_range("y >= height"); } return m_Owner.m_Data[m_X + y * m_Owner.m_Width]; }
		T &operator[](size_type y)			{ if(y >= m_Owner.m_Height) { throw std::out_of_range("y >= height"); } return m_Owner.m_Data[m_X + y * m_Owner.m_Width]; }
#else
		T &operator[](size_type y) const	{ return m_Owner.m_Data[m_X + y * m_Owner.m_Width]; }
		T &operator[](size_type y)			{ return m_Owner.m_Data[m_X + y * m_Owner.m_Width]; }
#endif

	private:
		Matrix		&m_Owner;
		size_type	m_X;
	};
	
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_Width] = m.m_Data[x + y * m_Width];
				}
			}
		}
	}
	
	//------------------------------------------------------------------------------------
	// Name: operator=(const Matrix& m)
	//------------------------------------------------------------------------------------
	Matrix &operator=(const Matrix& m) {
		if(this != &m) {
			Matrix(m).swap(*this);
		}
	
		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() {
		delete [] m_Data;
	}
	
	//------------------------------------------------------------------------------------
	// Name: operator[](size_type x)
	//------------------------------------------------------------------------------------
	reference operator[](size_type x) {
#ifdef CHECKED_INDEXING
		if(x >= m_Width) {
			throw std::out_of_range("x >= width");
		}
#endif
		return reference(*this, x);
	}
	
	//------------------------------------------------------------------------------------
	// Name: reference operator[](size_type x) const
	//------------------------------------------------------------------------------------
	const reference operator[](size_type x) const {
#ifdef CHECKED_INDEXING
		if(x >= m_Width) {
			throw std::out_of_range("x >= width");
		}
#endif
		return reference(*this, x);
	}
	
	//------------------------------------------------------------------------------------
	// 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) {
			Matrix().swap(*this);
		} else {
	
			// allocate the new matrix and initialize it
			T *const t = new T[width * 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 * width] = m_Data[x + y * width];
					}
				}
			}

			// delete the old matrix
			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();
				}
			}
		}
	}
	
	//------------------------------------------------------------------------------------
	// Name: swap(Matrix &other)
	//------------------------------------------------------------------------------------
	void swap(Matrix &other) {
		std::swap(m_Data, other.m_Data);
		std::swap(m_Width, other.m_Width);
		std::swap(m_Height, other.m_Height);
	}
	
private:
	T			*m_Data;
	size_type	m_Width;
	size_type	m_Height;
	
};

#endif
