#include <Execution/ParallelExecutor.hpp>

#include <deque>
#include <functional>
#include <future>
#include <map>
#include <exception>

#include <QtCore/QGlobalStatic>

#include <Models/OutputModelBox.hpp>

std::shared_ptr<abstraction::Value> ParallelExecutor::execute(OutputModelBox* output) {

    const std::vector<ModelBox*>& boxes = ModelBox::getAllModelBoxes();

    for (size_t i = 0; i < boxes.size(); ++i) {
        auto* box = boxes[i];
        for (auto* input: box->inputs) {
            if (!input) {
                throw std::invalid_argument { "Connection graph contains an unconnected input." };
            }
        }
        box->id = i;
        box->level = static_cast<size_t>(-1);
        box->clearCachedResult();
    }

    std::vector<std::vector<size_t>> M;
    size_t N = boxes.size();
    M.resize(N);
    for (size_t i = 0; i < N; ++i) {
        M[i].resize(N);
    }

    for (size_t i = 0; i < N; ++i) {
        ModelBox* b = boxes[i];
        for (const auto& neighbour: b->outputs) {
            if (b == neighbour.first) {
                throw std::invalid_argument { "Connection graph contains a loop." };
            }
            M[b->id][neighbour.first->id] = 1;
        }
    }

    // transitive closure
    for (size_t k = 0; k < N; k++) {
        for (size_t i = 0; i < N; i++) {
            for (size_t j = 0; j < N; j++) {
                M[i][j] = M[i][j] | static_cast<size_t>(M[i][k] & M[k][j]);
            }
        }
    }

    // transitive reduction
    for (size_t i = 0; i < N; ++i) {
        M[i][i] = 0;
    }

    for (size_t j = 0; j < N; ++j) {
        for (size_t i = 0; i < N; ++i) {
            if (M[i][j]) {
                for (size_t k = 0; k < N; ++k) {
                    if (M[j][k]) {
                        M[i][k] = 0;
                    }
                }
            }
        }
    }


    // topological sort
    std::set<ModelBox*> S;
    for (auto* box: boxes) {
        bool hasInEdges = false;
        for (size_t i = 0; i < N; ++i) {
            if (M[i][box->id]) {
                hasInEdges = true;
                break;
            }
        }
        if (!hasInEdges) {
            S.insert(box);
        }
    }

    std::vector<ModelBox*> sorted;
    while (!S.empty()) {
        ModelBox* n = *S.begin();
        S.erase(n);
        sorted.push_back(n);
        for (size_t i = 0; i < N; ++i) {
            ModelBox* m = boxes[i];
            if (M[n->id][i]) {
                M[n->id][i] = 0;

                bool hasInEdges = false;
                for (size_t j = 0; j < N; ++j) {
                    if (M[j][m->id]) {
                        hasInEdges = true;
                        break;
                    }
                }
                if (!hasInEdges) {
                    S.insert(m);
                }
            }
        }
    }

    for (size_t i = 0; i < N; ++i) {
        for (size_t j = 0; j < N; ++j) {
            if (M[i][j]) {
                throw std::invalid_argument { "Connection graph contains a cycle." };
            }
        }
    }

    size_t W = 8;
    std::vector<std::vector<ModelBox*>> levels;

    auto addToLevel = [&](ModelBox* box, size_t level) {

        if (level >= levels.size()) {
            levels.resize(level + 1);
        }

        while (levels[level].size() == W) {
            ++level;
            if (level >= levels.size()) {
                levels.resize(level + 1);
            }
        }

        levels[level].push_back(box);
        box->level = level;
    };

    for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) {
        ModelBox* box = *it;

        size_t level = 0;
        for (const auto& neighbour: box->outputs) {
            if (level <= neighbour.first->level) {
                level = neighbour.first->level + 1;
            }
        }

        addToLevel(box, level);
    }

    for (auto it = levels.rbegin(); it != levels.rend(); ++it) {
        const std::vector<ModelBox*>& level = *it;

        std::vector<std::future<void>> futures;
        for (auto* box: level) {
            futures.push_back(std::async(std::launch::async, [=]{ box->getCachedResultOrEvaluate(); }));
        }
    }

    return output->getCachedResult();
}
