#include "matrix.h"

void Matrix::delete_data() {
    for (int i = 0; i < m; i++) {
        delete [] this->data[i];
    }
    delete [] this->data;

    this->m = 0;
    this->n = 0;
}

void Matrix::insert_matrix(const Matrix& m) {
    this->m = m.m;
    this->n = m.n;

    this->data = new int* [this->m];
    for (int i = 0; i < this->m; i++) {
        this->data[i] = new int [this->n];
        for (int j = 0; j < this->n; j++) {
            this->data[i][j] = m.data[i][j];
        }
    }
}

Matrix::Matrix(int m, int n, int value) {
    this->m = m;
    this->n = n;
    this->data = new int* [this->m];

    for (int i = 0; i < this->m; i++) {
        this->data[i] = new int [this->n];
        for (int j = 0; j < this->n; j++) {
            this->data[i][j] = value;
        }
    }
}

Matrix::Matrix(const Matrix& m) {
    this->insert_matrix(m);
}

Matrix::~Matrix() {
    this->delete_data();
}

Matrix& Matrix::operator= (const Matrix& m) {
    if (this == &m) {
        return *this;
    }

    this->delete_data();

    this->insert_matrix(m);

    return *this;
}

int Matrix::det() {
    if (this->m != this->n) {
        throw logic_error("Determinant of matrix that is not square is undefined!");
    }

    if (this->m == 1) {
        return this->data[0][0];
    } else {
        int res = 0;
        for (int j = 0; j < this->n; j++) {
            // ! alg_add causes recursion
            res += this->data[0][j] * this->alg_add(1, j + 1);
        }
        return res;
    }
}

int Matrix::rank() {
}

Matrix Matrix::get_transposed_matrix() {
    Matrix tmat(this->n, this->m);
    for (int i = 0; i < this->m; i++) {
        for (int j = 0; j < this->n; j++) {
            tmat[j][i] = (*this)[i][j];
        }
    }

    return tmat;
}

Matrix Matrix::get_minor_matrix(int im, int jn) {
    // out arrays begin from 0 and user expects matrix to begin from 1
    im--; jn--;

    if (this->m != this->n) {
        throw logic_error("This function can find only minor of square matrix.");
    }
    if (im > this->m || jn > this->n) {
        throw out_of_range("Minor indexes are out of range.");
    }

    Matrix res(this->m - 1, this->n - 1);

    int ni = 0, nj = 0;
    for (int i = 0; i < this->m; i++) {
        if (i != im) {
            nj = 0;
            for (int j = 0; j < this->n; j++) {
                if (j != jn) {
                    res.data[ni][nj] = this->data[i][j];
                    nj++;
                }
            }
            ni++;
        }
    }

    return res;
}

int Matrix::alg_add(int m, int n) {
    Matrix minor = this->get_minor_matrix(m, n);
    return (((m + n) % 2 == 0)? 1: -1) * minor.det();
}

int* Matrix::operator[] (int i) {
    if (i > this->m) {
        throw out_of_range("");
    }
    return this->data[i];
}

Matrix operator* (const int& k, const Matrix& m) {
    Matrix res(m);
    for (int i = 0; i < res.m; i++) {
        for (int j = 0; j < res.n; j++) {
            res.data[i][j] *= k;
        }
    }
    return res;
}

bool Matrix::operator== (const Matrix& m) {
    if (this->m != m.m || this->n != m.n) {
        return false;
    }
    for (int i = 0; i < m.m; i++) {
        for (int j = 0; j < m.n; j++) {
            if (this->data[i][j] != m.data[i][j]) {
                return false;
            }
        }
    }

    return true;
}

Matrix operator+ (const Matrix& m1, const Matrix& m2) {
    if (m1.m == m2.m && m1.n == m2.n) {
        Matrix res(m1.m, m2.n);
        for (int i = 0; i < m1.m; i++) {
            for (int j = 0; j < m1.n; j++) {
                res.data[i][j] = m1.data[i][j] + m2.data[i][j];
            }
        }
        return res;
    }
    else {
        throw invalid_argument("Matrixes must have the same size!");
    }
}

ostream& operator<< (ostream& out, const Matrix& m) {
    cout << "--" << endl;
    for (int i = 0; i < m.m; i++) {
        out << "| ";
        for (int j = 0; j < m.n; j++) {
            out << m.data[i][j] << " ";
        }
        out << endl;
    }
    cout << "--";
    return out;
}
