#ifndef EPIWORLD_PERSON_BONES_HPP
#define EPIWORLD_PERSON_BONES_HPP

template<typename TSeq>
class Model;

template<typename TSeq>
class Virus;

template<typename TSeq>
class Viruses;

template<typename TSeq>
class Viruses_const;

template<typename TSeq>
class Tool;

template<typename TSeq>
class Tools;

template<typename TSeq>
class Tools_const;

template<typename TSeq>
class Queue;

template<typename TSeq>
struct Event;

template<typename TSeq>
class Entity;

template<typename TSeq>
class Entities;

template<typename TSeq>
class AgentsSample;

/**
 * @brief Agent (agents)
 * 
 * @tparam TSeq Sequence type (should match `TSeq` across the model)
 */
template<typename TSeq>
class Agent {
    friend class Model<TSeq>;
    friend class Virus<TSeq>;
    friend class Tool<TSeq>;
    friend class Tools<TSeq>;
    friend class Tools_const<TSeq>;
    friend class Queue<TSeq>;
    friend class AgentsSample<TSeq>;
private:

    std::vector< size_t > * neighbors = nullptr;
    std::vector< size_t > * neighbors_locations = nullptr;
    size_t n_neighbors = 0u;

    std::vector< size_t > entities; ///< Entity IDs (indices into Model::entities)

    unsigned int state = 0u;
    unsigned int state_prev = 0u; ///< For accounting, if need to undo a change.
    
    int state_last_changed = -1; ///< Last time the agent was updated.
    int id = -1;
    
    VirusPtr<TSeq> virus = nullptr;

    std::vector< ToolPtr<TSeq> > tools;
    unsigned int n_tools = 0u;

    void reset(); ///< Resets the agent to the initial state (no virus, no tools, no entities, state 0.)

public:

    Agent() = default;
    Agent(Agent<TSeq> && p);
    Agent(const Agent<TSeq> & p);
    Agent<TSeq> & operator=(const Agent<TSeq> & other_agent);
    ~Agent();

    /**
     * @name Add/Remove Virus/Tool
     * 
     * Any of these is ultimately reflected at the end of the iteration.
     * 
     * @param tool Tool to add
     * @param virus Virus to add
     * @param state_new state after the change
     * @param queue 
     */
    ///@{
    void add_tool(
        Model<TSeq> & model,
        ToolPtr<TSeq> & tool,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
        );

    void add_tool(
        Model<TSeq> & model,
        const Tool<TSeq> & tool,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
        );

    void set_virus(
        Model<TSeq> & model,
        VirusPtr<TSeq> & virus,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
        );

    void set_virus(
        Model<TSeq> & model,
        const Virus<TSeq> & virus,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
        );

    void add_entity(
        Model<TSeq> & model,
        Entity<TSeq> & entity,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
        );

    void rm_tool(
        Model<TSeq> & model,
        epiworld_fast_uint tool_idx,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
    );

    void rm_tool(
        Model<TSeq> & model,
        ToolPtr<TSeq> & tool,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
    );

    void rm_virus(
        Model<TSeq> & model,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
    );

    void rm_entity(
        Model<TSeq> & model,
        epiworld_fast_uint entity_idx,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
    );

    void rm_entity(
        Model<TSeq> & model,
        Entity<TSeq> & entity,
        epiworld_fast_int state_new = -99,
        epiworld_fast_int queue = -99
    );

    void rm_agent_by_virus(Model<TSeq> & model) = delete; ///< Agent removed by virus
    ///@}
    
    /**
     * @name Get the rates (multipliers) for the agent
     * 
     * @param v A pointer to a virus.
     * @return epiworld_double 
     */
    ///@{
    epiworld_double get_susceptibility_reduction(VirusPtr<TSeq> & v, Model<TSeq> & model);
    epiworld_double get_transmission_reduction(VirusPtr<TSeq> & v, Model<TSeq> & model);
    epiworld_double get_recovery_enhancer(VirusPtr<TSeq> & v, Model<TSeq> & model);
    epiworld_double get_death_reduction(VirusPtr<TSeq> & v, Model<TSeq> & model);
    ///@}

    int get_id() const; ///< Id of the individual

    VirusPtr<TSeq> & get_virus();
    const VirusPtr<TSeq> & get_virus() const;

    ToolPtr<TSeq> & get_tool(int i);
    Tools<TSeq> get_tools();
    const Tools_const<TSeq> get_tools() const;
    size_t get_n_tools() const noexcept;

    void mutate_virus();
    void add_neighbor(
        Agent<TSeq> & p,
        bool check_source = true,
        bool check_target = true
        );

    /**
     * @brief Swaps neighbors between the current agent and agent `other`
     * 
     * @param other 
     * @param n_this 
     * @param n_other 
     */
    void swap_neighbors(
        Agent<TSeq> & other,
        size_t n_this,
        size_t n_other,
        Model<TSeq> & model
    );

    std::vector< Agent<TSeq> * > get_neighbors(Model<TSeq> & model);
    size_t get_n_neighbors() const;

    void change_state(
        Model<TSeq> & model,
        epiworld_fast_uint new_state,
        epiworld_fast_int queue = 0
        );

    const unsigned int & get_state() const;
    const unsigned int & get_state_prev() const;

    bool has_tool(epiworld_fast_uint t) const;
    bool has_tool(std::string name) const;
    bool has_tool(const Tool<TSeq> & t) const;
    bool has_virus(epiworld_fast_uint t) const;
    bool has_virus(std::string name) const;
    bool has_virus(const Virus<TSeq> & v) const;
    bool has_entity(epiworld_fast_uint t) const;
    bool has_entity(std::string name, const Model<TSeq> & model) const;

    void print(Model<TSeq> & model, bool compressed = false) const;

    /**
     * @brief Access the j-th column of the agent
     * 
     * If an external array has been specified, then these two
     * functions can be used to access additional agent's features 
     * not included in the model.
     * 
     * @param j 
     * @param model Reference to the Model
     * @return double& 
     */
    ///@{
    double & operator()(size_t j, Model<TSeq> & model);
    double operator()(size_t j, const Model<TSeq> & model) const;
    ///@}

    const std::vector< size_t > & get_entities() const;

    const Entity<TSeq> & get_entity(size_t i, const Model<TSeq> & model) const;
    Entity<TSeq> & get_entity(size_t i, Model<TSeq> & model);

    size_t get_n_entities() const;

    bool operator==(const Agent<TSeq> & other) const;
    bool operator!=(const Agent<TSeq> & other) const {return !operator==(other);};

};



#endif
