#include "pch.h"
#include "GraphView.h"
#include "RunData.h"
#include <QDebug>
#include <QString>
#include <regex>



RunData::RunData()
{
}

RunData::RunData(std::string filePath) : fileStream(filePath), filePath(filePath)
{
    
    std::string file = filePath.substr(filePath.find_last_of("/\\")+1);
    name = file.substr(0, file.rfind("."));
    if (!fileStream.is_open())
    {
        //Cant open file
        badFileFlag = true;
        return;
    }
    /* Start extracting */
    while (fileStream.peek() != EOF) {
        std::string buffer;
        getLine(buffer);
        SolutionPointData sol;

        std::regex regexIter("i:([\\+\\-]?\\d+)");
        std::regex regexObjectiveFunction("of:([\\+\\-]?\\d+\\.\\d+(?:E[\\+\\-]\\d+)?)");
        std::smatch match;
        if (!std::regex_search(buffer, match, regexIter)) {
            qDebug() << "Bad formatatted Line[" << actualLine << "].";
            qDebug() << "Failed to matched:";
            qDebug() << QString::fromStdString(buffer);
            return;
        }
        sol.iteration = std::stoi(match[1]);
        if (!std::regex_search(buffer, match, regexObjectiveFunction)) {
            qDebug() << "Bad formatatted Line[" << actualLine << "].";
            qDebug() << "Failed to matched:";
            qDebug() << QString::fromStdString(buffer);
            return;
        }
        sol.objectiveFunction = std::stod(match[1]);
        std::regex regexParticleNumber("pN:([\\+\\-]?\\d+)");
        if (!std::regex_search(buffer, match, regexParticleNumber)) {
            qDebug() << "Bad formatatted Line[" << actualLine << "].";
            qDebug() << "Failed to matched:";
            qDebug() << QString::fromStdString(buffer);
            return;
        }
        sol.particleNumber = std::stoi(match[1]);
        std::regex regexBitVec("b:([01]+)");
        if(!std::regex_search(buffer, match, regexBitVec)) {
            qDebug() << "Bad formatatted Line[" << actualLine << "].";
            qDebug() << "Failed to matched:";
            qDebug() << QString::fromStdString(buffer);
            return;
        }
        sol.bitVec.resize(match[1].length());
        int count = 0;
        std::string str = match[1];
        for (std::string::size_type i = 0; i < str.size(); ++i) {
            sol.bitVec[i] = (str[i] == '1');
        }
        solutionVec.push_back(sol);
    }
    fileStream.close();
    calculateBestAndAverageIter();
    calculateParticleSolution();
    calculateDotsForDistribution();
    calculateBitFieldData();
    calculateMeanHammingDistance();
}

void RunData::getLine(std::string& bufferString)
{
    std::getline(fileStream, bufferString);
    actualLine++;
}

void RunData::calculateBestAndAverageIter()
{
    if (solutionVec.empty()) {
        return;
    }
    double minObjectiveFunctionInIter = solutionVec[0].objectiveFunction;
    double maxObjectiveFunctionInIter = solutionVec[0].objectiveFunction;
    double bestObjectiveFunctionFound = solutionVec[0].objectiveFunction;
    double actualIterObjectiveFunctionAggregate = solutionVec[0].objectiveFunction;
    int actualIter = solutionVec[0].iteration;
    int foundSolutionInIteration = 1; 
    for(int i = 1; i < solutionVec.size(); i++) {
        SolutionPointData nextData = solutionVec[i];
        if (nextData.iteration != actualIter) {
            //save last
            bestSolutionPerIteration.push_back(GraphDataPoint((double)actualIter, bestObjectiveFunctionFound));
            averageSolutionPerItertion.push_back(GraphDataPoint((double)actualIter, actualIterObjectiveFunctionAggregate / (double)foundSolutionInIteration));
            minSolutionPerItertion.push_back(GraphDataPoint((double)actualIter, minObjectiveFunctionInIter));
            maxSolutionPerItertion.push_back(GraphDataPoint((double)actualIter, maxObjectiveFunctionInIter));
            //init new iteration
            actualIter = nextData.iteration;
            foundSolutionInIteration = 1;
            actualIterObjectiveFunctionAggregate = nextData.objectiveFunction;
            minObjectiveFunctionInIter = nextData.objectiveFunction;
            maxObjectiveFunctionInIter = nextData.objectiveFunction;
        }
        else {
            //increae aggregate
            foundSolutionInIteration++;
            actualIterObjectiveFunctionAggregate += nextData.objectiveFunction;
        }
        //update best min and max if better
        if (nextData.objectiveFunction < bestObjectiveFunctionFound) {
            bestObjectiveFunctionFound = nextData.objectiveFunction;
        }
        if (nextData.objectiveFunction < minObjectiveFunctionInIter) {
            minObjectiveFunctionInIter = nextData.objectiveFunction;
        }
        if (nextData.objectiveFunction > maxObjectiveFunctionInIter) {
            maxObjectiveFunctionInIter = nextData.objectiveFunction;
        }
    }
    //save last iteration
    bestSolutionPerIteration.push_back(GraphDataPoint((double)actualIter, bestObjectiveFunctionFound));
    averageSolutionPerItertion.push_back(GraphDataPoint((double)actualIter, actualIterObjectiveFunctionAggregate / (double)foundSolutionInIteration));
    minSolutionPerItertion.push_back(GraphDataPoint((double)actualIter, minObjectiveFunctionInIter));
    maxSolutionPerItertion.push_back(GraphDataPoint((double)actualIter, maxObjectiveFunctionInIter));


}

