#include "Graph.h"
#include "Stopwatch.h"
#include <algorithm>
#include <chrono>
#include <queue>
#include <random>
#include <thread>

#include <iostream>

Graph::Graph()
{
    //ctor
}

Graph::~Graph()
{
    //dtor
}

unsigned Graph::getVertexNumber()
{
    return vertexNumber;
}

void Graph::randomGenerateFullGraph(Graph &graph, unsigned maxWeight)
{
    std::random_device randomSrc;
    std::default_random_engine randomGen(randomSrc());
    std::uniform_int_distribution<> weightDist(1, maxWeight);

    for(int i = 0; i < graph.vertexNumber; i++)
    {
        for(int j = 0; j < graph.vertexNumber; j++)
        {
            if(i != j)
            {
                // Bez warunku na krawedzie juz wygenerowane...
                // ...z tym radzi sobie juz metoda addEdge
                int randomWeight = weightDist(randomGen);
                graph.addEdge(i, j, randomWeight);
            }
        }
    }
}

std::vector<unsigned> Graph::travellingSalesmanBruteForce(Graph &graph)
{
    // ALGORYTM przegladu zupelnego
    // Implementacja: Jan Potocki 2017
    // (refactoring 2019)
    std::vector<unsigned> vertexArray;

    // Generowanie "spisu" wierzcholkow
    // (od razu w odpowiedniej kolejnosci dla next_permutation)
    for(int i = 1; i < graph.vertexNumber; i++)
        vertexArray.push_back(i);

    std::vector<unsigned> minCombination;
    int minRoute = -1;

    // Petla przegladajaca kolejne permutacje
    do
    {
        std::vector<unsigned> combination;

        // Dodanie wierzcholka startowego i pierwszego na trasie
        combination.push_back(0);
        combination.push_back(vertexArray.front());

        // W petli reszta wiercholkow
        for(int i = 1; i < vertexArray.size(); i++)
            combination.push_back(vertexArray.at(i));

        // Powrot do wierzcholka startowego
        combination.push_back(0);
        // PEA 2
        // Jan Potocki 2017

        int route = 0;
        for(int i = 1; i < combination.size(); i++)
            route += graph.getWeight(combination.at(i - 1), combination.at(i));

        if(minRoute == -1 || route < minRoute)
        {
            minRoute = route;
            minCombination = combination;
        }
    }
    while(next_permutation(vertexArray.begin(), vertexArray.end()));

    return minCombination;
}

