#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include <sstream>
#include "tinyxml2.h"
#include <fstream>

#define INF 9999999

using namespace std;
using namespace tinyxml2;

#ifndef XMLCheckResult
	#define XMLCheckResult(a_eResult) if (a_eResult != XML_SUCCESS) { printf("Error: %i\n", a_eResult); return a_eResult; }
#endif

//cesta ke zdrojovemu SVG souboru
char filepath[100] = "source/graph80.svg";
//s kolika cestami soucasne se bude pracovat
const int PATHS = 5000;
//kolik iteraci algoritmus probehne
const int ITERS = 100000;
//maximalni koeficient krizeni
const float CROSSOVER_MAX = 0.4f;
//porovnavaci rozsah pro ukonceni behu algoritmu
const int condition = 500;
//pomer zahazovanych cest
const int GENERATION = PATHS*0.4;
const int BEST_GENERATION = PATHS - GENERATION;

vector<string> &split(const string &s, char delim, vector<string> &elems) {
    stringstream ss(s);
    string item;
    while (getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}

vector<string> split(const string &s, char delim) {
    vector<string> elems;
    split(s, delim, elems);
    return elems;
}

typedef struct {
	// jednoducha struktura reprezentujici cestu prez vsechna mesta
	vector<int> nodes; // vektor s mesty, kterymi postupne prochazi cesta, napr.: 1 4 2 0 3
	int length; // delka cesty
} Path;

int nodeCount() {
    XMLDocument xmlDoc;
    XMLError eResult = xmlDoc.LoadFile(filepath);
    XMLCheckResult(eResult);

    XMLElement * pRoot = xmlDoc.FirstChildElement("svg");
    if (pRoot == nullptr) return XML_ERROR_FILE_READ_ERROR;

    XMLElement * pElementG = pRoot->FirstChildElement("g");
    if (pElementG == nullptr) return XML_ERROR_PARSING_ELEMENT;

    XMLElement * pElementPath = pElementG->FirstChildElement("ellipse");
    if (pElementPath == nullptr) return XML_ERROR_PARSING_ELEMENT;

    int counted = 0;

    while (pElementPath != nullptr)
    {
        counted++;

        pElementPath = pElementPath->NextSiblingElement("ellipse");     //podmínka
    }

	return counted;
}

int load_XML(vector<vector<int> > &a) {
    XMLDocument xmlDoc;
    XMLError eResult = xmlDoc.LoadFile(filepath);
    XMLCheckResult(eResult);

    XMLElement * pRoot = xmlDoc.FirstChildElement("svg");
    if (pRoot == nullptr) return XML_ERROR_FILE_READ_ERROR;

    XMLElement * pElementG = pRoot->FirstChildElement("g");
    if (pElementG == nullptr) return XML_ERROR_PARSING_ELEMENT;

    XMLElement * pElementPath = pElementG->FirstChildElement("path");
    if (pElementPath == nullptr) return XML_ERROR_PARSING_ELEMENT;

    const char * szAttributeText = nullptr;

    while (pElementPath != nullptr)
    {
        szAttributeText = pElementPath->Attribute("inkscape:connection-start");
        if (szAttributeText == nullptr) return XML_ERROR_PARSING_ATTRIBUTE;
        string str = szAttributeText;

        int strStart = atoi(str.substr(1).c_str());

        szAttributeText = pElementPath->Attribute("inkscape:connection-end");
        if (szAttributeText == nullptr) return XML_ERROR_PARSING_ATTRIBUTE;
        str = szAttributeText;

        int strEnd = atoi(str.substr(1).c_str());

        szAttributeText = pElementPath->Attribute("dist");
        if (szAttributeText == nullptr) return XML_ERROR_PARSING_ATTRIBUTE;
        string strDist = szAttributeText;

        vector<string> x = split(strDist, ' ');

        a[strStart][strEnd] = atoi(x[0].c_str());
        a[strEnd][strStart] = atoi(x[1].c_str());

        pElementPath = pElementPath->NextSiblingElement("path");     //podmínka
    }

	return XML_SUCCESS;
}

void floyd(vector<vector<int> > &a, int numOfNodes) {
    // inicializace nul na diagonále
    for(int i=0; i < numOfNodes; i++)
        a[i][i]=0;

    //vytiskne vstupni matici
    /*for(int i = 0; i < numOfNodes; i++){
        for(int j = 0; j < numOfNodes; j++)
            cout << a[i][j] << " ";
        cout << endl;
    }
    cout << endl;*/

    // Floyd-Warshall
    for(int k = 0; k < numOfNodes; k++)
        for(int i = 0; i < numOfNodes; i++)
            for(int j = 0; j < numOfNodes; j++)
                if(a[i][j]>a[i][k]+a[k][j])
                    a[i][j]=a[i][k]+a[k][j];

    //vytiskne finalni matici
    /*for(int i = 0; i < numOfNodes; i++){
        for(int j = 0; j < numOfNodes; j++)
            cout << a[i][j] << " ";
        cout << endl;
    }*/
}

void initPath(int numOfNodes, Path* p) {
	for (int i = 0; i < numOfNodes; ++i)
		p->nodes.push_back(i); // vytvori cestu, ktera jde pres uzly vzestupne, tj. pro numOfNodes = 5 to bude: 0 1 2 3 4
}

Path mutation(int numOfNodes, int **distances, Path* p) {
	//nalezeni dvou nahodnych uzlu
	int random1 = (rand() % (numOfNodes - 1)) + 1;
	int random2 = (rand() % (numOfNodes - 1)) + 1;

    //prohozeni uzlu na jejich indexech
	int tmp = p->nodes[random1];
	p->nodes[random1] = p->nodes[random2];
	p->nodes[random2] = tmp;

    //vypocet delky trasy po mutaci
    p->length = 0;
	for (int i = 0; i < numOfNodes - 1; i++)
		p->length += distances[p->nodes[i]][p->nodes[i + 1]];
    p->length += distances[p->nodes[numOfNodes-1]][p->nodes[0]];

	return *p;
}

Path crossover(int numOfNodes, int **distances, Path* p1, Path* p2) {

    Path pResult;

    //vygeneruje na jakem indexu dojde k preskoku
    int index = 1 + (rand() % (numOfNodes - 3));

    //okopiruje p1 po vygenerovany index
    for(int i = 0; i <= index; ++i){
        pResult.nodes.push_back(p1->nodes[i]);
    }

    vector<int>::iterator newIndex = find(p2->nodes.begin(), p2->nodes.end(), p1->nodes[index]);

    //okopiruje do vysledku uzly z p2, ktere zacinaji na newIndex a nebyla v p1
    for( vector<int>::iterator i = newIndex; i < p2->nodes.end(); i++) {
        if(find(pResult.nodes.begin(), pResult.nodes.end(), *i) == pResult.nodes.end())
            pResult.nodes.push_back(*i);
    }

    //okopiruje do vysledku uzly z p2, ktera jeste nejsou ve vysledne trase
    for(vector<int>::iterator i = p2->nodes.begin(); i < newIndex; i++) {
        if(find(pResult.nodes.begin(), pResult.nodes.end(), *i) == pResult.nodes.end())
            pResult.nodes.push_back(*i);
    }

    //vypocet delky trasy po krizeni
    pResult.length = 0;
    for (int i = 0; i < numOfNodes - 1; i++)
        pResult.length += distances[pResult.nodes[i]][pResult.nodes[i + 1]];
    pResult.length += distances[pResult.nodes[numOfNodes-1]][pResult.nodes[0]];

    return pResult;

}

Path shufflePath(int numOfNodes, int **distances, Path* p) {
    // nahodne zamicha mesta, tedy uz to nebude 0 1 2 3 4, ale napr 0 2 4 1 3
	random_shuffle(p->nodes.begin() + 1, p->nodes.end());

	// vypocet delky cesty pomoci matice vzdalenosti
	p->length = 0;
	for (int i = 0; i < numOfNodes - 1; i++)
		p->length += distances[p->nodes[i]][p->nodes[i + 1]];
    p->length += distances[p->nodes[numOfNodes-1]][p->nodes[0]];

	return *p;
}

struct cmpPath {
    // funkce ktera dokaze porovnat dve instance tridy Path, pouziva se v std::sort()
	inline bool operator() (const Path& a, const Path& b)
	{
		return (a.length < b.length);
	}
};

void printPath(int numOfNodes, vector<Path> paths) {
	for (int i = 0; i < numOfNodes; ++i)
		cout << paths[0].nodes[i] << (i != numOfNodes - 1 ? " => " : "");
    cout << " => 0";
	cout << endl;
}

void printDebug(int numOfNodes, vector<Path> paths) {
    //vypis X cest a jejich delek
   for (int j = 0; j < 10; ++j){
        cout << endl << endl <<"Delka: " << paths[j].length << endl;
        for (int i = 0; i < numOfNodes; ++i)
            cout << paths[j].nodes[i] << (i != numOfNodes - 1 ? " => " : "");
        cout << endl << "---------------------------------------------------------" << endl;
	}
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------
int main(void) {
	srand(unsigned(time(0)));

    int numOfNodes = nodeCount();
    vector<vector<int>> a(numOfNodes, vector<int>(numOfNodes, INF));

	//nacteni a parsovani XML
	load_XML(a);
	//vypocet ohodnoceni cest
	floyd(a, numOfNodes);

    //prevede z vektoru na dvojrozmerne pole
    int **distances = new int*[numOfNodes];
    for (int i = 0; i < numOfNodes; i++)
        distances[i] = new int[numOfNodes];
    for (int i = 0; i < numOfNodes; i++)
        for (int j = 0; j < numOfNodes; j++)
            distances[i][j] = a[i][j];

    vector<Path> paths;
    vector<Path> cross_paths;

    int kri = 0;
    int mut = 0;
    //int rep = 0;
    float tmp[ITERS] = {0};
    float compare = INF;

    Path p;
	initPath(numOfNodes, &p);

	// vygeneruje nahodne cesty
	for (int i = 0; i < PATHS; i++)
		paths.push_back(shufflePath(numOfNodes, distances, &p));

    //hlavni cyklus genetickeho algoritmu
	int iter = 0;
	while(compare != 1) {
		sort(paths.begin(), paths.end(), cmpPath()); // seradi cesty od nejlepsi po nejhorsi (podle celkove delky)

		paths.erase(paths.begin() + BEST_GENERATION, paths.end()); // odstrani urcite % nejhure ohodnocenych cest

		//cout << "Aktualni velikost: " << paths.size() << endl;
//-----------------------------------------------------------------------------------------------------------------------------------------------
        kri = 0;
        for (int i = 0; i < GENERATION * CROSSOVER_MAX; ++i)                    //KRIZENI
		{
			Path pResult;
			Path& p1 = paths[rand()%BEST_GENERATION];
            Path& p2 = paths[rand()%BEST_GENERATION];

            if (p1.nodes == p2.nodes) continue;
            paths.push_back(crossover(numOfNodes, distances, &p1, &p2));
            kri++;
        }

//-----------------------------------------------------------------------------------------------------------------------------------------------
        int limit = PATHS-paths.size();//-PATHS*REPRODUCTION;              //limit pro mutace
        //cout << endl << "Domutovat: " << limit << endl;

        mut = 0;

        for (int i = 0; i < limit; i++)                                         //MUTACE
		{
			Path p = paths[i%BEST_GENERATION];
			paths.push_back(mutation(numOfNodes, distances, &p));
			mut++;
		}

		//cout << "Aktualni velikost: " << paths.size() << endl;
//-----------------------------------------------------------------------------------------------------------------------------------------------
        //výpočet podmínky konce běhu algoritmu
        tmp[iter] = paths[0].length;
        if(iter > condition)
            compare = tmp[iter]/tmp[iter-condition];

        iter++;

        cout <<"Vaha nalezene trasy: " << paths[0].length << endl;
	}

	sort(paths.begin(), paths.end(), cmpPath()); // finalne seradi cesty od nejlepsi po nejhorsi (podle celkove delky)

    // vytiskne 10 cest a jejich vahy
	//printDebug(numOfNodes, paths);
    cout << "------------------------------------------------------" << endl;
	//vytiskne nejlepší nalezenou trasu
	cout <<"Vaha nalezene nejlepsi trasy: " << paths[0].length << endl << endl;
	printPath(numOfNodes, paths);

	cout << "------------------------------------------------------" <<endl;
	cout << "Pocet cest: " << paths.size() << endl << endl;
	cout << "Pocet krizeni: " << kri << endl;
	cout << "Pocet mutaci: " << mut << endl;
	cout << endl << "Pocet iteraci: " << iter << endl;
	//cout << "Pocet reprodukci: " << rep << endl;

    //vytiskne matici do souboru
	ofstream matrix ("matrix.txt", ofstream::out | ofstream::trunc);
    if (matrix.is_open()){

        for(int i = 0; i < numOfNodes; i++){
            for(int j = 0; j < numOfNodes; j++){
                matrix << a[i][j] << " ";
            }
            matrix << endl;
        }
        matrix.close();
    }
    else cout << "Unable to open file.";

    //vytiskne nalezenou trasu do textoveho souboru
	ofstream myfile ("result.txt", ofstream::out | ofstream::trunc);
    if (myfile.is_open()){
        myfile <<"Delka nalezene trasy: " << paths[0].length << endl << endl;
        for (int i = 0; i < numOfNodes; ++i)
            myfile << paths[0].nodes[i] << (i != numOfNodes - 1 ? " => " : "");
        myfile.close();
    }
    else cout << "Unable to open file.";

    return 0;
}
