// Отключение предупреждений безопасности
#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_WEIGHT 1
#define MAX_WEIGHT 9

// Связность графа
#define DENSITY 0.80

// Число вершин графа
#define V_SIZE 5000

// Тип числа вершин в графе
typedef int vsize;

// Тип весов рёбер графа
typedef int weight;

// Путь к файлу логов
#define LOG_PATH "lab.log"

// Определение бесконечного веса ребра
#define INF 10000


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

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


weight get_random(weight a, weight b, float density, weight inf);
vsize  gen_random(vsize a, vsize b);
void print_log(const char *message, ...);
void print_err(const char *message);

DWORD WINAPI MyThreadFunction(LPVOID lpParam);


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

weight **M;
vsize  **P;

// Число вершин графа G=(V,E), |V|=n
vsize n = V_SIZE;

// Пути к файлам матрицы смежности, запросов и результата
const char *M_path = "M.txt";      FILE *Mstream;
const char *Q_path = "query.txt";  FILE *Qstream;
const char *R_path = "result.txt"; FILE *Rstream;
const char *L_path = "lab.log";    FILE *Lstream;

// Числа строк матрицы M, обрабатываемые потоками
vsize Wrows[MAX_THREADS];

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

vsize k;

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

	// Номера начальной и конечной вершин искомого пути
	vsize Vstart, Vend;

    // Время выполнения каждого из запусков программы
    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();

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

                if(n <= 0)
                {
                    print_log("[ERROR] Не удалось сгенерировать файл с матрицей смежности: Не верно задана размерность матрицы");
                    return 0;
                }

                Mstream = fopen(M_path, "w");
                Qstream = fopen(Q_path, "w");

                if(Mstream == NULL || Qstream == NULL) 
                {
                    print_err("Не удалось сгенерировать файлы");
                    return 0;
                }

                // Записываем число вершин графа
                if(fprintf(Mstream, "%d\n", n) < 0)
                {
                    print_err("Не удалось сгенерировать файлы");
                    fclose(Mstream);
                    fclose(Qstream);
                    return 0;
                }

                vsize Msize = n * n;
                weight **M = (weight**) malloc(n * sizeof(weight*));
                if(M == NULL)
                {
                    print_err("Не удалось выделить память");
                    fclose(Mstream);
                    fclose(Qstream);
                    return 0;
                }
                for(i = 0; i < n; i++)
                    M[i] = (weight*) malloc(n * sizeof(weight));
                for(i = 0; i < n; i++)
                {
                    for(j = 0; j < n; j++)
                    {
                        if(j < i)
                            M[i][j] = M[j][i];
                        else if(j == i)
                            M[i][j] = 0;
                        else
                            M[i][j] = get_random(MIN_WEIGHT, MAX_WEIGHT, (float) DENSITY, INF);
                    }
                }

                for(i = 0; i < n; i++)
                {
                    for(j = 0; j < n - 1; j++)
                    {
                        if(fprintf(Mstream, "%d ", M[i][j]) < 0)
                        {
                            print_err("Не удалось сгенерировать файлы");
                            fclose(Mstream);
                            fclose(Qstream);
                            free(M);
                            return 0;
                        }
                    }
                    if(fprintf(Mstream, "%d\n", M[i][n - 1]) < 0)
                    {
                        print_err("Не удалось сгенерировать файлы");
                        fclose(Mstream);
                        fclose(Qstream);
                        free(M);
                        return 0;
                    }
                }
                free(M);

                vsize r1, r2;
                do
                {
                    r1 = gen_random(1, n);
                    r2 = gen_random(1, n);
                } while(n > 1 && r1 == r2);

                if(fprintf(Qstream, "%d -> %d", r1, r2) < 0)
                {
                    print_err("Не удалось сгенерировать файлы");
                    fclose(Mstream);
                    fclose(Qstream);
                    return 0;
                }

                if(fclose(Mstream) || fclose(Qstream))
                {
                    print_err("Не удалось закрыть файлы");
                    return 0;
                }

                // Не учитываем время, затраченное на генерацию файлов
                time[h] = (double) clock();

                Mstream = fopen(M_path, "r");
                Qstream = fopen(Q_path, "r");

#ifdef DEBUG
                if(Mstream == NULL || Qstream == NULL)
                {
                    print_err("Не удалось открыть файлы");
                    return 0;
                }
#endif // DEBUG
            }

#ifdef DEBUG
            // Считываем число вершин в графе
            if(fscanf(Mstream, "%d\n", &n) <= 0)
            {
                print_log("[ERROR] Не удалось считать число вершин графа");
                fclose(Mstream);
                fclose(Qstream);
                return 0;
            }

            // Проверка числа вершин
            if(n <= 0)
            {
                print_log("[ERROR] Не верно задано число вершин графа: |V| = n должно быть больше нуля");
                fclose(Mstream);
                fclose(Qstream);
                return 0;
            }

            // Чтение начальной и конечной вершин искомого пути
            if(fscanf(Qstream, "%d -> %d", &Vstart, &Vend) <= 0)
            {
                print_log("[ERROR] Не удалось считать число вершин графа");
                fclose(Mstream);
                fclose(Qstream);
                return 0;
            }
            Vstart--; Vend--;

            // Проверка начальной и конечной вершин искомого пути
            if(Vstart < 0 || Vend < 0 || Vstart >= n || Vend >= n)
            {
                print_log("[ERROR] Не верно заданы начальная и конечная вершины искомого пути");
                fclose(Mstream);
                fclose(Qstream);
                return 0;
            }
