// Отключение предупреждений безопасности
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_NONSTDC_NO_DEPRECATE

// Коды ошибок
#define E_THREAD_ALLOC  1
#define E_THREAD_CREATE 2

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#include <math.h>


// --- Настройки программы ---

// Включение режима отладки
// #define DEBUG

// Число запусков теста
#define TEST_COUNT 5


// --- Настройки потоков ---

// Ограничения на число потоков
#define MIN_THREADS 1
#define MAX_THREADS 12


int gen_random(float, float);
int matrix_create(const char*, int, int);
void print_log(const char*, ...);
void print_err(const char*);

DWORD WINAPI MyThreadFunction(LPVOID lpParam);

// Структура данных потока
typedef struct ThreadData {
    int i;     // Номер потока
} TDATA, *PTDATA;

int **A;
int **B;
int **C;

// Размерности матриц А{mxn}, B{nxq}, C{mxq}
int n = 50000, m = 50000, q = 50000;

// Пути к файлам исходных данных и результата
const char *A_path = "A.txt";    FILE *Astream;
const char *B_path = "B.txt";    FILE *Bstream;
const char *C_path = "C.txt";    FILE *Cstream;
const char *L_path = "lab.log";  FILE *Lstream;

// Числа строк матрицы A, обрабатываемые потоками
int Arows[MAX_THREADS];

// Номера строк матрицы A, обрабатываемые потоками
int Nrows[MAX_THREADS];