std::vector<unsigned> Graph::travellingSalesmanBranchAndBound(Graph &graph)
{
    // ALGORYTM pracujacy w oparciu o kolejke priorytetowa i niejawnie utworzone drzewo
    // Zrodlo: www.ii.uni.wroc.pl/~prz/2011lato/ah/opracowania/met_podz_ogr.opr.pdf
    // Autor: Mateusz Lyczek 2011
    // Implementacja: Jan Potocki 2017
    std::priority_queue<std::vector<unsigned>, std::vector< std::vector<unsigned> >, RouteComparison> routeQueue;
    std::vector<unsigned> optimalRoute;     // Tu bedziemy zapisywac optymalne (w danej chwili) rozwiazanie
    int optimalRouteLength = -1;            // -1 - bedziemy odtad uznawac, ze to jest nieskonczonosc ;-)

    // UMOWA
    // Pierwszy element wektora to dlugosc trasy (trzeba ustawic "z palca"!)
    // Kolejne to wierzcholki na trasie
    std::vector<unsigned> currentRoute;     // Niejawne tworzenie drzewa, tu bedzie korzen
    currentRoute.push_back(0);              // Poczatkowe oszacowanie nie ma znaczenia
    currentRoute.push_back(0);              // Wierzcholek startowy (korzen drzewa rozwiazan)
    routeQueue.push(currentRoute);          // Dodanie do kolejki korzenia

    while(!routeQueue.empty())
    {
        // Przypisanie korzenia do dalszej roboty
        currentRoute = routeQueue.top();
        routeQueue.pop();

        // Sprawdzenie, czy rozwiazanie jest warte rozwijania, czy odrzucic
        if(optimalRouteLength == -1 || currentRoute.at(0) < optimalRouteLength)
        {
            for(int i = 0; i < graph.vertexNumber; i++)
            {
                // Petla wykonywana dla kazdego potomka rozpatrywanego wlasnie rozwiazania w drzewie
                // Ustalenie, czy dany wierzcholek mozna jeszcze wykorzystac, czy juz zostal uzyty
                bool vertexUsed = false;
                for(int j = 1; j < currentRoute.size(); j++)
                {
                    if(currentRoute.at(j) == i)
                    {
                        vertexUsed = true;
                        break;
                    }
                }
                if(vertexUsed)
                    continue;

                // Niejawne utworzenie nowego wezla reprezuntujacego rozpatrywane rozwiazanie...
                std::vector<unsigned> nextRoute = currentRoute;
                //unsigned nextLength = graph.getWeight(nextRoute.back(), i);
                nextRoute.push_back(i);

                // Dalej bedziemy postepowac roznie...
                if(nextRoute.size() > graph.vertexNumber)
                {
                    // Doszlismy wlasnie do liscia
                    // Dodajemy droge powrotna, nie musimy nic szacowac
                    // (wszystko juz wiemy)
                    nextRoute.push_back(0);

                    nextRoute.at(0) = 0;

                    for(int j = 1; j < nextRoute.size() - 1; j++)
                    {
                        // Liczymy dystans od poczatku do konca
                        nextRoute.at(0) += graph.getWeight(nextRoute.at(j), nextRoute.at(j+ 1));
                    }
                    if(optimalRouteLength == -1 || nextRoute.at(0) < optimalRouteLength)
                    {
                        optimalRouteLength = nextRoute.at(0);
                        nextRoute.erase(nextRoute.begin());
                        optimalRoute = nextRoute;
                    }
                }
                else
                {
                    // Liczenie tego, co juz wiemy, od nowa...
                    // (dystans od poczatku)
                    nextRoute.at(0) = 0;
                    for(int j = 1; j < nextRoute.size() - 1; j++)
                    {
                        nextRoute.at(0) += graph.getWeight(nextRoute.at(j), nextRoute.at(j + 1));
                    }

                    // Reszte szacujemy...
                    // Pomijamy od razu wierzcholek startowy
                    for(int j = 1; j < graph.vertexNumber; j++)
                    {
                        // Odrzucenie wierzcholkow juz umieszczonych na trasie
                        bool vertexUsed = false;
                        for(int k = 1; k < currentRoute.size(); k++)
                        {
                            if(j == currentRoute.at(k))
                            {
                                vertexUsed = true;
                                break;
                            }
                        }
                        if(vertexUsed)
                            continue;

                        int minEdge = -1;
                        for(int k = 0; k < graph.vertexNumber; k++)
                        {
                            // Odrzucenie krawedzi do wierzcholka 0 przy ostatnim wierzcholku w czesciowym rozwiazaniu
                            // Wyjatkiem jest ostatnia mozliwa krawedz
                            if(j == i && k == 0)
                                continue;

                            // Odrzucenie krawedzi do wierzcholka umieszczonego juz na rozwazanej trasie
                            bool vertexUsed = false;
                            for(int l = 2; l < nextRoute.size(); l++)
                            {
                                if(k == nextRoute.at(l))
                                {
                                    vertexUsed = true;
                                    break;
                                }
                            }
                            if(vertexUsed)
                                continue;

                            // Odrzucenie samego siebie
                            if(k == j)
                                continue;

                            // Znalezienie najkrotszej mozliwej jeszcze do uzycia krawedzi
                            unsigned consideredLength = graph.getWeight(j, k);

                            if(minEdge == -1)
                                minEdge = consideredLength;
                            else if(minEdge > consideredLength)
                                minEdge = consideredLength;
                        }
                        nextRoute.at(0) += minEdge;
                    }

                    // ...i teraz zastanawiamy sie co dalej
                    if(optimalRouteLength == -1 || nextRoute.at(0) < optimalRouteLength)
                    {
                        routeQueue.push(nextRoute);
                    }
                }
            }
        }
        else
        {
            // Jezeli jedno rozwiazanie odrzucilismy, to wszystkie inne tez mozemy
            // (kolejka priorytetowa, inne nie moga byc lepsze)
            break;
        }
    }

    return optimalRoute;
}