#else
            fscanf(Mstream, "%d\n", &n);
            fscanf(Qstream, "%d -> %d", &Vstart, &Vend);
            Vstart--; Vend--;
#endif // DEBUG
            fclose(Qstream);

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

            // Запись информации о группе тестов в лог-файл
            if(!h)
            {
                print_log("----------------------------------------------\nЧисло потоков  %d\nЧисло вершин       %d\nПамять             %f МБ\n\n", threads, n, double((n * n * sizeof(weight) + n * n * sizeof(vsize))  / 1024.0 / 1024.0));
#ifdef DEBUG
                print_log("[DEBUG] Распределение строк матрицы M между потоками:\n{");
                for(i = 0; i < threads - 1; i++)
                    print_log("%d (%d), ", Wrows[i], Nrows[i]);
                print_log("%d (%d)}\n\n", Wrows[threads - 1], Nrows[threads - 1]);
#endif // DEBUG
            }
            vsize Msize = n * n;
            vsize Psize = n * n;
        
            // Матрица смежности
            M = (weight**) malloc(n * sizeof(weight*));

            // Матрица предшествования
            P = (vsize**) malloc(n * sizeof(vsize*));
        
#ifdef DEBUG
            if(M == NULL || P == NULL)
            {
                print_err("Не удалось выделить память");
                fclose(Mstream);
                return 0;
            }
#endif // DEBUG

            for(i = 0; i < n; i++)
            {
                M[i] = (weight*) malloc(n * sizeof(weight));
                P[i] = (vsize*) malloc(n * sizeof(vsize));
            }

            // Чтение матрицы смежности
            for(i = 0; i < n; i++)
                for(j = 0; j < n; j++)
                    fscanf(Mstream, "%d", &M[i][j]);
            fclose(Mstream);

            // Инициализация матрицы предшествования
            for(i = 0; i < n; i++)
            {
                for(j = 0; j < n; j++)
                    P[i][j] = (M[i][j] == INF) ? -1 : i;
            }

            // Выделяем память для потока
            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(M); free(P);
                        ExitProcess(E_THREAD_ALLOC);
                    }
                #endif // DEBUG

                pDataArray[i]->i = i;
            }

            for(k = 0; k < n; k++)
            {
                for(i = 0; i < threads; 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]);
            }

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

#ifdef DEBUG
            if(threads == MIN_THREADS && !h)
            {
                print_log("[DEBUG] Матрица весов кратчайших путей:\n");
                for(i = 0; i < n; i++)
                {
                    for(j = 0; j < n - 1; j++)
                    {
                        if(M[i][j] >= INF)
                            print_log("- ");
                        else
                            print_log("%d ", M[i][j]);
                    }
                    if(M[i][n - 1] >= INF)
                        print_log("-\n");
                    else
                        print_log("%d\n", M[i][n - 1]);
                }
                print_log("\n[DEBUG] Матрица предшествования:\n");
                for(i = 0; i < n; i++)
                {
                    for(j = 0; j < n - 1; j++)
                    {
                        if(P[i][j] == -1)
                            print_log("- ");
                        else
                            print_log("%d ", P[i][j] + 1);
                    }
                    if(P[i][n - 1] == -1)
                        print_log("-\n");
                    else
                        print_log("%d\n", P[i][n - 1] + 1);
                }
                print_log("\n");
            }
#endif // DEBUG
        
            // Вершины искомого пути (в обратном порядке)
            vsize *path = (vsize*) malloc(n * sizeof(vsize));

            i = 0;
            path[i] = Vend;
            do
            {
                i++;
                if(i < n)
                    path[i] = P[Vstart][path[i-1]];
            } while(i < n && path[i] != Vstart && path[i] != -1);

            // Запись результата в файл
            Rstream = fopen(R_path, "w");
            if(M[Vstart][Vend] >= INF || path[i] == -1)
            {
                fprintf(Rstream, "Путь из вершины %d в вершину %d не существует\n", Vstart + 1, Vend + 1);
            }
            else
            {
                fprintf(Rstream, "Искомый путь (длина %d):\n", M[Vstart][Vend]);
                while(i >= 0)
                {
                    if(i)
                        fprintf(Rstream, "%d => ", path[i] + 1);
                    else
                        fprintf(Rstream, "%d", path[i] + 1);
                    i--;
                }
            }
            fclose(Rstream);
        
            // Освобождаем память
            free(M);
            free(P);
            free(path);

            // Записываем время в лог-файл        
            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;

    vsize first = Nrows[t];
    vsize last  = first + Wrows[t];

    vsize i, j;
    for(i = first; i < last; i++)
    {
        for(j = 0; j < n; j++)
        {
            if(M[i][j] > (M[i][k] + M[k][j]))
            {
                P[i][j] = P[k][j];
                M[i][j] = M[i][k] + M[k][j];
            } 
        }
    }

    return 0; 
} 

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

// Генерирует псевдо-случайное число из интервала [a;b], возвращая inf с вероятностью (1 - p)
weight get_random(weight a, weight b, float p, weight inf)
{
    return (weight) ( (((double) rand() / RAND_MAX) >= p) ? inf : (a + (b - a) * ((double) rand() / RAND_MAX)) );
}

// Записывает сообщение 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);
}