//
// Created by Dan Crosby on 4/17/21.
//

#ifndef EAGLE0_AIATTACKLOCATIONS_HPP
#define EAGLE0_AIATTACKLOCATIONS_HPP

#include <memory>
#include <utility>
#include <vector>

#include "src/main/cpp/net/eagle0/shardok/library/map/Coordinates.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"

namespace shardok {

using Unit = net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;

class AttackLocations {
private:
    const MapIndex rowCount;
    const MapIndex columnCount;
    CoordsSet mageLocations;
    CoordsSet necromancerLocations;
    CoordsSet engineerLocations;
    CoordsSet archeryLocations;
    CoordsSet adjacentLocations;
    CoordsSet allLocations;

public:
    AttackLocations(const MapIndex rowCount, const MapIndex columnCount)
        : rowCount(rowCount),
          columnCount(columnCount),
          mageLocations(columnCount, rowCount),
          necromancerLocations(columnCount, rowCount),
          engineerLocations(columnCount, rowCount),
          archeryLocations(columnCount, rowCount),
          adjacentLocations(columnCount, rowCount),
          allLocations(columnCount, rowCount) {}

    explicit AttackLocations(const HexMap* map)
        : rowCount(map->row_count()),
          columnCount(map->column_count()),
          mageLocations(map),
          necromancerLocations(map),
          engineerLocations(map),
          archeryLocations(map),
          adjacentLocations(map),
          allLocations(map) {}

    AttackLocations(
            const MapIndex rowCount,
            const MapIndex columnCount,
            CoordsSet mageL,
            CoordsSet necrL,
            CoordsSet engrL,
            CoordsSet archL,
            CoordsSet adjL)
        : rowCount(rowCount),
          columnCount(columnCount),
          mageLocations(mageL),
          necromancerLocations(necrL),
          engineerLocations(engrL),
          archeryLocations(archL),
          adjacentLocations(adjL),
          allLocations(mageLocations) {
        allLocations += necromancerLocations;
        allLocations += engineerLocations;
        allLocations += archeryLocations;
        allLocations += adjacentLocations;
    }

    [[nodiscard]] auto RowCount() const -> MapIndex { return rowCount; }
    [[nodiscard]] auto ColumnCount() const -> MapIndex { return columnCount; }
    [[nodiscard]] auto MageLocations() const -> const CoordsSet& { return mageLocations; }
    [[nodiscard]] auto NecromancerLocations() const -> const CoordsSet& {
        return necromancerLocations;
    }
    [[nodiscard]] auto EngineerLocations() const -> const CoordsSet& { return engineerLocations; }
    [[nodiscard]] auto ArcheryLocations() const -> const CoordsSet& { return archeryLocations; }
    [[nodiscard]] auto AdjacentLocations() const -> const CoordsSet& { return adjacentLocations; }
    [[nodiscard]] auto AllLocations() const -> const CoordsSet& { return allLocations; }

    auto LocationsWithEnemyInRange(const Unit* unit) const -> CoordsSet;

    auto operator+=(const AttackLocations& rhs) -> AttackLocations& {
        mageLocations += rhs.mageLocations;
        necromancerLocations += rhs.necromancerLocations;
        engineerLocations += rhs.engineerLocations;
        archeryLocations += rhs.archeryLocations;
        adjacentLocations += rhs.adjacentLocations;
        allLocations += rhs.allLocations;

        return *this;
    }

    void AddMageLocation(const Coords& l) {
        mageLocations.Add(l);
        allLocations.Add(l);
    }

    void AddMageLocations(const CoordsSet& cs) {
        mageLocations += cs;
        allLocations += cs;
    }

    void AddNecromancerLocation(const Coords& l) {
        necromancerLocations.Add(l);
        allLocations.Add(l);
    }

    void AddNecromancerLocations(const CoordsSet& cs) {
        necromancerLocations += cs;
        allLocations += cs;
    }

    void AddEngineerLocation(const Coords& l) {
        engineerLocations.Add(l);
        allLocations.Add(l);
    }

    void AddEngineerLocations(const CoordsSet& cs) {
        engineerLocations += cs;
        allLocations += cs;
    }

    void AddArcheryLocation(const Coords& l) {
        archeryLocations.Add(l);
        allLocations.Add(l);
    }

    void AddArcheryLocations(const CoordsSet& cs) {
        archeryLocations += cs;
        allLocations += cs;
    }

    void AddAdjacentLocation(const Coords& l) {
        adjacentLocations.Add(l);
        allLocations.Add(l);
    }

    void AddAdjacentLocations(const CoordsSet& cs) {
        adjacentLocations += cs;
        allLocations += cs;
    }
} __attribute__((aligned(128)));

struct AttackLocationsCache {
private:
    const MapIndex rowCount;
    const MapIndex columnCount;

    // For each of these vectors, the index is the map index of the tile *that can be attacked*,
    // and the entry is the CoordsSet of the tiles that can attack that location.

    std::vector<AttackLocations> notLateGameLocations;
    std::vector<AttackLocations> lateGameLocations;

public:
    AttackLocationsCache(const HexMap* hexMap, const SettingsGetter& settings);

    auto CachedLocations(const Coords& singleTarget, bool lateGame) -> const AttackLocations&;
    auto CachedLocations(const CoordsSet& targets, bool lateGame) -> AttackLocations;
    auto CachedLocations(const vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
} __attribute__((aligned(128)));

using ALCache = std::unique_ptr<AttackLocationsCache>;

}  // namespace shardok

#endif  // EAGLE0_AIATTACKLOCATIONS_HPP