std::vector<unsigned> Graph::travellingSalesmanGreedy(Graph &graph, unsigned startVertex)
{
    // ALGORYTM zachlanny z wierzcholkiem startowym przekazanym w parametrze
    // Implementacja: Jan Potocki 2017
    std::vector<unsigned> route;

    // Przypisanie wierzcholka startowego
    route.push_back(startVertex);

    for(int i = 0; i < graph.vertexNumber - 1; i++)
    {
        int minEdge = -1;
        unsigned nextVertex;
        for(int j = 0; j < graph.vertexNumber; j++)
        {
            // Odrzucenie samego siebie lub wierzcholka startowego
            // (zeby bylo szybciej)
            if(route.back() == j || route.front() == j)
                continue;

            // Odrzucenie krawedzi do wierzcholka umieszczonego juz na trasie
            bool vertexUsed = false;
            for(int k = 0; k < route.size(); k++)
            {
                if(j == route.at(k))
                {
                    vertexUsed = true;
                    break;
                }
            }
            if(vertexUsed)
                continue;

            // Znalezienie najkrotszej mozliwej jeszcze do uzycia krawedzi
            unsigned consideredLength = graph.getWeight(route.back(), j);

            if(minEdge == -1)
            {
                minEdge = consideredLength;
                nextVertex = j;
            }
            else if(minEdge > consideredLength)
            {
                minEdge = consideredLength;
                nextVertex = j;
            }
        }
        route.push_back(nextVertex);
    }

    route.push_back(startVertex);
    return route;
}

std::vector<unsigned> Graph::travellingSalesmanHybrid(Graph &graph)
{
    // ALGORYTM hybrydowy losowo-zachlanny
    // Losowa czesc wierzcholkow jest losowana, reszta zachlannie
    // Implementacja: Jan Potocki 2019
    std::vector<unsigned> route;

    std::random_device randomSrc;
    std::default_random_engine randomGen(randomSrc());
    std::uniform_int_distribution<> vertexNumberDist(1, graph.vertexNumber);
    std::uniform_int_distribution<> vertexDist(0, graph.vertexNumber - 1);

    // Liczba losowanych wierzcholkow
    unsigned randomVertexNumber = vertexNumberDist(randomGen);

    // Czesc losowa
    for(int i = 0; i < randomVertexNumber; i++)
    {
        unsigned randomVertex;
        bool vertexUsed;

        do
        {
            randomVertex = vertexDist(randomGen);
            vertexUsed = false;

            for(int j = 0; j < route.size(); j++)
            {
                if(route.at(j) == randomVertex)
                {
                    vertexUsed = true;
                    break;
                }
            }
        } while(vertexUsed == true);

        route.push_back(randomVertex);
    }

    // Czesc zachlanna
    for(int i = 0; i < graph.vertexNumber - randomVertexNumber; i++)
    {
        int minEdge = -1;
        unsigned nextVertex;
        for(int j = 0; j < graph.vertexNumber; j++)
        {
            // Odrzucenie samego siebie lub wierzcholka startowego
            // (zeby bylo szybciej)
            if(route.back() == j || route.front() == j)
                continue;

            // Odrzucenie krawedzi do wierzcholka umieszczonego juz na trasie
            bool vertexUsed = false;
            for(int k = 0; k < route.size(); k++)
            {
                if(j == route.at(k))
                {
                    vertexUsed = true;
                    break;
                }
            }
            if(vertexUsed)
                continue;

            // Znalezienie najkrotszej mozliwej jeszcze do uzycia krawedzi
            unsigned consideredLength = graph.getWeight(route.back(), j);

            // PEA 2 Plus
            // Jan Potocki 2019
            if(minEdge == -1)
            {
                minEdge = consideredLength;
                nextVertex = j;
            }
            else if(minEdge > consideredLength)
            {
                minEdge = consideredLength;
                nextVertex = j;
            }
        }
        route.push_back(nextVertex);
    }

    route.push_back(route.front());
    return route;
}

std::vector<unsigned> Graph::travellingSalesmanRandom(Graph &graph)
{
    // ALGORYTM losowy
    // Implementacja: Jan Potocki 2019
    std::vector<unsigned> route;

    std::random_device randomSrc;
    std::default_random_engine randomGen(randomSrc());
    std::uniform_int_distribution<> vertexDist(0, graph.vertexNumber - 1);

    for(int i = 0; i < graph.vertexNumber; i++)
    {
        unsigned randomVertex;
        bool vertexUsed;

        do
        {
            randomVertex = vertexDist(randomGen);
            vertexUsed = false;

            for(int j = 0; j < route.size(); j++)
            {
                if(route.at(j) == randomVertex)
                {
                    vertexUsed = true;
                    break;
                }
            }
        } while(vertexUsed == true);

        route.push_back(randomVertex);
    }

    route.push_back(route.front());
    return route;
}

