//
// Created by Dan Crosby on 10/13/22.
//

#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"

#include <thread>

#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"

// Dynamic thread count based on hardware capabilities
static const int ASYNC_COUNT = []() {
    const int cores = static_cast<int>(std::thread::hardware_concurrency());
    // Use cores-2 to leave room for OS and other processes, minimum 4 threads
    const int threadCount = std::max(4, cores - 4);
    fprintf(stderr,
            "ActionPointDistances using %d threads (detected %d cores)\n",
            threadCount,
            cores);
    return threadCount;
}();

namespace shardok {

FixedActionPointDistances::FixedActionPointDistances(const HexMap* /*map*/, int columnCount)
    : ActionPointDistances(columnCount) {}

auto FixedActionPointDistances::Create(
        const HexMap* map,
        const BattalionTypeSPtr& battalionType,
        bool includeBravingWater,
        int braveWaterActionPointCost) -> std::shared_ptr<FixedActionPointDistances> {
    // Create the object using private constructor
    auto apd = std::shared_ptr<FixedActionPointDistances>(
            new FixedActionPointDistances(map, map->column_count()));

    const int indexCount = map->row_count() * map->column_count();

    vector<std::future<vector<vector<DIST_T>>>> futures(indexCount);

    auto braveWaterPossibleCoords =
            includeBravingWater ? apd->BraveWaterPossibleCoords(map) : nullptr;

    int chunkSize = (indexCount + ASYNC_COUNT - 1) / ASYNC_COUNT;
    // Break into chunks for async
    for (int chunkIdx = 0; chunkIdx < ASYNC_COUNT; chunkIdx++) {
        futures[chunkIdx] = std::async(std::launch::async, [=]() -> vector<vector<DIST_T>> {
            vector<vector<DIST_T>> chunkVec;
            chunkVec.reserve(chunkSize);
            const int chunkStartIndex = chunkIdx * chunkSize;

            for (int i = 0; i < chunkSize; i++) {
                const auto fromIndex = chunkStartIndex + i;
                if (fromIndex >= indexCount) { continue; }
                chunkVec.push_back(ActionPointDistances::GenerateDistances(
                        fromIndex,
                        map,
                        includeBravingWater,
                        braveWaterActionPointCost,
                        battalionType,
                        braveWaterPossibleCoords));
            }
            return chunkVec;
        });
    }

    apd->distances.reserve(indexCount);

    for (int chunkIdx = 0; chunkIdx < ASYNC_COUNT; chunkIdx++) {
        auto resultsVec = futures[chunkIdx].get();
        apd->distances.insert(apd->distances.end(), resultsVec.begin(), resultsVec.end());
    }

    return apd;
}

}  // namespace shardok
