#pragma once

#include "vector2.h"


namespace lm
{


//! 2次正方行列
/*

 インデックスは数学の行列と一致
 [ (0,0) , (0,1) ]
 [ (1,0) , (1,1) ]

 通しインデックスは
 [ 0 , 1 ]
 [ 2 , 3 ]

 積の結合方向は

 [ax] = [bx by] * [ m00 m01 ]
 [ay]             [ m10 m11 ]

 [ax] = [ m00 m01 ] * [bx]
 [ay]   [ m10 m11 ]   [by]

 ベクトルの行列変換の正方向は
 v' = v * m;

*/
template<typename T>
class matrix2
{
public:
	enum
	{
		WIDTH        = 2 , //!< 行,列の幅
		NUM_ELEMENTS = 4 , //!< 全要素数
	};

public:
	T m00 , m01 ,
	  m10 , m11 ;

public:
	matrix2(void)
		: m00(0) , m01(0)
		, m10(0) , m11(0)
	{}

	matrix2(const T* _v)
		: m00(_v[0]) , m01(_v[1])
		, m10(_v[2]) , m11(_v[3])
	{}

	matrix2(const matrix2<T>& m)
		: m00(m.m00) , m01(m.m01)
		, m10(m.m10) , m11(m.m11)
	{}


	// バッファの先頭を直接参照
	T*       v(void)       { return &m00; }
	const T* v(void) const { return &m00; }


	// 通しインデックスで要素取得
	      T& operator[](size_t idx);
	const T& operator[](size_t idx) const;
	      T& operator()(size_t idx);
	const T& operator()(size_t idx) const;

	      T& at(size_t idx);
	const T& at(size_t idx) const;


	// row(行) , col(列) のインデックスで要素取得
	      T& operator()(size_t idx_row, size_t idx_col);
	const T& operator()(size_t idx_row, size_t idx_col) const;

	      T& at(size_t idx_row, size_t idx_col);
	const T& at(size_t idx_row, size_t idx_col) const;


	void set( const matrix2<T>& m );
	void set( const T* _v );
	matrix2<T>& operator=( const matrix2<T>& m );

	// 全要素に同じ値をセットする
	void fill( const T& _v );

	//! 単位行列化
	void set_identity(void);
	//! 零行列化
	void set_zero(void);

	//! 単位行列生成
	static const matrix2<T>& get_identity(void);
	//! 零行列生成
	static const matrix2<T>& get_zero(void);

	//! 転置
	void transpose(void);
	matrix2<T> get_transpose(void) const;


	bool equals( const matrix2<T>& m ) const;
	bool equals( const T* _v ) const;
	bool operator==( const matrix2<T>& m ) const;
	bool operator!=( const matrix2<T>& m ) const;


	matrix2<T>& operator+=( const matrix2<T>& m );
	matrix2<T>& operator-=( const matrix2<T>& m );
	matrix2<T>& operator*=( const float& f );
	matrix2<T>& operator/=( const float& f );

	matrix2<T>& operator*=( const matrix2<T>& m );
};

typedef matrix2<float>  matrix2f;
typedef matrix2<double> matrix2d;



// global method

template<typename T>
matrix2<T> operator+( const matrix2<T>& m1 , const matrix2<T>& m2 );
template<typename T>
matrix2<T> operator-( const matrix2<T>& m1 , const matrix2<T>& m2 );
template<typename T>
matrix2<T> operator*( const matrix2<T>& m1 , const matrix2<T>& m2 );

template<typename T>
matrix2<T> operator*( const matrix2<T>& m1 , const T& val );
template<typename T>
matrix2<T> operator*( const T& val , const matrix2<T>& m1 );

template<typename T>
matrix2<T> operator/( const matrix2<T>& m1 , const T& val );

// vector2 との積
// ※ { v *= m } == { v = v * m } != { v = m * v }
template<typename T>
vector2<T>& operator*=( vector2<T>& v , const matrix2<T>& m );
template<typename T>
vector2<T> operator*( const vector2<T>& v , const matrix2<T>& m );

template<typename T>
vector2<T> operator*( const matrix2<T>& m , const vector2<T>& v );



// matrix2 implementation

template<typename T> inline
T& matrix2<T>::operator[](size_t idx)
{
	return at(idx);
}
template<typename T> inline
const T& matrix2<T>::operator[](size_t idx) const
{
	return at(idx);
}
template<typename T> inline
T& matrix2<T>::operator()(size_t idx)
{
	return at(idx);
}
template<typename T> inline
const T& matrix2<T>::operator()(size_t idx) const 
{
	return at(idx);
}
template<typename T> inline
T& matrix2<T>::operator()(size_t idx_row, size_t idx_col)
{
	return at( idx_row , idx_col );
}
template<typename T> inline
const T& matrix2<T>::operator()(size_t idx_row, size_t idx_col) const
{
	return at( idx_row , idx_col );
}

template<typename T> inline
T& matrix2<T>::at(size_t idx)
{
	return v()[idx];
}

template<typename T> inline
const T& matrix2<T>::at(size_t idx) const
{
	return v()[idx];
}

template<typename T> inline
T& matrix2<T>::at(size_t idx_row, size_t idx_col)
{
	return v()[ idx_col + idx_row * WIDTH ];
}

template<typename T> inline
const T& matrix2<T>::at(size_t idx_row, size_t idx_col) const
{
	return v()[ idx_col + idx_row * WIDTH ];
}


template<typename T> inline
void matrix2<T>::set( const matrix2<T>& m )
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		this->at(i) = m.at(i);
}

