#ifndef DESCSS_Matrix_h
#define DESCSS_Matrix_h

template <class T>
class matrix {
public:
    matrix(int n, int m);
    matrix(const matrix<T>&);
    ~matrix();

    void Input(T* p);
    void Output();

    int rows() const { return n; };
    int cols() const { return m; };
    T& operator()(int i, int j) const;

    matrix<T>& operator=(const matrix<T>&);
    matrix<T> operator+() const;
    matrix<T> operator+(const matrix<T>&) const;
    matrix<T> operator*(const matrix<T>&) const;
    matrix<T> operator+(const T&) const;
    matrix<T> operator*(const T&) const;
    matrix<T> operator/(const T&) const;

protected:
    int n, m;
    T** element;
};

template <class T>
matrix<T>::matrix(int n, int m) {
    if (m < 0 || n < 0) std::cout << "Rows and Cols must be >= 0 " << std::endl;
    if ((m == 0 || n == 0) && (m != 0 || n != 0))
        std::cout << "Either both or neither rows and columns should be zero " << std::endl;

    this->m = m, this->n = n;
    element = new T*[n];
    for (int i = 0; i < n; i++) element[i] = new T[m];
}

template <class T>
matrix<T>::matrix(const matrix<T>& A) {
    n = A.n, m = A.m;
    element = new T*[n];
    for (int i = 0; i < n; i++) element[i] = new T[m];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) element[i][j] = A.element[i][j];
}

template <class T>
matrix<T>::~matrix() {}

template <class T>
matrix<T>& matrix<T>::operator=(const matrix<T>& A) {
    if (this != &A) {
        delete[] element;
        n = A.n, m = A.m;
        element = new T*[n];
        for (int i = 0; i < n; i++) element[i] = new T[m];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++) element[i][j] = A.element[i][j];
    }
    return *this;
}

template <class T>
void matrix<T>::Input(T* p) {
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) this->element[i][j] = *(p++);
}

template <class T>
void matrix<T>::Output() {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) std::cout << element[i][j] << " ";
        std::cout << std::endl;
    }
}

template <class T>
T& matrix<T>::operator()(int i, int j) const {
    if (i < 1 || i > n || j < 1 || j > m) std::cout << "Matrix Index Out Of Bounds " << std::endl;
    return element[i - 1][j - 1];
}

template <class T>
matrix<T> matrix<T>::operator+(const matrix<T>& A) const {
    if (n != A.n || m != A.m) std::cout << "Matrix Size is Out of batch " << std::endl;
    matrix<T> w(n, m);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) w.element[i][j] = element[i][j] + A.element[i][j];
    return w;
}

template <class T>
matrix<T> matrix<T>::operator+(const T& C) const {
    matrix<T> w(n, m);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) w.element[i][j] = element[i][j] + C;
    return w;
}

template <class T>
matrix<T> matrix<T>::operator*(const matrix<T>& A) const {
    if (m != A.n) std::cout << "Matrix Style is Out of batch " << std::endl;

    matrix<T> w(n, m);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < A.m; j++) {
            w.element[i][j] = 0;
            for (int k = 0; k < m; k++) w.element[i][j] += element[i][k] * A.element[k][j];
        }
    return w;
}

template <class T>
matrix<T> matrix<T>::operator*(const T& C) const {
    matrix<T> w(n, m);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) w.element[i][j] = element[i][j] * C;
    return w;
}

template <class T>
matrix<T> matrix<T>::operator/(const T& C) const {
    matrix<T> w(n, m);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) w.element[i][j] = element[i][j] / C;
    return w;
}

#endif