#include "main.h"


// TODO: check if no bias in selfing: can indiv pick itself?
// TODO: while we spawn idividuals: new inds have been added to the population
//       Can they be used as parents? MAJOR ISSSUE !
//       => Not as long as index & demogrphy are not updated?


/* * * * * * * * * * * REMINDER * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * The program is supposed to go as the following:
 * - Load lua script
 * - Load species, trait and individuals
 *
 * Then starts the main loop,
 *    Iterate on cells,
 *        Iterate on species (pops in cell)
 *            Apply events to individuals accordig to the scheduler
 *
 *
 * Clock object: iterates tics
 * Calculates events at each tick (or at the beginning of the program?)
 * Set a priority list in the script to determine the order of events occurring a the same tick
 *
 * If nothing happens, no need to update each individual (age), fitness
 * Age should be updatable since the last event (use birth date and not age?=
 * Should I divide populations in age classes?
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


// Default seeds
double S1 = 10;
double S2 = 11;

//bool verbose = true;
bool checkRandom = false;
bool saveTime = true;

indId individual::counter_created(0);
indId individual::counter_alive(0);


void randomValues()
{
    // check random randomness
    cout << "r: " ;
    for (int i=0;i<5;i++)
    {
        //cout << random() << " ; ";
    }
    cout << endl;
    cout << "g: " ;
    for (int i=0;i<5;i++)
    {
        cout << gasdev() << " ; ";
    }
    cout << endl;

    cout << "R: " ;
    for (int i=0;i<5;i++)
    {
        cout << Random() << " ; ";
    }
    cout << endl;
}

int main()
{
    vector<int> secondsByTick;
    time_t tInit, t0, t1;
    tInit = 0;
    t0 = 0;
    t1 = 0;

    if (saveTime)
    {
        tInit = time(NULL);
    }

    // Lua main script name declaration
    string file = "luaSrc/main.lua";

    // New lua state (for now the only state?)
    cout << "\n\nStrating Lua scripts.."  << endl;
    lua_wrapper state(file);
    cout << "End of Lua initialization, Starting C++\n" << endl;


    //ProfilerStart("/tmp/profile");

    cout << "Loading parameters from config.lua" << endl;
    // Open config.lua table
    S1 = 0;
    S2 = 0;
    state.open_global_table("configSeed"); // <- ERROR: Open global table, getglobal got wrong type or non existant value for seed
    //Get seeds for random generator
    S1 = state.get_field<int>("seed1");
    S2 = state.get_field<int>("seed2");
    state.close_table();

    //cout << "GET SEEDS" << S1 << " " << S2 << endl;

    state.open_global_table("config");
    //Eref (when plasticity only)
    //int Eref = state.get_field<int>("Eref");
    //cout << "ER" << Eref << endl;
    // Init random function
    Random(S1);

    if (checkRandom)
    {
        randomValues();
    }


    // Get map size parameters
    int mw = state.get_field<int>("mapLength");
    int ml = state.get_field<int>("mapWidth");


    // Clock parameters
    double simLength = state.get_field<double>("simLength");
    double timestep = state.get_field<double>("timeStep");

    double savestep = state.get_field<double>("saveStep");
    bool isRegression = state.get_field<bool>("isRegression");
    unsigned int cellRef = state.get_field<int>("cellRef");

    cout << "CELL REF FOR GENEFLUX OUTPUTS:" << cellRef << endl;


    // Output management
    string outputDir = state.get_field<string>("outputDir");
    cout << "OUT:" << outputDir << endl;

    // Close table
    state.close_table();
    cout << "End of loading." << endl;


    /*********************** Map Creation (with an lenght) ********************/
    cout << "Generating Map"  << endl;
    landscape myMap(mw, ml);//, Eref);


    /************************  Loading species: *******************************/
    cout << "Loading Species"  << endl;
    vector<species> speciesVect;
    load_species(state, "speciesTable", speciesVect);

    // visualisation of chromosom map ?

    species * currSpe = NULL;
    for (unsigned int i = 0; i < speciesVect.size(); ++i)
    {
            currSpe = &(speciesVect[i]);
            currSpe->checkChromoStruct(true);

            //initiate empty pops
            myMap.addIsEmptyForSpe(currSpe);

            currSpe->update_SelInt_matrix(myMap);
    }

    /********************* Loading intitial populations ***********************/
    cout << "Loading Individuals"  << endl;
    load_populations(state, "populations", speciesVect, myMap);

    for (unsigned int i = 0; i < speciesVect.size(); ++i)
    {
            currSpe = &(speciesVect[i]);
            myMap.refreshIsEmptyForSpe(currSpe);
    }

    /************************* Init clock *************************************/
    cout << "Init Clock"  << endl;
    mClock myClock(timestep, simLength);

    cout << "End of initialization\n\n" << endl;


    cell * currCell = NULL;
    population * currPop  = NULL;

    valarray<double> sfit; //temporary fitnesse storage
    vector<trait *> vTraits;
    //individual_index indInd;

    //cout << "Checkpoint Alpha"  << endl;
    //Output Useful infos that are not changed through simulation
    // TODO : better function (filename as arg)
    output_ploidy(speciesVect, outputDir);
    //cout << "Checkpoint Beta"  << endl;


    if (saveTime)
    {
        t1 = time(NULL);
        secondsByTick.push_back(difftime(t1, tInit));
    }
    cout << "INITIALIZATION TIME:" << difftime(t1, tInit) << endl;
    cout << "Main loop" << endl;

    /**************************************************************************/
    /****************************    MAIN LOOP    *****************************/
    /**************************************************************************/
    while (myClock.inc_step())
    {
        auto start = std::chrono::high_resolution_clock::now();
        if (saveTime)
        {
            t0 = t1;
        }
        cout << "\n\n### Clock " << myClock.get_time() << " ###\n" << endl;
        cout << "Alive ind: " << individual::counter_alive << endl;
        cout << "Created ind: " << individual::counter_created << endl;
        
        /*********************   Genetic outputs    ***************************/
	 if (( myClock.get_time()==1)
		|| (!isRegression && myClock.get_time()%(int)savestep == 0)
		|| (isRegression && (myClock.get_time()==250 || myClock.get_time()==1000))
	   )
        {
            cout << "Genet out" << endl;
            for (unsigned int i = 0; i < speciesVect.size(); i++)
            {
		        //cout << "GO" << i << endl;
                currSpe = &(speciesVect[i]);
                output_genetic(myMap, currSpe, myClock, outputDir);
                output_geneflow(myMap, currSpe, myClock, outputDir, cellRef);
                currSpe->purgeMigrations();
            }
        }
        
        auto stop = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double>elapsed = stop - start;
        //cout << "S1" << elapsed.count() <<endl;
        start = stop;

        /**************** Update ages / traits / fitness **********************/
        cout << "Update Ages/trait/fitness" << endl;

        //Iteration on species
        for (unsigned int i = 0; i < speciesVect.size(); ++i)
        {
            myMap.refreshIsEmptyForSpe(currSpe);
            vector<bool> isPopEmpty = myMap.isPopEmptyInSpe(currSpe);

            for(unsigned int cellId = 0; cellId < myMap.max_size(); ++cellId)
            {
                currCell = myMap.get_cell(cellId);
                currPop = currCell->get_pop(currSpe);
                if (!isPopEmpty[cellId])
                {
                    currPop->calc_traits();
                }
                currPop->make_ages_index(myClock);
            }
        }

        stop = std::chrono::high_resolution_clock::now();
        elapsed = stop - start;
        ////cout << "S2" << elapsed.count() <<endl;
        start = stop;

        /*****************  Making group index     ****************************/
        cout << "Make groupe index" << endl;
        for (unsigned int i = 0; i < speciesVect.size(); ++i)
        {
            currSpe = &(speciesVect[i]);

            if (currSpe->is_assort_strategy("unmatchable"))
            {
                // clean unmatchable vector
                currSpe->clear_unmatchable();
            }

            currSpe->calc_demo(myMap);

            vTraits = currSpe->get_traits();
            for(trait * currTrait : vTraits)
            {
               currSpe->make_gIndex(myMap, currTrait->get_name() + "-pheno", 1); // SEGFAULT WHEN POP IS EMPTY
            }
            currSpe->make_gIndex(myMap, "fitness", 1);
            currSpe->make_gIndex(myMap, "age", 1); // WHy?

            //Update assortMating threshold
            if(currSpe->get_assortMating() != "")
            {
                for(trait * currTrait : vTraits)
                {
                   currSpe->make_gIndex(myMap, currTrait->get_name() + "-phenoWithE", 1); // SEGFAULT WHEN POP IS EMPTY
                }
                currSpe->update_assort_threshold(myMap);
            }
        }

        stop = std::chrono::high_resolution_clock::now();
        elapsed = stop - start;
        ////cout << "S3" << elapsed.count() <<endl;
        start = stop;

        /*************************   Outputs values    ************************/
        if (( myClock.get_time()==1)
		|| (!isRegression && myClock.get_time()%(int)savestep == 0)
		|| (isRegression && (myClock.get_time()==250 || myClock.get_time()==1000))
	   )
        {
            cout << "Outputs" << endl;
            for (species& s : speciesVect)
            {
                currSpe = &s;
                //Print age and fitness
                output_value(myMap, currSpe, myClock, "age", outputDir);
                output_value(myMap, currSpe, myClock, "fitness", outputDir);


                vTraits = currSpe->get_traits();
                for(trait* t : vTraits)
                {
                   output_value(myMap,  currSpe, myClock, t->get_name() + "-add", outputDir);
                   output_value(myMap,  currSpe, myClock, t->get_name() + "-pheno", outputDir);
                   if(currSpe->get_assortMating() != "")
                   {
                       output_value(myMap,  currSpe, myClock, t->get_name() + "-phenoWithE", outputDir);
                   }
                   output_value(myMap,  currSpe, myClock, t->get_name() + "-epsi", outputDir);

                   output_addByLocus(myMap,  currSpe, myClock, t, outputDir);

                   // check if several ltypes, generates separated ltypes add output files if more than on
                   if (t->get_nLtype() > 1)
                   {
                        output_addByLtype(myMap,  currSpe, myClock, t, outputDir);
                   }
                }
            }
        }

        stop = std::chrono::high_resolution_clock::now();
        elapsed = stop - start;
        //cout << "S4" << elapsed.count() <<endl;
        start = stop;

        /*******************  Refresh environment *****************************/
        int actualT = myClock.get_time();
        for (species& s : speciesVect)
        {
            currSpe = &s;
            // REFRESH FLUXMAT
            currSpe->adapt_fluxMat(actualT);

            vTraits = currSpe->get_traits();

            bool isChangeSelint = false;
            // REFRESH E AND ZOPT
            for(trait* t : vTraits)
            {
                if (t->isKeyInTemporalE(actualT))
                {
                    if (actualT > 1)
                    {
                        cout << "CHANGE ENVIRONMENT FOR TRAIT " << t-> get_name() << endl;
                    }
                    t->set_E(actualT);
                }

                if (t->isKeyInTemporalZopt(actualT))
                {
                    if (actualT > 1)
                    {
                        cout << "CHANGE ZOPT FOR TRAIT " << t-> get_name() << endl;
                    }
                    t->set_Zopt(actualT);
                }

                if (t->isKeyInTemporalSelInt(actualT))
                {
                    if (actualT > 1)
                    {
                        cout << "CHANGE SELINT FOR TRAIT " << t-> get_name() << endl;
                    }
                    t->set_SelInt(actualT);
                    isChangeSelint = true;
                }
            }
            if (isChangeSelint)
            {
                currSpe->update_SelInt_matrix(myMap);
            }
        }

        stop = std::chrono::high_resolution_clock::now();
        elapsed = stop - start;
        //cout << "S5" << elapsed.count() <<endl;
        start = stop;

        /*******************  Demography & Repro  *****************************/
        cout << "Demography" << endl;

        //vector<vector<int>> curPops(speciesVect.size(), vector<int>(myMap.max_size()));
        for(unsigned int cellId = 0; cellId < myMap.max_size(); ++cellId)
        {
            currCell = myMap.get_cell(cellId);

            //Iteration on species
            for (unsigned int i = 0; i < speciesVect.size(); ++i)
            {
                currSpe = &(speciesVect[i]);
                currPop = currCell->get_pop(currSpe);
                int newP = currPop->demography_new(); //Calculate demography
                if (newP == 0)
                {
                    myMap.setIsEmpty(currSpe, cellId, true);
                }
                else
                {
                    myMap.setIsEmpty(currSpe, cellId, false);
                }
                currPop->adjust(myMap, myClock); //Resolves demography
            }
        }

        // need refresh of demo ?
        for (unsigned int i = 0; i < speciesVect.size(); ++i)
        {
            currSpe = &(speciesVect[i]);
            currSpe->calc_demo(myMap);
        }

        stop = std::chrono::high_resolution_clock::now();
        elapsed = stop - start;
        //cout << "S6" << elapsed.count() <<endl;
        start = stop;

        /*********************** Kill above threshold *************************/
        cout << "Kill" << endl;
        for(unsigned int cellId = 0; cellId < myMap.max_size(); ++cellId)
        {
            currCell = myMap.get_cell(cellId);

            //Iteration on species
            for (unsigned int i = 0; i < speciesVect.size(); ++i)
            {
                currSpe = &(speciesVect[i]);
                if (!myMap.getIsEmpty(currSpe, cellId))
                {
                    //cout << currSpe << "/" << cellId << endl;
                    currPop = currCell->get_pop(currSpe);
                    currPop->kill_range("age", 1.0, 3.0);
                }
            }

        }
        if (saveTime)
        {
            t1 = time(NULL);
            secondsByTick.push_back(difftime(t1, t0) );
        }
        stop = std::chrono::high_resolution_clock::now();
        elapsed = stop - start;
        //cout << "S7" << elapsed.count() << endl;
        start = stop;
    }

    //End time loop
    cout << "End of simulation." << endl;
    if (saveTime)
    {
        output_time(secondsByTick, outputDir);
    }

    //ProfilerStop();
    // DELETE ALL OBJECTS
    // pops
    for(unsigned int cellId = 0; cellId < myMap.max_size(); ++cellId)
    {
        currCell = myMap.get_cell(cellId);

        //Iteration on species
        for (unsigned int i = 0; i < speciesVect.size(); ++i)
        {
            currSpe = &(speciesVect[i]);
            currPop = currCell->get_pop(currSpe);
            delete currPop;
       }
    }

    return 0;
}
