// -*- c++ -*-
#pragma once

#include <utility>
#include <valarray>
#include <stdexcept>

template<std::size_t N, std::size_t M>
class ImatrixNM
{
public:
	ImatrixNM()
		: data(0, N * M)
	{
	}

	ImatrixNM(ImatrixNM<N, M>&& other)
	{
		*this = std::move(other);
	}

	ImatrixNM(const ImatrixNM<N, M>& other)
	{
		*this = other;
	}

	ImatrixNM& operator=(ImatrixNM<N, M>&& other)
	{
		data = std::move(other.data);
		return *this;
	}

	ImatrixNM& operator=(const ImatrixNM<N, M>& other)
	{
		data = other.data;
		return *this;
	}

	int& m(std::size_t x, std::size_t y)
	{
		if(x >= N || y >= M) throw std::out_of_range("Subscript out of range.");
		return data[x + y * N];
	}

	const int& m(std::size_t x, std::size_t y) const
	{
		if(x >= N || y >= M) throw std::out_of_range("Subscript out of range.");
		return data[x + y * N];
	}

	ImatrixNM<N, M> operator+(const ImatrixNM<N, M>& other) const
	{
		ImatrixNM m(*this);
		m.data += other.data;
		return m;
	}

	ImatrixNM<N, M> operator+(int val)
	{
		ImatrixNM<N, M> m(*this);
		m.data += val;
		return m;
	}

	ImatrixNM<N, M> operator-(const ImatrixNM<N, M>& other) const
	{
		ImatrixNM<N, M> m(*this);
		m.data -= other.data;
		return m;
	}

	ImatrixNM <N, M>operator-(int val)
	{
		ImatrixNM<N, M> m(*this);
		m.data -= val;
		return m;
	}

	ImatrixNM<N, M> operator%(const ImatrixNM<N, M>& other) const
	{
		ImatrixNM<N, M> m(*this);
		m.data %= other.data;
		return m;
	}

	ImatrixNM<N, M> operator%(int val)
	{
		ImatrixNM m(*this);
		m.data %= val;
		return m;
	}

	template<std::size_t R>
	ImatrixNM<N, R> operator*(const ImatrixNM<M, R>& other) const
	{
		ImatrixNM<N, R> out;
		for(std::size_t i = 0; i < N; ++i)
		{
			for(std::size_t j = 0; j < N; ++j)
			{
				out.m(i,j) = 0;
				for(std::size_t k = 0; k < M; ++k)
				{
					out.m(i,j) += m(i,k) * other.m(k,j);
				}
			}
		}

		return out;
	}

	ImatrixNM<N, M> operator*(int val) const
	{
		ImatrixNM<N, M> m(*this);
		m.data *= val;
		return m;
	}

	template<std::size_t R>
	ImatrixNM<N, R> operator/(const ImatrixNM<M, R>& other) const
	{
		ImatrixNM<N, R> out;
		for(std::size_t i = 0; i < N; ++i)
		{
			for(std::size_t j = 0; j < N; ++j)
			{
				out.m(i,j) = 0;
				for(std::size_t k = 0; k < M; ++k)
				{
					out.m(i,j) += m(i,k) / other.m(k,j);
				}
			}
		}

		return out;
	}

	ImatrixNM<N, M> operator/(int val) const
	{
		ImatrixNM<N, M> m(*this);
		m.data /= val;
		return m;
	}

	struct pos_t
	{
		std::size_t x;
		std::size_t y;
	};

	void Move(pos_t from, pos_t to)
	{
		m(to.x, to.x) = m(from.x, from.x);
		m(from.x, from.y) = 0;
	}

	std::vector<int> Row(std::size_t n) const
	{
		if(n >= M) throw std::out_of_range("Subscript out of range.");
		std::vector<int> out;
		for(std::size_t x = 0; x < N; ++x)
		{
			out.push_back(m(x, n));
		}
		return out;
	}

	std::vector<int> Column(std::size_t n) const
	{
		if(n >= N) throw std::out_of_range("Subscript out of range.");
		std::vector<int> out;
		for(std::size_t y = 0; y < M; ++y)
		{
			out.push_back(m(n, y));
		}
		return out;
	}

private:
	// Invariant; at all times data contains w * h initialized data members
	std::valarray<int> data; // default initialized to empty
};