#ifndef MATRIX_H_
#define MATRIX_H_

#include <iostream>
#include <vector>
#include <functional>

template <typename T, int m, int n>
class matrix
{
    T values[m*n];

    public:
        typedef matrix<T, 1, n> row_t;
        typedef matrix<T, m, 1> column_t;

        matrix()
        {
            for (int i = 0; i < m; ++i)
                for (int j = 0; j < n; j++)
                    values[i*n + j] = T();
        }

        matrix(std::initializer_list<std::initializer_list<T>> list)
        {
            int i = 0, j = 0;
            for (const auto& row : list) {
                j = 0;
                for (const auto& cell : row) {
                    set(i, j++, cell);
                }
                i++;
            }
        }

        T get(int i, int j) const
        {
            return values[i*n + j];
        }

        void set(int i, int j, T value)
        {
            values[i*n + j] = value;
        }

        template <int p> matrix<T, m, p> operator* (const matrix<T, n, p>& rhs) const
        {
            matrix<T, m, p> result;

            for (int i = 0; i < m; ++i) {
                for(int j = 0; j < p; j++) {
                    T accumulator = 0;
                    for(int k = 0; k < n; k++) 
                        accumulator += this->get(i, k) * rhs.get(k, j);

                    result.set(i, j, accumulator);
                }
            }

            return result;
        }

        matrix<T, m, n> operator* (const T& rhs) const
        {
            matrix <T, m, n> result;

            for (int i = 0; i < m; ++i)
                for (int j = 0; j < m; ++j) 
                    result.set(i, j, rhs * get(i, j));

            return result;
        }

        matrix<T, n, m> transpose() const
        {
            matrix<T, n, m> result;

            for (int i = 0; i < m; ++i)
                for (int j = 0; j < m; ++j) 
                    result.set(j, i, get(i, j));


            return result;
        }

        matrix<T, m, n> operator- () const
        {
            matrix<T, m, n> result;

            for (int i = 0; i < m; ++i)
                for (int j = 0; j < n; ++j)
                    result.set(i, j, -get(i, j));

            return result;
        }

        matrix<T, m, n> operator- (const matrix<T, m, n>& rhs) const
        {
            return *this + (-rhs);
        }

        matrix<T, m, n> operator- (const T& value) const
        {
            return *this + (-value);
        }

        matrix<T, m, n> operator+ (const matrix<T, m, n>& rhs) const
        {
            matrix<T, m, n> result;

            for (int i = 0; i < m; ++i)
                for (int j = 0; j < n; ++j)
                    result.set(i, j, this->get(i, j) + rhs.get(i, j));

            return result;
        }

        matrix<T, m, n> operator+ (const T& value) const
        {
            matrix <T, m, n> result;

            for (int i = 0; i < m; ++i)
                for (int j = 0; j < n; ++j)
                    result.set(this->get(i, j) + value);

            return result;
        }

        column_t column(std::size_t j)
        {
            column_t result;

            for (int i = 0; i < m; ++i) {
                result.set(i, 0, get(i, j));
            }
            
            return result;
        }

        row_t row(std::size_t i)
        {
            row_t result;

            for (int j = 0; j < n; ++j) {
                result.set(0, j, get(i, j));
            }
            
            return result;
        }

        friend std::ostream& operator<< (std::ostream& stream, const matrix<T, m, n>& matrix)
        {
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; j++) {
                    stream << matrix.get(i, j) << " ";
                }
                stream << std::endl;
            }

            return stream;
        }
};

template <typename T, int m, int n> matrix<T, m, n> operator*(const T& lhs, const matrix<T, m, n>& rhs) {
    return rhs * lhs;
}

template <typename T, int n> using vector = matrix<T, n, 1>;

template <typename T, int m, int n> void fill(matrix<T, m, n> &mat, std::function<T(const int&, const int&)> filler)
{
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            mat.set(i, j, filler(i, j));
}

template <typename T, int n, int m> 
vector<T, m+n> concat(const vector<T, n> &a, const vector<T, m> &b)  
{
    vector<T, m+n> result;

    for (int i = 0; i < n; i++)
        result.set(i, 0, a.get(i, 0));
    for (int i = 0; i < m; i++)
        result.set(i+n, 0, a.get(i, 0));

    return result;
}

template <typename T, int n>
vector<T, n> normalize(const vector<T, n> &vec)
{
    T accumulator = T();

    for (int i = 0; i < n; i++) {
        T temp = vec.get(i, 0);
        accumulator += temp * temp;
    }

    accumulator = sqrt(accumulator);
    std::function<T(const T&)> normalizer = [accumulator](const T& item) { return item / accumulator; };
    return map(vec, normalizer);
}

template <typename T, int m, int n> 
matrix<T, m, n> map(const matrix<T, m, n>& matrix, std::function<T(const T&)> func)
{
    ::matrix<T, m, n> result;

    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            result.set(i, j, func(matrix.get(i, j)));

    return result;
}

template <typename T, int m, int n> 
matrix<T, m, n> combine(const matrix<T, m, n>& a, const matrix<T, m, n>& b, std::function<T(const T&, const T&)> func)
{
    ::matrix<T, m, n> result;

    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            result.set(i, j, func(a.get(i, j), b.get(i, j)));

    return result;
}

template <typename T, int m, int n> 
matrix<T, m, n> combine(const matrix<T, m, n>& a, const matrix<T, m, n>& b, T c, T d)
{
    return a*c + d*b;
}

#endif