// The MIT License (MIT)
//
// Copyright (c) 2017-2021 Alexander Kurbatov

#pragma once

#include "core/Map.h"
#include "objects/Geyser.h"
#include "objects/Worker.h"

#include <sc2api/sc2_gametypes.h>
#include <sc2api/sc2_unit.h>

#include <list>

// ------------------------------------------------------------------
// ------------------------------------------------------------------
template <typename T>
struct Cache
{
    std::list<T>& operator()() { return m_objects; }

    bool Empty() const;
    uint64_t Count() const;
    void Add(const T& obj_);
    T& Back();
    void PopBack();
    void Clear();
    bool IsCached(const T& obj_) const;
    bool Swap(const T& obj_, Cache<T>& dst_);
    bool Remove(const T& obj_);
    T* GetClosestTo(const sc2::Point2D& location_);

 private:
    std::list<T> m_objects;
};

// ------------------------------------------------------------------
template <typename T>
bool Cache<T>::Empty() const
{
    return m_objects.empty();
}

// ------------------------------------------------------------------
template <typename T>
uint64_t Cache<T>::Count() const
{
    return m_objects.size();
}

// ------------------------------------------------------------------
template <typename T>
void Cache<T>::Add(const T& obj_)
{
    m_objects.push_back(obj_);
}

// ------------------------------------------------------------------
template <typename T>
T& Cache<T>::Back()
{
    return m_objects.back();
}

// ------------------------------------------------------------------
template <typename T>
void Cache<T>::PopBack()
{
    m_objects.pop_back();
}

// ------------------------------------------------------------------
template <typename T>
void Cache<T>::Clear()
{
    m_objects.clear();
}

// ------------------------------------------------------------------
template <typename T>
bool Cache<T>::IsCached(const T& obj_) const
{
    auto it = std::find(m_objects.begin(), m_objects.end(), obj_);

    return m_objects.end() != it;
}

// ------------------------------------------------------------------
template <typename T>
bool Cache<T>::Swap(const T& obj_, Cache<T>& dst_)
{
    auto it = std::find(m_objects.begin(), m_objects.end(), obj_);

    if (m_objects.end() == it)
    {
        return false;
    }

    dst_.Add(*it);
    m_objects.erase(it);
    return true;
}

// ------------------------------------------------------------------
template <typename T>
bool Cache<T>::Remove(const T& obj_)
{
    auto it = std::find(m_objects.begin(), m_objects.end(), obj_);
    if (m_objects.end() == it)
    {
        return false;
    }

    m_objects.erase(it);
    return true;
}

// ------------------------------------------------------------------
template <typename T>
T* Cache<T>::GetClosestTo(const sc2::Point2D& location_)
{
    auto closest_worker = m_objects.end();
    float distance = std::numeric_limits<float>::max();

    for (auto it = m_objects.begin(); it != m_objects.end(); ++it)
    {
        float d = sc2::DistanceSquared2D(it->GetPos(), location_);
        if (d >= distance)
        {
            continue;
        }

        distance = d;
        closest_worker = it;
    }

    if (closest_worker == m_objects.end())
    {
        return nullptr;
    }

    return &(*closest_worker);
}

// ------------------------------------------------------------------
// ------------------------------------------------------------------
struct Hub
{
    Hub(sc2::Race current_race_, const Expansions& expansions_);

    void OnStep();
    void OnUnitCreated(const sc2::Unit& unit_);
    void OnUnitDestroyed(const sc2::Unit& unit_);
    void OnUnitIdle(const sc2::Unit& unit_);
    void OnUnitEnterVision(const sc2::Unit& unit_);
    bool IsOccupied(const sc2::Unit& unit_) const;
    bool IsTargetOccupied(const sc2::UnitOrder& order_) const;
    void ClaimObject(const sc2::Unit& unit_);
    sc2::Race GetCurrentRace() const;

    Worker* GetClosestFreeWorker(const sc2::Point2D& location_);
    sc2::UNIT_TYPEID GetCurrentSupplyType() const;
    sc2::UNIT_TYPEID GetCurrentWorkerType() const;

    bool AssignRefineryConstruction(Order* order_, const sc2::Unit* geyser_);
    sc2::Tag AssignBuildTask(Order* order_, const sc2::Point2D& point_);
    void AssignVespeneHarvester(const sc2::Unit& refinery_);
    bool AssignLarva(Order* order_);

    const Cache<GameObject>&  GetLarvas() const;
    const Expansions& GetExpansions() const;
    Expansion* GetNextExpansion();
    Expansion* GetClosestExpansion(const sc2::Point2D& location_, Owner expansionOwner_ = Owner::NEUTRAL);
    Expansion* GetClosestExpansionExcluding(const sc2::Point2D& location_, std::vector<Expansion*> exlude_, Owner expansionOwner_ = Owner::NEUTRAL);
    Expansion* GetClosestExpansionIncluding(const sc2::Point2D& location_, std::vector<Expansion*> include_, Owner expansionOwner_ = Owner::NEUTRAL);
    const Expansion* GetBestMiningExpansionNear(const sc2::Unit* unit_) const;

 private:
    Expansion* GetExpansionOfTownhall(const sc2::Unit& unit_);
    void CaptureExpansion(const sc2::Unit& unit_);
    void EnemyOwnsExpansion(const sc2::Unit& unit_);
    void RemoveExpansionOwner(const sc2::Unit& unit_);

    sc2::Race m_current_race;
    Expansions m_expansions;
    sc2::UNIT_TYPEID m_current_supply_type;
    sc2::UNIT_TYPEID m_current_worker_type;

    Cache<Geyser> m_captured_geysers;

    Cache<Worker> m_busy_workers;
    Cache<Worker> m_free_workers;
    Cache<GameObject> m_larva;
};

extern std::unique_ptr<Hub> gHub;