void RunData::calculateParticleSolution()
{
    for (SolutionPointData sol : solutionVec) {
        GraphDataPoint point(sol.iteration, sol.objectiveFunction, &sol);
        auto iter = particleMap.find(sol.particleNumber);
        if (iter == particleMap.end()) {
            //create new Entry
            std::vector<GraphDataPoint> vec;
            vec.push_back(point);
            particleMap.insert({ sol.particleNumber, vec});
        }
        else {
            //append to vector in Entry
            iter->second.push_back(point);
        }
    }
}

void RunData::calculateDotsForDistribution()
{
    for (std::vector<SolutionPointData>::iterator it = solutionVec.begin(); it != solutionVec.end(); ++it) {
        dotsForDistribution.push_back(GraphDataPoint((double)it->iteration, it->objectiveFunction, &*it));
    }
}
int binominalIndex(const int n, int k)
{
    if (k > n / 2) k = n - k;
    return ((n+1)/2) * (n/2 + 1) + k;
}
boost::multiprecision::cpp_int positionInPermutation2(std::vector<boost::multiprecision::cpp_int>& pascalTriangleVec, std::vector<bool>::iterator begin, std::vector<bool>::iterator end, int amountOfSetBits)
{
    int amountOfBits = end - begin;
    //recursion base
    if (amountOfSetBits == 0) return 1;
    std::vector<bool>::iterator indexIter = std::find(begin, end, true);
    int index = indexIter - begin;
    int amountOfBitsAfterIndex = amountOfBits - 1 - index;
    //recursion base
    if (amountOfSetBits == 1) return amountOfBits - index;
    //Step 1 the amount of permutations with the rest amountOfBitsAfterIndex 
    boost::multiprecision::cpp_int before = pascalTriangleVec[binominalIndex(amountOfBitsAfterIndex, amountOfSetBits)];
    //Step 2 teh actual position of the rest
    boost::multiprecision::cpp_int after = positionInPermutation2(pascalTriangleVec, ++indexIter, end, amountOfSetBits - 1);
    //setp 3 add Step 1 and Step 2
    return before + after;
}

void RunData::calculateBitFieldData()
{
    std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();

    int amountOfBits = solutionVec[0].bitVec.size();
    int lines = amountOfBits + 1;
    std::vector<boost::multiprecision::cpp_int> pascalTriangleVec(((lines+1)/2 * (lines/2 + 1)));
    for (int line = 0; line < lines; line++) {
        for (int number = 0; number < line + 1; number++) {
            if (number > line / 2) break;
            if (number == 0 || number == line) {
                pascalTriangleVec[binominalIndex(line, number)] = 1;
            }
            else {
                pascalTriangleVec[binominalIndex(line, number)] = pascalTriangleVec[binominalIndex(line - 1, number)] + pascalTriangleVec[binominalIndex(line - 1, number - 1)];
            }

        }
    }
    std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
    std::chrono::milliseconds time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    qDebug() << "PascalTriangle: " << time.count() << "ms";

    for (std::vector<SolutionPointData>::iterator it = solutionVec.begin(); it != solutionVec.end(); ++it) {
            int amountOfSetBits = std::count(it->bitVec.begin(), it->bitVec.end(), true);
            boost::multiprecision::cpp_dec_float_100 position(positionInPermutation2(pascalTriangleVec, it->bitVec.begin(), it->bitVec.end(), amountOfSetBits) - 1);
            boost::multiprecision::cpp_dec_float_100 maxAmountOfPermutaions (pascalTriangleVec[binominalIndex(amountOfBits, amountOfSetBits)] - 1);

            dotsForBitField.push_back(GraphDataPoint((position / maxAmountOfPermutaions).convert_to<double>(), amountOfSetBits, &*it));
    }
    std::chrono::high_resolution_clock::time_point end2 = std::chrono::high_resolution_clock::now();
    time = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - end);
    qDebug() << "BitFieldBerechnung: " << time.count() << "ms";

}

