#include "matrixstruct.h"
#include "head.h"

MATRIX2D* create_matrix2d(int rows,int cols) {
    MATRIX2D* m = (MATRIX2D*)malloc(sizeof(MATRIX2D));
    m->rows = rows;
    m->cols = cols;
    
    m->data = (int**)malloc(rows * sizeof(int*));

    for (int i = 0; i < rows; i++){
        m->data[i] = (int*)malloc(cols * sizeof(int));
    }

    return m;
}

void create_matrix(MATRIX2D** __addr,unsigned char n,int rows,int cols){
    __addr[n] = create_matrix2d(rows,cols);
    for (int i = 0;i<rows;i++){
        for(int j = 0;j<cols;j++){
            __addr[n]->data[i][j] = 0;
        }
    }   
}

void input_matrix(MATRIX2D** __addr,unsigned char n){
    int** data = __addr[n]->data;

    for(int rows = 0; rows < __addr[n]->rows;rows++){
        for(int cols = 0; cols < __addr[n]->cols;cols++){
                int num = get_num();
                data[rows][cols] = num;
            }
        }
    }

    void get_matrix(MATRIX2D** __addr,unsigned char n){
        int rows = __addr[n]->rows;
        int cols = __addr[n]->cols;
    
    //printf("\tМассив №%d",n);
    //printf("Колл-во строк %d\nКолл-во колон %d\n\n",rows,cols);
    for(int r = 0;r<rows;r++){
        for(int c = 0;c<cols;c++){
            printf("%d ",__addr[n]->data[r][c]);
        }
        putchar('\n');
    }
}

void logic_matrix(MATRIX2D** __addr,unsigned char n1,unsigned char n2){
    char inbuff;
    char _buff[BUFF_MAX];
    int** data1 = __addr[n1]->data;
    int** data2 = __addr[n2]->data;
    
    char cmd = 0;
    {
        buff_input(&inbuff,_buff);
        
        const char NUM_OF_CHAR_COMMANDS = 6;
        char * _charCommands[NUM_OF_CHAR_COMMANDS]; // _charCommands[*]["char"]
        _charCommands[0]    = (char[]){"=="};
        _charCommands[1]    = (char[]){"!="};
        _charCommands[2]    = (char[]){">="};
        _charCommands[3]    = (char[]){"<="};
        _charCommands[4]    = (char[]){">"};
        _charCommands[5]    = (char[]){"<"};

        cmd = cmd_buff(_buff,_charCommands,NUM_OF_CHAR_COMMANDS);
    }

    long long int cost1 = 0;
    {
        for(int r = 0;r<__addr[n1]->rows;r++)
            for(int c = 0;c<__addr[n1]->cols;c++)
                cost1 += data1[r][c];
    }
    long long int cost2 = 0;
    {
        for(int r = 0;r<__addr[n2]->rows;r++)
            for(int c = 0;c<__addr[n2]->cols;c++)
                cost2 += data2[r][c];
    }

    switch(cmd){
        case 0: //Null
                putchar('-');putchar('1');
            break;
        case 1: // ==
                putchar(cost1==cost2?'1':'0');
            break;
        case 2: // !=
                putchar(cost1!=cost2?'1':'0');
            break;
        case 3: //>=
                putchar(cost1>=cost2?'1':'0');
            break;
        case 4: //<=
                putchar(cost1<=cost2?'1':'0');
            break;
        case 5: // >
                putchar(cost1>cost2?'1':'0');
            break;
        case 6: // <
                putchar(cost1<cost2?'1':'0');
            break;
    }
}