std::vector<unsigned> Graph::travellingSalesmanTabuSearch(Graph &graph, unsigned tabuSteps, bool diversification, int iterationsToRestart, unsigned minStopTime, unsigned threadsNumber)
{
    // ALGORYTM wielawotkowy oparty na metaheurystyce tabu search
    // Pomocniczy kod uruchamiajacy watki wlasciwego algorytmu w najbardziej optymalny sposob
    // Implementacja: Jan Potocki 2019
    std::vector<unsigned> startVertexVector;
    std::vector<std::thread> threadsVector;
    std::vector<std::vector<unsigned>> resultsVector(threadsNumber);

    std::vector<int> resultsLength(threadsNumber);
    std::vector<unsigned> optimalResult;
    int optimalResultIndex;
    int optimalResultLength;

    std::random_device randomSrc;
    std::default_random_engine randomGen(randomSrc());
    std::uniform_int_distribution<> vertexDist(0, graph.vertexNumber - 1);

    // Petla uruchamiajaca watki
    for(int i = 0; i < threadsNumber; i++)
    {
        // Generowanie startowego rozwiazania...
        std::vector<unsigned> startRoute;
        unsigned startVertex;
        bool startVertexUsed;

        if(i < graph.vertexNumber)
        {
            // ...dopoki ma to sens - algorytmem zachlannym z innym wierzcholkiem startowym
            // (dla kazdego watku)
            do
            {
                startVertex = vertexDist(randomGen);
                startVertexUsed = false;

                for(int j = 0; j < startVertexVector.size(); j++)
                {
                    if(startVertexVector.at(j) == startVertex)
                    {
                        startVertexUsed = true;
                        break;
                    }
                }
            } while(startVertexUsed == true);

            // PEA 2 Plus
            // Jan Potocki 2019
            startVertexVector.push_back(startVertex);
            startRoute = Graph::travellingSalesmanGreedy(graph, startVertex);
        }
        else
        {
            // ...jezeli wszystkie wierzcholki sa juz wykorzystane - w pelni losowo
            startRoute = Graph::travellingSalesmanRandom(graph);
        }

        // Uruchomienie watku
        threadsVector.push_back(std::thread(Graph::travellingSalesmanTabuSearchEngine, std::ref(graph), tabuSteps, diversification, iterationsToRestart, minStopTime, startRoute, std::ref(resultsVector.at(i)), std::ref(resultsLength.at(i))));
    }

    // Petla potwierdzajaca zakonczenie watkow
    for(int i = 0; i < threadsNumber; i++)
        threadsVector.at(i).join();

    // Przegladanie wszystkich rozwiazan i wybor optymalnego
    optimalResultIndex = 0;
    optimalResultLength = resultsLength.at(0);

    for(int i = 0; i < threadsNumber; i++)
    {
        if(resultsLength.at(i) < optimalResultLength)
        {
            optimalResultIndex = i;
            optimalResultLength = resultsLength.at(i);
        }
    }

    optimalResult = resultsVector.at(optimalResultIndex);

    return optimalResult;
}