void RunData::calculateMeanHammingDistance()
{
    std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
    std::vector<SolutionPointData>::iterator iterBegin = solutionVec.begin();
    for (std::vector<SolutionPointData>::iterator iter = solutionVec.begin(); iter != solutionVec.end(); iter++) {
        if (iter->iteration != iterBegin->iteration) {
            double mean = meanHammingDistance(iterBegin, iter);
            meanHammingDistancePerIteration.push_back(GraphDataPoint(iterBegin->iteration, mean));
            iterBegin = iter;
        }
    }
    std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
    std::chrono::milliseconds time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    qDebug() << "Mean: " << time.count() << "ms";
}


int RunData::hammingdistance(std::vector<bool>& bitVecA, std::vector<bool>& bitVecB)
{
    //assert((bitVecA.size() == bitVecB.size()));
    int count = 0;
    auto iterB = bitVecB.begin();
    for (auto iterA = bitVecA.begin(); iterA != bitVecA.end(); iterA++) {
        if (*iterA != *iterB) {
            count++;
        }
        iterB++;
    }
    return count;
}

double RunData::meanHammingDistance(std::vector<SolutionPointData>::iterator begin, std::vector<SolutionPointData>::iterator end)
{
    if (std::distance(begin, end)  <= 1) {
        return 0.0;
    }
    std::vector<SolutionPointData>::iterator startParticle(begin);
    double hammingValuesAccumulated = 0.0;
    int count = 0;
    for (std::vector<SolutionPointData>::iterator iter = begin; iter != end; iter++) {
        for (std::vector<SolutionPointData>::iterator iterParticle = iter + 1; iterParticle != end; iterParticle++) {
            hammingValuesAccumulated += hammingdistance(iter->bitVec, iterParticle->bitVec);
            count++;
        }
    }
    return hammingValuesAccumulated / (double) count;
}

boost::multiprecision::cpp_int RunData::binominal(const int n,int k)
{
    boost::multiprecision::cpp_int value(1);
    if (k > n / 2) k = n - k;
    for (int i = 0; i < k; i++) {
        value *= boost::multiprecision::cpp_int(n - i);
        value /= boost::multiprecision::cpp_int(i + 1);
    }
    return value;
}

boost::multiprecision::cpp_int RunData::positionInPermutation(std::vector<bool>::iterator begin, std::vector<bool>::iterator end, int amountOfSetBits)
{
    int amountOfBits = end - begin;
    //recursion base
    if (amountOfSetBits == 0) return 1;
    std::vector<bool>::iterator indexIter = std::find(begin, end, true);//TODO:false könnte andersrum sortieren!
    int index = indexIter - begin;
    int amountOfBitsAfterIndex = amountOfBits - 1 - index;
    //recursion base
    if (amountOfSetBits == 1) return amountOfBits - index;
    //Step 1 the amount of permutations with the rest amountOfBitsAfterIndex 
    boost::multiprecision::cpp_int before = binominal(amountOfBitsAfterIndex, amountOfSetBits);
    //Step 2 teh actual position of the rest
    boost::multiprecision::cpp_int after = positionInPermutation(++indexIter, end, amountOfSetBits - 1);
    //setp 3 add Step 1 and Step 2
    return before + after;
}


GraphDataPoint::GraphDataPoint(double x, double y, SolutionPointData* orginalPoint) : x(x), y(y), orginalPoint(orginalPoint)
{

}

GraphDataPoint::GraphDataPoint(double x, double y, QColor color, SolutionPointData* orginalPoint)
    : x(x), y(y), orginalPoint(orginalPoint), color(color)
{
}

bool GraphDataPoint::existLink()
{
    return orginalPoint != nullptr;
}

QPointF GraphDataPoint::toQPointF() const
{
    return QPointF(x, y);
}

double GraphDataPoint::squaredDistance(QPointF& graphPoint) const
{
    return std::pow(graphPoint.x() - x, 2) + std::pow(graphPoint.y() - y, 2);
}