template<typename T> inline
void matrix2<T>::set( const T* _v )
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		this->at(i) = _v[i];
}

template<typename T> inline
matrix2<T>& matrix2<T>::operator=( const matrix2<T>& m )
{
	set(m);
	return *this;
}


template<typename T> inline
void matrix2<T>::fill( const T& _v )
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		this->at(i) = _v;
}


template<typename T> inline
void matrix2<T>::set_identity(void)
{
	set( get_identity() );
}

template<typename T> inline
void matrix2<T>::set_zero(void)
{
	set( get_zero() );
}

template<typename T> inline
const matrix2<T>& matrix2<T>::get_identity(void)
{
	static const T v[NUM_ELEMENTS]
		= { T(1) , T(0) ,
		    T(0) , T(1) };

	static const matrix2<T> m(v);
	return m;
}

template<typename T> inline
const matrix2<T>& matrix2<T>::get_zero(void)
{
	static const T v[NUM_ELEMENTS]
		= { T(0) , T(0) 
		    T(0) , T(0) };

	static const matrix2<T> m(v);
	return m;
}

template<typename T> inline
void matrix2<T>::transpose(void)
{
	(std::swap)( m01 , m10 );
}

template<typename T> inline
matrix2<T> matrix2<T>::get_transpose(void) const
{
	matrix2<T> m = (*this);
	m.transpose();
	return m;
}


template<typename T> inline
bool matrix2<T>::equals( const matrix2<T>& m ) const
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		if( m.at(i) != at(i) ) return false;
	return true;
}

template<typename T> inline
bool matrix2<T>::equals( const T* _v ) const
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		if( m[i] != at(i) ) return false;
	return true;
}

template<typename T> inline
bool matrix2<T>::operator==( const matrix2<T>& m ) const
{
	return equals( m );
}

template<typename T> inline
bool matrix2<T>::operator!=( const matrix2<T>& m ) const
{
	return !equals( m );
}

template<typename T> inline
matrix2<T>& matrix2<T>::operator+=( const matrix2<T>& m )
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		at(i) += m[i];

	return *this;
}

template<typename T> inline
matrix2<T>& matrix2<T>::operator-=( const matrix2<T>& m )
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		at(i) -= m[i];

	return *this;
}

template<typename T> inline
matrix2<T>& matrix2<T>::operator*=( const float& val )
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		at(i) *= val;

	return *this;
}

template<typename T> inline
matrix2<T>& matrix2<T>::operator/=( const float& val )
{
	for( size_t i = 0 ; i < NUM_ELEMENTS ; ++i )
		at(i) /= val;

	return *this;
}

template<typename T> inline
matrix2<T>& matrix2<T>::operator*=( const matrix2<T>& m )
{
	matrix2<T> tmp;

	tmp.m00 = this->m00 * m.m00 + this->m01 * m.m10;
	tmp.m10 = this->m10 * m.m00 + this->m11 * m.m10;

	tmp.m01 = this->m00 * m.m01 + this->m01 * m.m11;
	tmp.m11 = this->m10 * m.m01 + this->m11 * m.m11;

	(*this) = tmp;

	return *this;
}



// global method implements

template<typename T> inline
matrix2<T> operator+( const matrix2<T>& m1 , const matrix2<T>& m2 )
{
	matrix2<T> ret = m1;
	ret += m1;
	return ret;
}

template<typename T> inline
matrix2<T> operator-( const matrix2<T>& m1 , const matrix2<T>& m2 )
{
	matrix2<T> ret = m1;
	ret -= m1;
	return ret;
}

template<typename T> inline
matrix2<T> operator*( const matrix2<T>& m1 , const matrix2<T>& m2 )
{
	matrix2<T> ret = m1;
	ret *= m2;
	return ret;
}

template<typename T> inline
matrix2<T> operator*( const matrix2<T>& m1 , const T& val )
{
	matrix2<T> ret = m1;
	ret *= val;
	return ret;
}

template<typename T> inline
matrix2<T> operator*( const T& val , const matrix2<T>& m1 )
{
	matrix2<T> ret = m1;
	ret *= val;
	return ret;
}

template<typename T> inline
matrix2<T> operator/( const matrix2<T>& m1 , const T& val )
{
	matrix2<T> ret = m1;
	ret /= val;
	return ret;
}

template<typename T> inline
vector2<T>& operator*=( vector2<T>& v , const matrix2<T>& m )
{
	vector2<T> tmp = v;
	v.x = tmp.x * m.m00 + tmp.y * m.m10;
	v.y = tmp.x * m.m01 + tmp.y * m.m11;

	return v;
}

template<typename T> inline
vector2<T> operator*( const vector2<T>& v , const matrix2<T>& m )
{
	vector2<T> ret = v;
	ret *= m;
	return ret;
}

template<typename T> inline
vector2<T> operator*( const matrix2<T>& m , const vector2<T>& v )
{
	vector2<T> ret;
	ret.x = v.x * m.m00 + v.y * m.m01;
	ret.y = v.x * m.m10 + v.y * m.m11;

	return ret;
}


}
