#ifndef POPULATION_H
#define POPULATION_H

#include <iostream>
#include <string>
#include <vector>
#include <set>

#include "misc.h"
#include "specie.h"
#include "individual.h"
#include "individual_index.h"
#include "map.h"

#define FEMALE 0
#define MALE 1


class species;
class individual;
class cell;
class landscape;
class individual_index;
class trait;
class group_index;

//double Random();


using indMap = unordered_map<indId, individual*>;

/*
 * Acts like an index of individuals, it only stores a pointer to the actual individual
 * It is basically a wrapper for a vector of pointers to individuals
 * It allows some otimisation of the vectors behaviour (time/memory balance)
 * or the replacment of vector for another container
 */

 /* 21/01/15 => I now think that in a cell, a population for each species should exist making numerous things easier
    THis most notably allows to transfer some parameters from cells to populations (species specific things lke cap/growth)

 */

// TODO try uniq_ptr for ind
// TODO: put private all functions not used from outside
class population
{
    public:
        population(species * i_species, unsigned int i_abs, unsigned int n_pops);
        virtual ~population();

        /**/// Funtions to add/remove individuals:
        // Adds inidvidual to population
        void add_individual(individual* p_ind); // OK
        void add_individual_buffer(individual* p_ind); // OK
        // Removes individual from poulation, does NOT delete
        individual * remove_individual(indId id); // OK
        // Deletes individual
        void destroy_individual(indId id); // OK
        // Deletes individual from iterator, returns next ierator
        indMap::iterator destroy_from_it(indMap::iterator it); // Ok
        // Selects a random individual from index
        indId pick_random(); // OK
        // Temp function deletes a random individual
        void kill_random(); // OK
        // Returns a vector of indId in the container order, updated when needed
        const vector<indId>& get_ids();

        /**/// Demography functions:
        // Former demography function - DEPRECATED
        //int demography(); // Ok
        // New demography function
        int demography_new(); // OK


        // define a new size of pop artificially
        void force_targetPop(int tPop);

        //check if cumul_fitness different between indiv
        void checkIfCfitDiff();
        // Spawn (or despawns) individuals to reach targetPop value
        //population* adjust(landscape &myMap, mClock &m_clock);
        void adjust(landscape &myMap, mClock &m_clock);
        void adjust_generationmode(landscape &myMap, mClock &m_clock);
        int storm_impact();
        // Uptate ages, deletes individals above threshold
        void update_ages(mClock &m_clock);
        int get_age() {return age;}




        /**/// Funtions related to reproduction:
        // Selects a pop based on number of individuals * flux matrix of propagule
        population * select_pop_demo(landscape &myMap, int propagule, int motherPop); // Ok
        // Seclects pop  based on number of individuals in a phenotypic range
        population * select_pop_assortMating(double assortValue, landscape &myMap,
                                         int propagule, double& tolerance, int motherPop); //Ok
        // Adds new ind to pop from pointers to mother and father
        individual* generate_ind(individual& motherRef, individual& fatherRef, mClock &m_clock);
        // Rand selects an ind from the pop inside the tolerance level for assort trait
        indId pick_assortMating(double assortValue, double tolerance, bool needMotherCut, indId refMother);
        // Rand selects an ind inside start/end of range, weighted by weights
        indId pick_weighted_range(individual_index &iiRange,
                                        individual_index &iiWeight,
                                        double start, double end, bool needMotherCut, indId refMother);
        //Rand selects an individual based on fitness
        indId pick_fitness(bool needMotherCut, indId refMother);
        // Selects a partent pop & id based on fitness
        pair<population*, indId> select_parent(int sex, landscape &myMap, int motherPop, indId motherNum); // Ok
        pair<population*, indId> select_father_assortMating(pair<population*, indId> mother, string assort, landscape &myMap); // need check
        // Spawns a new individual in this population
        individual* spawn(landscape &myMap, mClock &m_clock); // Ok


        /**/// Individual index based functions
        // Calculates ages and store them in an index
        void make_ages_index(mClock &m_clock); // Ok
        // Deletes all individuals in the given range
        int kill_range(string key, double mini, double maxi); // Ok
        // Same than kill_range, with a kill probability of ratio for each indiv
        int kill_range(string key, double mini, double maxi, float ratio);
        // Checks exisence of an indiv index
        bool index_exist(string key); // Ok
        // Gets pointer to indv index
        individual_index * get_index(string key);
        // Gets pointer to indv index, same, quits on failure
        individual_index * get_index_safe(string key);
        // Adds a new indiv index to the population, erase any old one with same key
        void add_index(const string& key, const vector<double>& values,
                       const vector<indId>& idVect, bool sort_values = true);

        vector<double> weight_without_mother(vector<double> originWeight, int motherPop);