void Graph::travellingSalesmanTabuSearchEngine(Graph &graph, unsigned tabuSteps, bool diversification, int iterationsToRestart, unsigned minStopTime, std::vector<unsigned> startRoute, std::vector<unsigned> &result, int &resultLength)
{
    // ALGORYTM oparty na metaheurystyce tabu search z dywersyfikacja i sasiedztwem typu swap
    // Rdzen przeznaczony do uruchamiania jako jeden watek
    // Projekt i implementacja: Jan Potocki 2017
    // (refactoring 2019)
    Stopwatch onboardClock;

    std::vector<unsigned> optimalRoute;     // Tu bedziemy zapisywac optymalne (w danej chwili) rozwiazanie
    int optimalRouteLength = -1;            // -1 - bedziemy odtad uznawac, ze to jest nieskonczonosc ;-)
    std::vector<unsigned> currentRoute;     // Rozpatrywane rozwiazanie

    // Wyznaczenie poczatkowego rozwiazania algorytmem zachlannym
    //currentRoute = Graph::travellingSalesmanGreedy(graph);
    currentRoute = startRoute;

    // Inicjalizacja glownej petli...
    std::vector< std::vector<unsigned> > tabuArray;
    unsigned currentTabuSteps = tabuSteps;
    int stopCounter = 0;
    bool timeNotExceeded = true;
    onboardClock.start();

    // Rdzen algorytmu
    while(timeNotExceeded == true)
    {
        bool cheeseSupplied = true;
        bool intensification = false;

        while(cheeseSupplied == true)
        {
            std::vector<unsigned> nextRoute;
            int nextRouteLength = -1;

            std::vector<unsigned> nextTabu(3, 0);
            nextTabu.at(0) = currentTabuSteps;

            // Generowanie sasiedztwa typu swap przez zamiane wierzcholkow
            // (wierzcholka startowego i zarazem ostatniego nie ruszamy,
            // pomijamy tez od razu aktualny wierzcholek)
            for(int i = 1; i < graph.vertexNumber - 1; i++)
            {
                for(int j = i + 1; j < graph.vertexNumber; j++)
                {
                    std::vector<unsigned> neighbourRoute = currentRoute;

                    // Zamiana
                    unsigned buffer = neighbourRoute.at(j);
                    neighbourRoute.at(j) = neighbourRoute.at(i);
                    neighbourRoute.at(i) = buffer;

                    unsigned neighbourRouteLength = 0;
                    for(int i = 1; i < neighbourRoute.size(); i++)
                        neighbourRouteLength += graph.getWeight(neighbourRoute.at(i - 1), neighbourRoute.at(i));

                    // Sprawdzenie, czy dany ruch nie jest na liscie tabu
                    // (dwa wierzcholki)
                    bool tabu = false;
                    for(int k = 0; k < tabuArray.size(); k++)
                    {
                        if(tabuArray.at(k).at(1) == i && tabuArray.at(k).at(2) == j)
                        {
                            tabu = true;
                            break;
                        }

                        if(tabuArray.at(k).at(1) == j && tabuArray.at(k).at(2) == i)
                        {
                            tabu = true;
                            break;
                        }
                    }

                    // Kryterium aspiracji...
                    if(tabu == true && neighbourRouteLength >= optimalRouteLength)
                        // ...jezeli niespelnione - pomijamy ruch
                        continue;

                    if(nextRouteLength == -1)
                    {
                        nextRouteLength = neighbourRouteLength;
                        nextRoute = neighbourRoute;
                        nextTabu.at(1) = i;
                        nextTabu.at(2) = j;
                    }
                    else if(nextRouteLength > neighbourRouteLength)
                    {
                        nextRouteLength = neighbourRouteLength;
                        nextRoute = neighbourRoute;
                        nextTabu.at(1) = i;
                        nextTabu.at(2) = j;
                    }
                }
            }

            currentRoute = nextRoute;
            // PEA 2 Plus
            // Jan Potocki 2019

            if(optimalRouteLength == -1)
            {
                optimalRouteLength = nextRouteLength;
                optimalRoute = nextRoute;

                // Reset licznika
                stopCounter = 0;
            }
            else if(optimalRouteLength > nextRouteLength)
            {
                optimalRouteLength = nextRouteLength;
                optimalRoute = nextRoute;

                // Zaplanowanie intensyfikacji przy znalezieniu nowego optimum
                intensification = true;

                // Reset licznika
                stopCounter = 0;
            }

            // Weryfikacja listy tabu...
            // ...aktualizacja kadencji na liscie tabu
            for(int i = 0; i < tabuArray.size(); i++)
            {
                tabuArray.at(i).at(0)--;
            }

            //...usuniecie zerowych kadencji
            for(int i = 0; i < tabuArray.size(); i++)
            {
                if(tabuArray.at(i).at(0) == 0)
                    tabuArray.erase(tabuArray.begin() + i);
            }

            // ...dopisanie ostatniego ruchu do listy tabu
            tabuArray.push_back(nextTabu);

            // Zliczenie iteracji
            stopCounter++;

            // Zmierzenie czasu
            onboardClock.stop();
            if(onboardClock.read() > minStopTime)
                timeNotExceeded = false;

            // Sprawdzenie warunku zatrzymania
            if(diversification == true)
            {
                // Przy aktywowanej dywersyfikacji - po zadanej liczbie iteracji bez poprawy
                if(stopCounter >= iterationsToRestart || timeNotExceeded == false)
                    cheeseSupplied = false;
            }
            else
            {
                // Przy nieaktywowanej dywersyfikacji - po uplynieciu okreslonego czasu
                if(timeNotExceeded == false)
                    cheeseSupplied = false;
            }
        }

        // Dywersyfikacja
        if(diversification == true)
        {
            if(intensification == true)
            {
                // Intensyfikacja przeszukiwania przez skrócenie kadencji
                // (jezeli w ostatnim przebiegu znaleziono nowe minimum)
                currentRoute = optimalRoute;
                currentTabuSteps = tabuSteps / 4;
                intensification = false;
                // PEA 2 Plus
                // Jan Potocki 2019
            }
            else
            {
                // W innym przypadku wlasciwa dywersyfikacja przez wygenerowanie nowego
                // rozwiazania startowego algorytmem hybrydowym losowo-zachlannym
                currentRoute = Graph::travellingSalesmanHybrid(graph);
                currentTabuSteps = tabuSteps;
                intensification = false;
            }
        }

        // Reset licznika iteracji przed restartem
        stopCounter = 0;
    }

    result = optimalRoute;
    resultLength = optimalRouteLength;
}