int _tmain()
{
    srand((UINT32) time(NULL));
    
    int i, j, h, d;

    // Время выполнения каждого из запусков программы
    double time[TEST_COUNT];

    PTDATA pDataArray[MAX_THREADS];
    DWORD  dwThreadIdArray[MAX_THREADS];
    HANDLE hThreadArray[MAX_THREADS];

    // Запускаем программу для 1, 2, ..., MAX_THREADS потоков
    for(int threads = MIN_THREADS; threads <= MAX_THREADS; threads++)
    {
        // Запускаем тест TEST_COUNT раз
        for(h = 0; h < TEST_COUNT; h++)
        {
            // Фиксируем время начала работы программы
            time[h] = (double) clock();

            // Пытаемся открыть файлы с матрицами A и B
            Astream = fopen(A_path, "r");
            Bstream = fopen(B_path, "r");
            
            // Обработка ошибок при открытии файлов
            if(Astream == NULL || Bstream == NULL)
            {
                if((Astream != NULL && fclose(Astream)) || (Bstream != NULL && fclose(Bstream)))
                {
                    print_log("[ERROR] Не удалось закрыть один из исходных файлов");
                    return 0;
                }

#ifdef DEBUG
                // Генереция новых файлов с матрицами A и B
                if(!matrix_create(A_path, m, n) || !matrix_create(B_path, n, q))
                {
                    print_log("[ERROR] Не удалось сгенерировать матрицы A и B");
                    return 0;
                }
#else
                matrix_create(A_path, m, n);
                matrix_create(B_path, n, q);
#endif // DEBUG
                    
                // Не учитываем время, затраченное на генерацию матриц
                time[h] = (double) clock(); 

                Astream = fopen(A_path, "r");
                Bstream = fopen(B_path, "r");

#ifdef DEBUG
                if(Astream == NULL || Bstream == NULL)
                {
                    print_log("[ERROR] Не удалось сгенерировать матрицы A и B");
                    return 0;
                }
#endif // DEBUG
            }

#ifdef DEBUG
            // Считываем  размерности матриц
            if(fscanf(Astream, "%d %d\n", &m, &n) <= 0 || fscanf(Bstream, "%d %d\n", &d, &q) <= 0)
            {
                print_log("[ERROR] Не удалось считать размерности матриц");
                fclose(Astream);
                fclose(Bstream);
                return 0;
            }

            if(n != d || n <= 0 || m <= 0 || q <= 0)
            {
                if(n != d)
                    print_log("[ERROR] Не верно заданы размерности матриц: Число столбцов в матрице A не совпадает с числом строк в матрице B");

                if(n <= 0 || m <= 0 || q <= 0)
                    print_log("[ERROR] Не верно заданы размерности матриц: Размерность не может быть меньше либо равна нулю");

                fclose(Astream);
                fclose(Bstream);
                return 0;
            }
#else
            fscanf(Astream, "%d %d\n", &m, &n);
            fscanf(Bstream, "%d %d\n", &d, &q);
#endif // DEBUG

            // Делим строки матрицы A между потоками
            d = (int) floor(double(m / threads));
            for(i = 0; i < threads; i++)
            {
                Arows[i] = d;
                Nrows[i] = i * d;
            }
            d = m % threads;
            for(i = 0; d != 0; d--)
            {
                Arows[i]++;
                for(j = i + 1; j < threads; j++)
                    Nrows[j]++;
                i = (i < threads) ? i + 1 : 0;
            }

            // Запись информации о группе тестов в лог-файл
            if(!h)
            {
                time[h] = (double) clock() - time[h];
                if(threads == MIN_THREADS)
                    print_log("----------------------------------------------\nРазмерности матриц m=%d n=%d q=%d\n\n", m, n, q);
                print_log("Число потоков      %d\n\n", threads);
#ifdef DEBUG
                print_log("[DEBUG] Распределение строк матрицы A между потоками:\n{");
                for(i = 0; i < threads - 1; i++)
                    print_log("%d (%d), ", Arows[i], Nrows[i]);
                print_log("%d (%d)}\n\n", Arows[threads - 1], Nrows[threads - 1]);
#endif // DEBUG
                time[h] = (double) clock() - time[h]; 
            }

            // Выделение памяти
            A = (int **) malloc(m * sizeof(int *));
            B = (int **) malloc(n * sizeof(int *));
            C = (int **) malloc(m * sizeof(int *));

#ifdef DEBUG
            if(A == NULL || B == NULL || C == NULL)
            {
                print_err("Не удалось выделить память");
                fclose(Astream);
                fclose(Bstream);
                if(A != NULL) free(A);
                if(B != NULL) free(B);
                if(C != NULL) free(C);
                return 0;
            }
#endif // DEBUG

            for(i = 0; i < m; i++)
            {
                A[i] = (int *) malloc(n * sizeof(int));
                C[i] = (int *) malloc(q * sizeof(int));
#ifdef DEBUG
                if(A[i] == NULL || C[i] == NULL)
                {
                    print_err("Не удалось выделить память");
                    fclose(Astream);
                    fclose(Bstream);
                    free(A); free(B); free(C);
                    return 0;
                }
#endif // DEBUG
            }

            for(i = 0; i < n; i++)
            {
                B[i] = (int *) malloc(q * sizeof(int));
#ifdef DEBUG
                if(B[i] == NULL)
                {
                    print_err("Не удалось выделить память");
                    fclose(Astream);
                    fclose(Bstream);
                    free(A); free(B); free(C);
                    return 0;
                }
#endif // DEBUG
            }

            // Чтение матрицы A
            for(i = 0; i < m; i++)
            {
                for(j = 0; j < n; j++)
                {
#ifdef DEBUG
                    if(fscanf(Astream, "%d", &A[i][j]) <= 0)
                    {
                        print_err("Не удалось считать элемент матрицы A");
                        fclose(Astream);
                        fclose(Bstream);
                        free(A); free(B); free(C);
                        return 0;
                    }
#else
                    fscanf(Astream, "%d", &A[i][j]);
#endif // DEBUG
                }
            }
            fclose(Astream);
             
            // Чтение матрицы B
            for(i = 0; i < n; i++)
            {
                for(j = 0; j < q; j++)
                {
#ifdef DEBUG
                    if(fscanf(Bstream, "%d", &B[i][j]) <= 0)
                    {
                        print_err("Не удалось считать элемент матрицы B");
                        fclose(Bstream);
                        free(A); free(B); free(C);
                        return 0;
                    }
#else
                    fscanf(Bstream, "%d", &B[i][j]);
#endif // DEBUG
                }
            }
            fclose(Bstream);

            // Обнуление результирующей матрицы C
            for(i = 0; i < m; i++)
                for(j = 0; j < q; j++)
                    C[i][j] = 0;

            for(i = 0; i < threads; i++)
            {
                // Выделяем память для потока
                pDataArray[i] = (PTDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TDATA));

#ifdef DEBUG
                // Если не удалось выделить память, то прерываем выполнение программы
                if(pDataArray[i] == NULL)
                {
                    print_log("[ERROR] Не удалось выделить память для потока\n");
                    free(A); free(B); free(C);
                    ExitProcess(E_THREAD_ALLOC);
                }
#endif // DEBUG

                pDataArray[i]->i = i;

                // Создаём поток
                hThreadArray[i] = CreateThread(NULL, 0, MyThreadFunction, pDataArray[i], 0, &dwThreadIdArray[i]);
                
          //      hThreadArray[i] = CreateThread( 
          //          NULL,                   // атрибуты безопасности по умолчанию
          //          0,                      // используем размер стека по умолчанию  
          //          MyThreadFunction,       // имя функции потока
          //          pDataArray[i],          // аргумент функции потока 
          //          0,                      // используем стандартные флаги 
          //          &dwThreadIdArray[i]		// получаем идентификатор потока
		        //);

#ifdef DEBUG
                // Если не удалось запустить поток, то прерываем выполнение программы
                if(hThreadArray[i] == NULL) 
                   ExitProcess(E_THREAD_CREATE);
#endif // DEBUG
  
            }

            // Ждём, пока не выполнятся все потоки
            WaitForMultipleObjects(threads, hThreadArray, TRUE, INFINITE);

            // Закрываем все дескрипторы потоков и освобождаем выделенную память
            for(i = 0; i < threads; i++)
            {
                CloseHandle(hThreadArray[i]);
                if(pDataArray[i] != NULL)
                {
                    HeapFree(GetProcessHeap(), 0, pDataArray[i]);
                    pDataArray[i] = NULL;
                }
            }
            free(A);
            free(B);

            // Записываем результат в файл
            Cstream = fopen(C_path, "w");

#ifdef DEBUG
            if (Cstream == NULL) 
            {
                print_err("Не удалось создать файл для произведения матриц");
                return 0;
            }

            for(i = 0; i < m; i++)
            {
                for(j = 0; j < q - 1; j++)
                {    
                    if(fprintf(Cstream, "%d ", C[i][j]) < 0)
                    {
                        fclose(Cstream);
                        return 0;
                    }

                }
                if(fprintf(Cstream, "%d\n", C[i][q - 1]) < 0)
                {
                    fclose(Cstream);
                    return 0;
                }
            }
#else
            for(i = 0; i < m; i++)
            {
                for(j = 0; j < q - 1; j++)  
                    fprintf(Cstream, "%d ", C[i][j]);
                fprintf(Cstream, "%d\n", C[i][q - 1]);
            }
#endif // DEBUG

            free(C);

            if(fclose(Cstream))
            {
                print_err("Не удалось закрыть поток");
                return 0;
            }

            // Записываем время в лог-файл        
            time[h] = ((double) clock() - time[h]) / CLOCKS_PER_SEC;
            print_log("Время (%d): %f сек\n", h + 1, time[h]);
        }

        // Записываем среднее время в лог-файл   
        time[0] = time[0] / TEST_COUNT;
        for(i = 1; i < TEST_COUNT; i++)
            time[0] += time[i] / TEST_COUNT;
        print_log("Среднее  : %f сек\n\n", time[0]);
    }

    return 0;
}