void edit_matrix(MATRIX2D** __addr, unsigned char n) {
    char inbuff;
    char _buff[BUFF_MAX];
    int** data = __addr[n]->data;
    int r = 0, c = 0;
    char mode = 0;

    char* _locStrings[3];
    _locStrings[0] = (char[]){"point"};
    _locStrings[1] = (char[]){"add"};
    _locStrings[2] = (char[]){"sub"};

    while (r < __addr[n]->rows) {
        char res = buff_input(&inbuff, _buff);
        if (res == -1) break;

        if (_buff[0] == 'n' && inbuff == 1) break;

        char loc_cmd = cmd_buff(_buff, _locStrings, 3);

        if (loc_cmd == 1) {
            buff_input(&inbuff, _buff);
            long pr = 0, pc = 0;
            int i = 0;
            for (; i < inbuff && (_buff[i] != ':' && _buff[i] != ',' && _buff[i] != '.'); i++)
                if (_buff[i] >= '0' && _buff[i] <= '9') pr = pr * 10 + (_buff[i] - '0');
            for (i++; i < inbuff; i++)
                if (_buff[i] >= '0' && _buff[i] <= '9') pc = pc * 10 + (_buff[i] - '0');

            if (pr < __addr[n]->rows && pc < __addr[n]->cols) {
                r = (int)pr;
                c = (int)pc;
            }
            continue;
        }

        if (loc_cmd == 2) { mode = 1; continue; }
        if (loc_cmd == 3) { mode = 2; continue; }

        long val = 0;
        int i = 0, sign = 1;
        if (inbuff > 0 && _buff[0] == '-') { sign = -1; i++; }
        for (; i < inbuff; i++) {
            if (_buff[i] >= '0' && _buff[i] <= '9')
                val = val * 10 + (_buff[i] - '0');
        }
        val *= sign;

        if (mode == 1) {
            long res_val = (long)data[r][c] + val;
            data[r][c] = (res_val > INT_MAX) ? INT_MAX : (res_val < INT_MIN) ? INT_MIN : (int)res_val;
        } else if (mode == 2) {
            long res_val = (long)data[r][c] - val;
            data[r][c] = (res_val > INT_MAX) ? INT_MAX : (res_val < INT_MIN) ? INT_MIN : (int)res_val;
        } else {
            data[r][c] = (val > INT_MAX) ? INT_MAX : (val < INT_MIN) ? INT_MIN : (int)val;
        }

        if (++c >= __addr[n]->cols) {
            c = 0;
            r++;
        }
    }
}

int get_rand(int min, int max) {
    return (double)rand() / (RAND_MAX + 1.0) * (max - min) + min;
}

void matrix_random(MATRIX2D** __addr,unsigned char n){
    unsigned int lo, hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    srand(((unsigned long long)hi << 32) | lo);

    for (int r = 0; r < __addr[n]->rows; r++){
        for(int c = 0; c < __addr[n]->cols; c++){
            __addr[n]->data[r][c] = get_rand(-100, 100);
        }
    }
}

void copy_matrix(MATRIX2D** __addr,unsigned char n1, unsigned char n2){
    // n2 = n1
    __addr[n2] = create_matrix2d(__addr[n1]->rows,__addr[n1]->cols);
    
    for(int i = 0; i<__addr[n1]->rows;i++){
        for(int j = 0; j<__addr[n1]->cols;j++){
            __addr[n2]->data[i][j] = __addr[n1]->data[i][j]; 
        }
    }
}

void free_matrix(MATRIX2D** __addr,unsigned char n){
    if (__addr[n] == NULL) return;

    MATRIX2D* m = __addr[n];
    for (int i = 0; i < m->rows; i++) {
        free(m->data[i]); 
    }
    free(m->data);
    free(m);

    __addr[n] = NULL;
}

void get_matrix_col(MATRIX2D** __addr,unsigned char n,int c){
    //printf("colum %d - ",c);
    if(c >= __addr[n]->cols)c = __addr[n]->cols - 1;
    if(c<0)c = 0;
    for(int i = 0;i<__addr[n]->rows;i++){
        printf("%d ",__addr[n]->data[i][c]); 
    }
    putchar('\n');
}

void get_matrix_row(MATRIX2D** __addr,unsigned char n,int r){
    //printf("row %d - ",r);
    if(r >= __addr[n]->rows)r = __addr[n]->rows - 1;
    if(r<0)r = 0;
    for(int i = 0;i<__addr[n]->cols;i++){
        printf("%d ",__addr[n]->data[r][i]); 
    }
    putchar('\n');
}

void transp_matrix(MATRIX2D** __addr,unsigned char n){
    MATRIX2D* tr = create_matrix2d(
        __addr[n]->cols,__addr[n]->rows);


    int** dataDef = __addr[n]->data;
    int** dataRes = tr->data;

    for(int r = 0;r<__addr[n]->rows;r++)
        for(int c = 0;c<__addr[n]->cols;c++)
            dataRes[c][r] = dataDef[r][c];

    free_matrix(__addr,n);
    
    __addr[n] = tr;
}

