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

#ifndef EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_HPP
#define EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_HPP

#include <array>
#include <future>
#include <limits>
#include <memory>
#include <vector>

#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 DIST_T = int16_t;

class ActionPointDistances {
private:
    const int8_t column_count;

protected:
    struct BraveableTileInfo;

    static void PopulateOne(
            std::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 std::vector<Coords> &indexToCoords,
            const std::vector<std::array<int, 6>> &adjacencyTable);
    [[nodiscard]] static auto GenerateDistances(
            int fromIndex,
            const HexMap *hexMap,
            bool includeBravingWater,
            int braveWaterCost,
            const BattalionTypeSPtr &battalionType,
            const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords)
            -> std::vector<DIST_T>;
    [[nodiscard]] auto BraveWaterPossibleCoords(const HexMap *hexMap) const
            -> std::shared_ptr<BraveableTileInfo>;

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

    // Create adjacency lookup table for efficient neighbor access
    [[nodiscard]] static auto CreateAdjacencyTable(const HexMap *hexMap)
            -> std::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;

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

    [[nodiscard]] 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;

    std::vector<std::shared_future<std::vector<int16_t>>> distances;

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

    ~OnDemandActionPointDistances() override = default;

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

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

}  // namespace shardok

#endif  // EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_HPP