DWORD WINAPI MyThreadFunction(LPVOID lpData) 
{ 
    PTDATA data = (PTDATA) lpData;
    int t = data->i;

    int first = Nrows[t];
    int last  = first + Arows[t];

    int i, j, k;
    for(i = 0; i < n; i++)
    {
        for(j = first; j < last; j++)
        {
            for(k = 0; k < q; k++)
            {
                C[j][k] += A[j][i] * B[i][k]; 
            }
        }
    }

    return 0; 
} 

// Генерирует псевдо-случайное число из интервала
//   a - левая граница интервала
//   b - правая граница интервала
// Вовзращает сгенерированное число из интервала из интервала [a;b]
int gen_random(float a, float b)
{
    return (int) (a + (b - a) * ((float)rand() / RAND_MAX));
}

// Генерирует файл с матрицей mхn
//   path - путь к файлу (например, имя файла или абсолютный путь к файлу)
//   m    - число строк в матрице
//   n    - число столбцов в матрице 
// Возвращает 1, если матрица успешно создана. В случае ошибки возвращает 0 и записывает сообщение в лог.
int matrix_create(const char *path, int m, int n)
{
    if(n <= 0 || m <= 0)
    {
        print_log("[ERROR] Не удалось сгенерировать файл с матрицей: Не верно задана размерность матрицы\n");
        return 0;
    }
    
    FILE *stream = fopen(path, "w");
    if(stream == NULL) 
    {
        print_err("Не удалось создать файл для матрицы");
        return 0;
    }

    // Записываем размерность матрицы
    fprintf(stream, "%d %d\n", m, n);

    // Генерируем матрицу
    int i, j;
    for(i = 0; i < m; i++)
    {
        for(j = 0; j < n - 1; j++)
            fprintf(stream, "%d ", gen_random(-10, 10)); 
        fprintf(stream, "%d\n", gen_random(-10, 10));
    }    

    if(fclose(stream))
    {
        print_err("Не удалось закрыть поток");
        return 0;
    }

    return 1;
}

