#ifndef _ResourcePool_h_
#define _ResourcePool_h_


#include <set>
#include <vector>
#include <boost/container/flat_set.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/version.hpp>
#include <boost/signals2/signal.hpp>
#include "../universe/EnumsFwd.h"
#include "../util/Enum.h"
#include "../util/Export.h"

class ObjectMap;

//! Types of resources that Empires can produce
FO_ENUM(
    (ResourceType),
    ((INVALID_RESOURCE_TYPE, -1))
    ((RE_INDUSTRY))
    ((RE_INFLUENCE))
    ((RE_RESEARCH))
    ((RE_STOCKPILE))
    ((NUM_RESOURCE_TYPES))
)

/** Returns the equivalent meter type for the given resource type; if no such
  * meter type exists, returns MeterType::INVALID_METER_TYPE. */
FO_COMMON_API MeterType ResourceToMeter(ResourceType type) noexcept;
FO_COMMON_API MeterType ResourceToTargetMeter(ResourceType type) noexcept;

/** Returns the equivalent resource type for the given meter type; if no such
  * resource type exists, returns INVALID_RESOURCE_TYPE. */
FO_COMMON_API ResourceType MeterToResource(MeterType type) noexcept;

/** The ResourcePool class keeps track of an empire's stockpile and production
  * of a particular resource (eg. research, industry). */
class FO_COMMON_API ResourcePool {
public:
    explicit ResourcePool(ResourceType type) :
        m_type(type)
    {}

    [[nodiscard]] auto        Type() const noexcept { return m_type; }
    [[nodiscard]] const auto& ObjectIDs() const noexcept { return m_object_ids; }
    [[nodiscard]] float       Stockpile() const noexcept { return m_stockpile; }
    [[nodiscard]] const auto& Output() const noexcept { return m_connected_object_groups_resource_output; }
    [[nodiscard]] float       TotalOutput() const;                  ///< returns amount of resource being generated by all ResourceCenters
    [[nodiscard]] float       GroupOutput(int object_id) const;     ///< returns amount of resource being generated by resource sharing group that contains the object with id \a object_id
    [[nodiscard]] const auto& Target() const noexcept { return m_connected_object_groups_resource_target_output; }
    [[nodiscard]] float       TargetOutput() const;
    [[nodiscard]] float       GroupTargetOutput(int object_id) const;
    [[nodiscard]] float       TotalAvailable() const;               ///< amount of resource immediately available = output + stockpile from all ResourceCenters, ignoring limitations of connections between centers
    [[nodiscard]] float       GroupAvailable(int object_id) const;  ///< returns amount of resource available in resource sharing group that contains the object with id \a object_id
    [[nodiscard]] const auto& Groups() const noexcept { return m_connected_system_groups; }

    /** Sets of groups of objects that can share resources, and the
      * amount of this pool's resource that each group has available  */
    [[nodiscard]] const auto& Available() const noexcept { return m_connected_object_groups_resource_output; }

    [[nodiscard]] std::string Dump() const;

    /** emitted after updating production, or called externally to indicate
      * that stockpile and change need to be refreshed. */
    mutable boost::signals2::signal<void ()> ChangedSignal;

    void SetObjects(std::vector<int> object_ids) noexcept;

    /** specifies which sets systems can share resources.  any two sets should
      * have no common systems. */
    void SetConnectedSupplyGroups(std::set<std::set<int>> connected_system_groups) noexcept;

    void SetStockpile(float d);             ///< sets current sockpiled amount of resource
    void Update(const ObjectMap& objects);  ///< recalculates total resource production

private:
    ResourcePool() = default;

    using int_flat_set = boost::container::flat_set<int>;
    std::vector<int>                m_object_ids;                                       ///< IDs of objects to consider in this pool
    std::set<std::set<int>>         m_connected_system_groups;                          ///< sets of systems between and in which objects can share this pool's resource
    std::map<int_flat_set, float>   m_connected_object_groups_resource_output;          ///< cached map from connected group of objects that can share resources, to how much resource is output by ResourceCenters in the group.  regenerated during update from other state information. // TODO: flat_map ?
    std::map<int_flat_set, float>   m_connected_object_groups_resource_target_output;   ///< cached map from connected group of objects that can share resources, to how much resource would, if all meters equaled their target meters, be output by ResourceCenters in the group.  regenerated during update from other state information.
    float                           m_stockpile = 0.0f;                                 ///< current stockpiled amount of resource
    ResourceType                    m_type = ResourceType::INVALID_RESOURCE_TYPE;       ///< what kind of resource does this pool hold?

    friend class boost::serialization::access;
    template <typename Archive>
    void serialize(Archive& ar, const unsigned int version);
};


BOOST_CLASS_VERSION(ResourcePool, 1)


template <typename Archive>
void ResourcePool::serialize(Archive& ar, const unsigned int version)
{
    ar  & BOOST_SERIALIZATION_NVP(m_type)
        & BOOST_SERIALIZATION_NVP(m_object_ids)
        & BOOST_SERIALIZATION_NVP(m_stockpile);
    if (version < 1) {
        int dummy = -1;
        ar  & boost::serialization::make_nvp("m_stockpile_object_id", dummy);
    }
    ar  & BOOST_SERIALIZATION_NVP(m_connected_system_groups);
}


#endif