        /**/// Header implemented functions:
        bool is_ind(indId id);
        // Get pointer of individual from id (obsolete?)
        individual* get_pInd(indId id); // Ok
        // Get reference to individual from its id
        individual& get_rInd(indId id); // Ok
        // Get number of individuals in the index
        unsigned int get_nInd(){ return index.size(); } // Ok
        // Get abs positions of the population
        unsigned int get_abs_pos(){ return abs;}  // OK
        // Gets species of the population
        species * get_species(){ return t_species; } // Ok

        vector<double> get_pheno_var() { return var_pheno; }

        vector<int> get_nb_father_by_pop(){ return nb_father_by_pop; }
        vector<int> get_nb_mother_by_pop(){ return nb_mother_by_pop; }

        int abs_select(string origin, float threshold, float ratio);
        int purge_killBuffer();

        void set_new_population();


        /**/// Phenotypeand fitness calculation
        // Additive value for one trait
        void calc_additive_values(vector<double>& addVals, trait * pTrait); // Ok
        // Additive value for all traits, indexing
        void calc_add_multiple(vector<vector<double>>& values, vector<trait *>& trait); // Ok
        // Genetic value for one trait, placeholder!
        void calc_genetic_values(vector<double>& add_values, trait * pTrait); // Ok
        // Genetic value for multiple traits, placeholder!
        void calc_gen_multiple(vector<vector<double>>& values, vector<trait *>& traits); // Ok
        // Pheno values for one trait
        void calc_phenotypic_values(vector<double>&genetic_values, double E); // Ok
        // Pheno values for multiples traits, indexing
        void calc_phenotypic_values_epsi(vector<double>& genetic_values, vector<double>& epsi_values, double E, double Zm);

        void calc_pheno_multiple(vector<vector<double>>& values, vector<trait *>& traits, vector<vector<double>>& epsi); // Ok
        // Calc multitrait fitness for all idividuals

        void generate_height();
        //float getTheoricalHeight(int age, indId i);
        void calc_height();
	    void refresh_handicap();
	    void impact_height(string trait, float cutVal, float handicap);

        void calc_fitness_multi(vector<double>& fitValues,
                                const vector<vector<double>>& traits_values,
                                const vector<trait *>& traits); // Ok
        void calc_fitness_multi_vector(vector<double>& fitValues,
                                const vector<vector<double>>& traits_values,
                                const vector<trait *>& traits,
                                const vector<double>& omatrix);
        // Calls all functions above
        void calc_traits(); // Ok


    protected:
    private:
        // Main Index of individuals (only place owning the individuals)
        indMap index;
        indMap indexNext;

        vector<int> nb_father_by_pop; // for the given population, get the information about the origin of each father, for statistic analysis
        vector<int> nb_mother_by_pop; // for the given population, get the information about the origin of each mother, for statistic analysis

        // TODO/IDEA:
        // In order to avoid making a new vector<indId> everytime we need the list of
        // individuals in the container order (ex: to make individual_index)
        // We could keep this vector here, return reference vir method
        // and update if only if index has been modified using a switch
        vector<indId> ids;
        bool idsUpdate;

        vector<int> eventValue;
        vector<int> eventKilled;

        // Parent speciess
        species * t_species;
        // Number of individuals to reach at the end of the step
        unsigned int targetPop;
        // Demograpy parameters
        double growth;
        int cap;
        int age; // IN THIS EVENAGE MODEL, ALL INDS HAS SAME AGE INSIDE A POP
        // Dictionary on individual indexes
        unordered_map<string, individual_index*> individual_values;
        // A triggers that asserts validity of individual_indexes
        // bool trigger;
        unsigned int abs;
        set<indId> killedBuffer;

        // pheno related values
        vector<double> var_pheno;
};


/* Gets a pointer to individual from its unique id
 * Stops program if individual not found
 */

inline bool population::is_ind(indId id)
{
    //cout << "POP " << abs << "ID:" << id;
    //auto it = index.find(id);

    //if (it == index.end())
    if (index.count(id)==0)
    {
        //cout <<  "found 0 times" << endl;
        return false;
    } else {
        //cout <<  "found 1+ times" << endl;
        return true;
    }
}


inline individual * population::get_pInd(indId id)
{
    // Retrieves iterator to a pair of values via find()
    auto it = index.find(id);
    if (it == index.end())
    {
        cout << "GET_PIND fail for ind" << id << endl;


        fail("Unable to find individual (pInd)");
    }
    // Access to the pointer (pair<indId, individual*>)
    return it->second;
}



inline individual& population::get_rInd(indId id)
{
    // Retrives iterator to a pair of values via find()
    auto it = index.find(id);
    if (it == index.end())
    {
        cout << "GET_RIND fail for ind" << id << endl;
        fail("Unable to find individual (rInd)");
    }
    // Acess to the pointer (pair<indId, individual*>)
    return *(it->second);
}


#endif // POPULATION_H