long long int determ_matrix(MATRIX2D** __addr, unsigned char n) {
    int size = __addr[n]->rows;
    int** data = __addr[n]->data;

    if (size == 1) return data[0][0];
    if (size == 2) return (long long int)data[0][0] * data[1][1] - (long long int)data[0][1] * data[1][0];

    double det = 1.0;
    double** temp = (double**)malloc(size * sizeof(double*));
    for (int i = 0; i < size; i++) {
        temp[i] = (double*)malloc(size * sizeof(double));
        for (int j = 0; j < size; j++) temp[i][j] = (double)data[i][j];
    }

    for (int i = 0; i < size; i++) {
        int pivot = i;
        for (int j = i + 1; j < size; j++) {
            double val1 = temp[j][i] < 0 ? -temp[j][i] : temp[j][i];
            double val2 = temp[pivot][i] < 0 ? -temp[pivot][i] : temp[pivot][i];
            if (val1 > val2) pivot = j;
        }

        if (pivot != i) {
            double* swap = temp[i];
            temp[i] = temp[pivot];
            temp[pivot] = swap;
            det *= -1;
        }

        double current_val = temp[i][i] < 0 ? -temp[i][i] : temp[i][i];
        if (current_val < 1e-9) { 
            for (int k = 0; k < size; k++) free(temp[k]);
            free(temp);
            return 0;
        }

        det *= temp[i][i];

        for (int j = i + 1; j < size; j++) {
            double factor = temp[j][i] / temp[i][i];
            for (int k = i + 1; k < size; k++) {
                temp[j][k] -= factor * temp[i][k];
            }
        }
    }

    long long int final_det;
    if (det >= 0) final_det = (long long int)(det + 0.5);
    else final_det = (long long int)(det - 0.5);

    for (int i = 0; i < size; i++) free(temp[i]);
    free(temp);

    return final_det;
}

void obr_matrix(MATRIX2D** __addr, unsigned char n) {
    int size = __addr[n]->rows;
    if (size != __addr[n]->cols) return; 

    long long int d = determ_matrix(__addr, n);
    if (d == 0) return; 

    double** temp = (double**)malloc(size * sizeof(double*));
    for (int i = 0; i < size; i++) {
        temp[i] = (double*)malloc(2 * size * sizeof(double));
        for (int j = 0; j < size; j++) {
            temp[i][j] = (double)__addr[n]->data[i][j];
            temp[i][j + size] = (i == j ? 1.0 : 0.0); 
        }
    }

    for (int i = 0; i < size; i++) {
        int pivot = i;
        for (int j = i + 1; j < size; j++) {
            double v1 = temp[j][i] < 0 ? -temp[j][i] : temp[j][i];
            double v2 = temp[pivot][i] < 0 ? -temp[pivot][i] : temp[pivot][i];
            if (v1 > v2) pivot = j;
        }

        double* row_ptr = temp[i];
        temp[i] = temp[pivot];
        temp[pivot] = row_ptr;

        double divisor = temp[i][i];
        for (int j = i; j < 2 * size; j++) {
            temp[i][j] /= divisor;
        }

        for (int k = 0; k < size; k++) {
            if (k != i) {
                double factor = temp[k][i];
                for (int j = i; j < 2 * size; j++) {
                    temp[k][j] -= factor * temp[i][j];
                }
            }
        }
    }

    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            double val = temp[i][j + size];
            if (val >= 0) __addr[n]->data[i][j] = (int)(val + 0.5);
            else __addr[n]->data[i][j] = (int)(val - 0.5);
        }
    }

    for (int i = 0; i < size; i++) free(temp[i]);
    free(temp);
}

void is_Matrix_Exist(MATRIX2D** __addr,unsigned char n){
    if(n > MAX_MATRIX_ALIVE) n = MAX_MATRIX_ALIVE-1;
    if(__addr[n] == NULL){
        //printf("Матрица №%d не существует, создана новая матрица 4x4\n",n);
        __addr[n] = create_matrix2d(4,4);
        matrix_random(__addr,n);
        }

    return;
}

