//
// Created by Dan Crosby on 9/6/20.
//

#ifndef EAGLE0_ACTIONPOINTDISTANCES_HPP
#define EAGLE0_ACTIONPOINTDISTANCES_HPP

#include <future>
#include <optional>

#include "src/main/cpp/net/eagle0/shardok/library/BattalionType.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/FlatbufferWrapper.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/Coordinates.hpp"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/hex_map.hpp"

// Uses Dijkstra's algorithm to calculate the distance from a tile to all other tiles
// for a particular battalion type.

namespace shardok {

using HexMap = net::eagle0::shardok::storage::fb::HexMap;
using HexMapW = Wrapper<HexMap>;
using Coords = net::eagle0::shardok::storage::fb::Coords;
using std::optional;
using std::shared_future;
using std::vector;

using DIST_T = int16_t;

class ActionPointDistances {
private:
    const int8_t column_count;

protected:
    struct BraveableTileInfo;

    static void PopulateOne(
            vector<DIST_T> &ds,
            const net::eagle0::shardok::storage::fb::HexMap *hexMap,
            bool includeBravingWater,
            int braveWaterCost,
            const BattalionTypeSPtr &battalionType,
            const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords,
            const vector<Coords> &indexToCoords,
            const vector<std::array<int, 6>> &adjacencyTable);
    static auto GenerateDistances(
            int fromIndex,
            const HexMap *hexMap,
            bool includeBravingWater,
            int braveWaterCost,
            const BattalionTypeSPtr &battalionType,
            const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords) -> vector<DIST_T>;
    auto BraveWaterPossibleCoords(const HexMap *hexMap) const -> std::shared_ptr<BraveableTileInfo>;

    // Create coordinate lookup table for efficient index->coords conversion
    static auto CreateIndexToCoords(const HexMap *hexMap) -> vector<Coords>;

    // Create adjacency lookup table for efficient neighbor access
    static auto CreateAdjacencyTable(const HexMap *hexMap) -> vector<std::array<int, 6>>;

    [[nodiscard]] auto ToIndex(const Coords &coords) const -> int {
        return coords.row() * column_count + coords.column();
    }

public:
    constexpr static DIST_T IMPOSSIBLE = std::numeric_limits<DIST_T>::max();

    explicit ActionPointDistances(const int8_t columnCount) : column_count(columnCount) {}

    virtual ~ActionPointDistances() = default;

    virtual auto Distance(int fromIndex, int toIndex) const -> DIST_T = 0;

    virtual auto Distance(const Coords &from, const Coords &to) const -> DIST_T = 0;
};

class OnDemandActionPointDistances final : public ActionPointDistances {
private:
    const HexMapW hexMap;
    const BattalionTypeSPtr battalionType;

    vector<shared_future<vector<int16_t>>> distances;

public:
    explicit OnDemandActionPointDistances(
            const HexMap *map,
            BattalionTypeSPtr battTp,
            bool includeBravingWater,
            int braveWaterActionPointCost = -1);

    ~OnDemandActionPointDistances() override = default;

    auto Distance(const int fromIndex, const int toIndex) const -> int16_t override {
        return distances[fromIndex].get()[toIndex];
    }

    auto Distance(const Coords &from, const Coords &to) const -> int16_t override {
        return Distance(ToIndex(from), ToIndex(to));
    }
};

}  // namespace shardok

#endif  // EAGLE0_ACTIONPOINTDISTANCES_HPP
