import random
import time
import copy
from multiprocessing import Pool
import concurrent.futures
from copy import deepcopy


class Matrix:
    def __init__(self) -> None:
        self.matrix_100 = [[0] * 100 for _ in range(100)]
        self.matrix_300 = [[0] * 300 for _ in range(300)]
        self.matrix_500 = [[0] * 500 for _ in range(500)]
        
    def str_matrix(self, type_list: str):
        _str = ""
        
        current_matrix = getattr(self, type_list)
                
        for i in range(len(current_matrix)):
            _str += "[ "
            
            for j in range(len(current_matrix[0])):
                _str += str(current_matrix[i][j]) + " "
                
            _str += " ]\n"
            
        return _str

# Глобальный объект класса для хранения результата работы потоков
result_matrix = copy.deepcopy(Matrix())

def init_matrix(matrix: Matrix, size: int):
    support_list_main = []
    
    for i in range(size):
        support_list_column = []
        
        for j in range(size):
            support_list_column.append(random.randint(0, 5))
            
        support_list_main.append(support_list_column)
        
    if size == 100:
        matrix.matrix_100 = support_list_main
    elif size == 300:
        matrix.matrix_300 = support_list_main
    elif size == 500:
        matrix.matrix_500 = support_list_main
            
# Функция параллельной обработки строк        
def process_row(args):
    i, j, m, n = args
    
    factor = m[j][i] / m[i][i]
    
    for k in range(i, n):
        m[j][k] -= factor * m[i][k]
        
    return m[j]

def parallel_det(matrix, num_threads=1):
    n = len(matrix)

    # Создаем копию матрицы, чтобы не изменять исходную
    m = deepcopy(matrix)
    
    det_value = 1

    # Функция для параллельной обработки строк
    def process_row(i, j):
        factor = m[j][i] / m[i][i]
        for k in range(i, n):
            m[j][k] -= factor * m[i][k]

    for i in range(n):
        # Поиск ненулевого элемента в текущем столбце для обмена строк
        if m[i][i] == 0:
            for j in range(i + 1, n):
                if m[j][i] != 0:
                    m[i], m[j] = m[j], m[i]
                    
                    # Меняем знак определителя при обмене строк
                    det_value *= -1  
                    
                    break
            else:
                # Если все элементы в столбце равны 0, то определитель равен 0
                return 0 

        # Приведение матрицы к треугольному виду с использованием потоков
        with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
            # Параллельно обрабатываем строки ниже текущей (от i+1 до n)
            futures = [
                executor.submit(process_row, i, j) for j in range(i + 1, n)
            ]
            concurrent.futures.wait(futures)

        # Умножаем на диагональный элемент
        det_value *= m[i][i]

    return det_value 

def det(matrix):
    n = len(matrix)
    # копия матрицы для сохранения исходной
    m = [row[:] for row in matrix]
    det_value = 1

    for i in range(n):
        # Поиск ненулевого элемента в текущем столбце для обмена строк
        if m[i][i] == 0:
            for j in range(i + 1, n):
                if m[j][i] != 0:
                    m[i], m[j] = m[j], m[i]
                    
                    # Меняем знак определителя при обмене строк
                    det_value *= -1  
                    
                    break
            else:
                return 0  # Если все элементы в столбце равны 0, то определитель равен 0

        # Приведение матрицы к треугольному виду
        for j in range(i + 1, n):
            factor = m[j][i] / m[i][i]
            
            for k in range(i, n):
                m[j][k] -= factor * m[i][k]

        # Умножаем на диагональный элемент
        det_value *= m[i][i]

    return det_value
            
def run_program():
    matrix = Matrix()
    init_matrix(matrix, 100)
    init_matrix(matrix, 300)
    init_matrix(matrix, 500)
    
    start_time = time.time()
    print(f"100x100:{det(matrix.matrix_100)}")
    end_time = time.time()
    print("Time 100x100: ", end_time - start_time)
    
    start_time = time.time()
    print(f"100x100:{parallel_det(matrix.matrix_100, 3)}")
    end_time = time.time()
    print("Time 100x100: ", end_time - start_time)
    
    start_time = time.time()
    print(f"100x100:{parallel_det(matrix.matrix_100, 5)}")
    end_time = time.time()
    print("Time 100x100: ", end_time - start_time)
    
    start_time = time.time()
    print(f"100x100:{parallel_det(matrix.matrix_100, 8)}")
    end_time = time.time()
    print("Time 100x100: ", end_time - start_time)
    
    # ----------------------------------------------------------------------------------------------------
    print("\n" + "-" * 50 + "\n")
    
    start_time = time.time()
    print(f"300x300:{det(matrix.matrix_300)}")
    end_time = time.time()
    print("Time 300x300: ", end_time - start_time)
    
    start_time = time.time()
    print(f"300x300:{parallel_det(matrix.matrix_300, 3)}")
    end_time = time.time()
    print("Time 300x300: ", end_time - start_time)
    
    start_time = time.time()
    print(f"300x300:{parallel_det(matrix.matrix_300, 5)}")
    end_time = time.time()
    print("Time 300x300: ", end_time - start_time)
    
    start_time = time.time()
    print(f"300x300:{parallel_det(matrix.matrix_300, 8)}")
    end_time = time.time()
    print("Time 300x300: ", end_time - start_time)
    
    # ----------------------------------------------------------------------------------------------------
    print("\n" + "-" * 50 + "\n")
    
    start_time = time.time()
    print(f"500x500:{det(matrix.matrix_500)}")
    end_time = time.time()
    print("Time 500x500: ", end_time - start_time)
    
    start_time = time.time()
    print(f"500x500:{parallel_det(matrix.matrix_500, 3)}")
    end_time = time.time()
    print("Time 500x500: ", end_time - start_time)
    
    start_time = time.time()
    print(f"500x500:{parallel_det(matrix.matrix_500, 5)}")
    end_time = time.time()
    print("Time 500x500: ", end_time - start_time)
    
    start_time = time.time()
    print(f"500x500:{parallel_det(matrix.matrix_500, 8)}")
    end_time = time.time()
    print("Time 500x500: ", end_time - start_time)
    
run_program()