// Записывает сообщение message в лог
void print_log(const char *message, ...)
{
    va_list ptr;
    va_start(ptr, message);

    // Открываем файл логов
    FILE *stream = fopen(L_path, "a");
    if(stream == NULL) 
        return;

    // Записываем сообщение в лог
    vfprintf(stream, message, ptr);

    fclose(stream);
    va_end(ptr);
}

// Выводит сообщение по коду ошибки errno
//   message - необязательное дополнительное описание ошибки
void print_err(const char *message)
{
    char *msg = (char *)"[ERROR] ";
    if(message != NULL)
        msg = strcat(msg, message);

    switch(errno)
    {
        case E2BIG          : msg = strcat(msg, "Список аргументов слишком длинный"); break;
        case EACCES         : msg = strcat(msg, "Отказ в доступе"); break;
        case EADDRINUSE     : msg = strcat(msg, "Адрес используется"); break;
        case EADDRNOTAVAIL  : msg = strcat(msg, "Адрес недоступен"); break;
        case EAFNOSUPPORT   : msg = strcat(msg, "Семейство адресов не поддерживается"); break;
        case EALREADY       : msg = strcat(msg, "Соединение уже устанавливается"); break;
        case EBADF          : msg = strcat(msg, "Неправильный дескриптор файла"); break;
        case EBADMSG        : msg = strcat(msg, "Неправильное сообщение"); break;
        case EBUSY          : msg = strcat(msg, "Ресурс занят"); break;
        case ECANCELED      : msg = strcat(msg, "Операция отменена"); break;
        case ECHILD         : msg = strcat(msg, "Нет дочернего процесса"); break;
        case ECONNABORTED   : msg = strcat(msg, "Соединение прервано"); break;
        case EDEADLK        : msg = strcat(msg, "Обход тупика ресурсов"); break;
        case EDESTADDRREQ   : msg = strcat(msg, "Требуется адрес назначения"); break;
        case EDOM           : msg = strcat(msg, "Ошибка области определения"); break;
        case EEXIST         : msg = strcat(msg, "Файл существует"); break;
        case EFAULT         : msg = strcat(msg, "Неправильный адрес"); break;
        case EFBIG          : msg = strcat(msg, "Файл слишком велик"); break;
        case EHOSTUNREACH   : msg = strcat(msg, "Хост недоступен"); break;
        case EIDRM          : msg = strcat(msg, "Идентификатор удален"); break;
        case EILSEQ         : msg = strcat(msg, "Ошибочная последовательность байтов"); break;
        case EINPROGRESS    : msg = strcat(msg, "Операция в процессе выполнения"); break;
        case EINTR          : msg = strcat(msg, "Прерванный вызов функции"); break;
        case EINVAL         : msg = strcat(msg, "Неправильный аргумент"); break;
        case EIO            : msg = strcat(msg, "Ошибка ввода-вывода"); break;
        case EISCONN        : msg = strcat(msg, "Сокет (уже) соединен"); break;
        case EISDIR         : msg = strcat(msg, "Это каталог"); break;
        case ELOOP          : msg = strcat(msg, "Слишком много уровней символических ссылок"); break;
        case EMFILE         : msg = strcat(msg, "Слишком много открытых файлов"); break;
        case EMLINK         : msg = strcat(msg, "Слишком много связей"); break;
        case EMSGSIZE       : msg = strcat(msg, "Неопределённая длина буфера сообщения"); break;
        case ENAMETOOLONG   : msg = strcat(msg, "Имя файла слишком длинное"); break;
        case ENETDOWN       : msg = strcat(msg, "Сеть не работает"); break;
        case ENETRESET      : msg = strcat(msg, "Соединение прервано сетью"); break;
        case ENETUNREACH    : msg = strcat(msg, "Сеть недоступна"); break;
        case ENFILE         : msg = strcat(msg, "Слишком много открытых файлов в системе"); break;
        case ENOBUFS        : msg = strcat(msg, "Буферное пространство недоступно"); break;
        case ENODEV         : msg = strcat(msg, "Нет такого устройства"); break;
        case ENOENT         : msg = strcat(msg, "Нет такого файла в каталоге"); break;
        case ENOEXEC        : msg = strcat(msg, "Ошибка формата исполняемого файла"); break;
        case ENOLCK         : msg = strcat(msg, "Блокировка недоступна"); break;
        case ENOLINK        : msg = strcat(msg, "Зарезервировано"); break;
        case ENOMEM         : msg = strcat(msg, "Недостаточно памяти"); break;
        case ENOMSG         : msg = strcat(msg, "Сообщение нужного типа отсутствует"); break;
        case ENOPROTOOPT    : msg = strcat(msg, "Протокол недоступен"); break;
        case ENOSPC         : msg = strcat(msg, "Памяти на устройстве не осталось"); break;
        case ENOSYS         : msg = strcat(msg, "Функция не реализована"); break;
        case ENOTCONN       : msg = strcat(msg, "Сокет не соединен"); break;
        case ENOTDIR        : msg = strcat(msg, "Это не каталог"); break;
        case ENOTEMPTY      : msg = strcat(msg, "Каталог непустой"); break;
        case ENOTSOCK       : msg = strcat(msg, "Это не сокет"); break;
        case ENOTTY         : msg = strcat(msg, "Неопределённая операция управления вводом-выводом"); break;
        case ENXIO          : msg = strcat(msg, "Нет такого устройства или адреса"); break;
        case EOPNOTSUPP     : msg = strcat(msg, "Операция сокета не поддерживается"); break;
        case EOVERFLOW      : msg = strcat(msg, "Слишком большое значение для типа данных"); break;
        case EPERM          : msg = strcat(msg, "Операция не разрешена"); break;
        case EPIPE          : msg = strcat(msg, "Разрушенный канал"); break;
        case EPROTO         : msg = strcat(msg, "Ошибка протокола"); break;
        case EPROTONOSUPPORT: msg = strcat(msg, "Протокол не поддерживается"); break;
        case EPROTOTYPE     : msg = strcat(msg, "Ошибочный тип протокола для сокета"); break;
        case ERANGE         : msg = strcat(msg, "Результат слишком велик"); break;
        case EROFS          : msg = strcat(msg, "Файловая система только на чтение"); break;
        case ESPIPE         : msg = strcat(msg, "Неправильное позиционирование"); break;
        case ESRCH          : msg = strcat(msg, "Нет такого процесса"); break;
        case ETIMEDOUT      : msg = strcat(msg, "Операция задержана"); break;
        case ETXTBSY        : msg = strcat(msg, "Текстовый файл занят"); break;
        case EWOULDBLOCK    : msg = strcat(msg, "Блокирующая операция"); break;
        case EXDEV          : msg = strcat(msg, "Неопределённая связь"); break;
        default             : msg = strcat(msg, "Неизвестная ошибка");
    }
    print_log(msg);
}