#include "../include/individual.h"

// Main function to generate individuals from two parents
individual::individual(individual& mother, individual& father,
                       mClock &m_clock, species* spe)  : id (counter_created)
{
    // Reserve size of the new genome
    genome.reserve(spe->get_gSize());
    vector<int> chrSplit = spe->get_chromoSplit();
    // Get ref of both parents genomes
    vector<int>& genMother = mother.get_genome_ref();
    vector<int>& genFather = father.get_genome_ref();
    valarray<bool>& maskMother = spe->get_maskFemale();

    int switchMother = 0, switchFather = 0, genPos = 0, plo = 0, nAll = 0;
    double crossRate = 0.0, mutRate = 0.0;

    // The porcess of creating a new individual is done per locus
    for(unsigned int loc = 0; loc < spe->get_nLoci(); ++loc)
    {

        plo = spe->get_ploidy(loc); // Ploidy at this locus
        genPos = spe->get_locIndex(loc); // Starting position of the locus on the genome
        nAll = spe->get_nAlleles(loc); // Number of alleles at this locus
        mutRate = spe->get_mutRate(loc);
        // Haploid locus: no crossing over and inheritence from only one parent
        if(plo == 1)
        {
            if(maskMother[genPos]) genome.push_back(genMother[genPos]);
            else genome.push_back(genFather[genPos]);
            //Mutation of the locus
            if(Random() < mutRate) mutate_pos(nAll, genPos);
        }
        // Diploid locus:
        else if(plo == 2)
        {
            // Crossing over reset at beginning <of each chromosome> or if going from ploidy 1 to 2
            bool isChromoBegin = false;
            for (int c=0;c < chrSplit.size(); c++) {
                if (loc == chrSplit[c]) {
                    isChromoBegin = true;
                }
            }
            if(isChromoBegin or spe->get_ploidy(loc - 1) == 1)
            {
                switchMother = floor(Random() * 2); // Randomly 0 or 1
                switchFather = floor(Random() * 2);
            }
            // Going from diploid to diploid, random change of crossing state
            else
            {
                // We change crossing over state (0 => 1 or 1 => 0)
                crossRate = spe->get_crossRate(loc);
                if(Random() < crossRate) switchMother = (switchMother + 1) % 2;
                if(Random() < crossRate) switchFather = (switchFather + 1) % 2;
            }
            // We create the first copy of the locus
            if(maskMother[genPos])
                genome.push_back(genMother[genPos + switchMother]);
            else
                genome.push_back(genFather[genPos + switchFather]);
            // We create the second copy of the locus
            if(maskMother[genPos + 1])
                genome.push_back(genMother[genPos + (1 + switchMother) % 2 ]);
            else
                genome.push_back(genFather[genPos + (1 + switchFather) % 2 ]);
            //Mutation of the locus
            if(Random() < mutRate) mutate_pos(nAll, genPos);
            if(Random() < mutRate) mutate_pos(nAll, genPos + 1);
        }
        else { cout << "Ploidy >  2 not currently supported" << endl; exit(0); }
    }
    // Other stuff
    age = 0;
    birth_date = m_clock.get_time();
    counter_created++;
    counter_alive++;
}




individual::individual(int p_age, vector<int> p_genome) : id (counter_created) //id is const
{
    birth_date = 0;
    age = 1;
    genome = p_genome;
    counter_created++;
    counter_alive++;
}



individual::~individual()
{
    counter_alive--;
}





int individual::age_update(mClock &m_clock, species * pSpe)
{
    unsigned int currStep = m_clock.get_step();
    age = currStep-birth_date;
    if(age >= pSpe->get_max_age()) return -1;

    return age;
}


void individual::print_all()
{
    cout << "- " << this << " , age : " << age << ", " << genome.size() << " : ";
    for(unsigned int i = 0; i < genome.size(); i++)
        cout << genome[i] << " ";
    cout << ";" << endl;
    return;
}


valarray<int> individual::get_subGenome(valarray<bool> * mask)
{
    valarray<int> temp (genome.data(), genome.size()); //Remove and change genome type

    return temp[*mask];
}


// Copy generation
vector<int> individual::get_genome()
{
    vector<int> temp = genome;
    return temp;
}

valarray<int> individual::get_genome_va()
{
    valarray<int> temp(genome.data(), genome.size());
    return temp;
}


// Draws a new allele from [0, nAll[ to replace the current one at position "pos"
// Prevents from drawing the same
void individual::mutate_pos(int nAll, int pos)
{
    // With -1, we draw in [0, (nAll-1)[, thus we can avoid picking
    // the current value again by shifting all results >= current value by one
    int draw = floor(Random() * (nAll-1));
    if(draw >= genome[pos]) draw ++;
    genome[pos] = draw;
}


void individual::mutate(species * pSpecie)
{
    double mutRate;
    int nloc = pSpecie->get_nLoci();
    int start;
    int length;
    int nAll;
    double draw;

    //locus loop
    for(int locus = 0; locus < nloc; locus++)
    {
        start = pSpecie->get_locIndex(locus);
        length = pSpecie->get_ploidy(locus);
        mutRate = pSpecie->get_mutRate(locus);
        nAll = pSpecie->get_nAlleles(locus);

        //ploidy loop
        for(int genPos = start; genPos < length; genPos++)
        {
            if(Random() < mutRate)
            {
                draw = floor(Random() * (nAll-1));
                if(draw >= genome[genPos]) draw ++;
                genome[genPos] = draw;
            }
        }
    }
}


//Debug purpose
int individual::check_integrity(species * pSpe)
{
    int nLoc = pSpe->get_nLoci();
    int plo = 0;

    for(int i = 0; i < nLoc; i++)
    {
        plo = pSpe->get_ploidy(i);
        for(int j = 0; j < plo; j++)
        {
            // Check if the number of alleles at each locus is respected
            if(genome[pSpe->get_locIndex(i)+j] >= pSpe->get_nAlleles(i))
            {
                cout << pSpe->get_name() << " ERROR : nLoc = " << i 
                     << ", all = " << j << ",  : "<< pSpe->get_locIndex(i)+j
                     << "::" << genome[pSpe->get_locIndex(i)+j] << "=>"
                     << pSpe->get_nAlleles(i) << endl;
                return 0;
            }
        }
    }
    return 1;
}
