// ****************************************************************************
//
//          Aevol - An in silico experimental evolution platform
//
// ****************************************************************************
//
// Copyright: See the AUTHORS file provided with the package or <www.aevol.fr>
// Web: http://www.aevol.fr/
// E-mail: See <http://www.aevol.fr/contact/>
// Original Authors : Guillaume Beslon, Carole Knibbe, David Parsons
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// ****************************************************************************

#include <zlib.h>

#include <algorithm>

#include "AeTime.h"
#include "Tree.h"
#include "Individual.h"
#include "ExpSetup.h"
#include "utils.h"
#include "macros.h"

namespace aevol {

// =================================================================
//                    Definition of static attributes
// =================================================================
const int32_t Tree::NO_PARENT = -1;

// =================================================================
//                             Constructors
// =================================================================
Tree::Tree(int32_t nb_indivs, int64_t tree_step) {
  nb_indivs_ = nb_indivs;
  tree_step_ = tree_step;

  replics_ = new ReplicationReport** [tree_step_];

  for (int32_t time = 0 ; time < tree_step ; time++) {
    replics_[time] = new ReplicationReport* [nb_indivs_];
    for (int32_t num_indiv = 0 ;
         num_indiv < nb_indivs_ ;
         num_indiv++) {
      replics_[time][num_indiv] = new ReplicationReport();
    }
  }
}


/**
 *
 */
Tree::Tree(int32_t nb_indivs, int64_t tree_step, const char* tree_file_name) {
  nb_indivs_ = nb_indivs;
  tree_step_ = tree_step;

  gzFile tree_file = gzopen(tree_file_name, "r");
  if (tree_file == Z_NULL) {
    printf("ERROR : Could not read tree file %s\n", tree_file_name);
    exit(EXIT_FAILURE);
  }

  // Read nb_indivs and tree_step
  gzread(tree_file, &nb_indivs_, sizeof(nb_indivs_));
  gzread(tree_file, &tree_step_, sizeof(tree_step_));

  replics_ = new ReplicationReport** [tree_step_];

  //for (int64_t t = AeTime::time()-tree_step_+1 ; t <= AeTime::time() ; t++) {
  for (int64_t t = 0 ; t < tree_step_ ; t++) {
    replics_[t] = new ReplicationReport* [nb_indivs_];

    for (int32_t indiv_i = 0 ;
         indiv_i < nb_indivs_ ;
         indiv_i++) {
      // Retrieve a replication report
      ReplicationReport* replic_report = new ReplicationReport(tree_file);

      // Put it at its rightful position
      //printf("Replication Report %d\n",replic_report->id());

      replics_[t][replic_report->id()] = replic_report;
    }
  }
  gzclose(tree_file);
}




// =================================================================
//                             Destructors
// =================================================================
Tree::~Tree() noexcept {
  if (replics_ != NULL)  {
    for (int32_t i = 0 ; i < tree_step_ ; i++)
      if (replics_[i] != NULL) {
        for (int32_t j = 0 ; j < nb_indivs_ ; j++)
          delete replics_[i][j];
        delete [] replics_[i];
      }
    delete [] replics_;
  }
}

// =================================================================
//                            Public Methods
// =================================================================
    ReplicationReport** Tree::reports(int64_t t) const {
        return replics_[mod(t - 1, tree_step_)];
    }

    ReplicationReport* Tree::report_by_index(int64_t t, int32_t index) const {
        // printf("Loading from %d Indiv %d (%d) : %p\n",mod(t - 1, tree_step_),
        //         index,replics_[mod(t - 1, tree_step_)][index]->id(),replics_[mod(t - 1, tree_step_)][index]);
        return replics_[mod(t - 1, tree_step_)][index];
    }

    void Tree::write_to_tree_file(const char* tree_file_name) {
        gzFile tree_file = gzopen( tree_file_name, "w" );

        // Write nb_indivs and tree_step
        int32_t nb_indivs = nb_indivs_;
        gzwrite(tree_file, &nb_indivs, sizeof(nb_indivs));
        gzwrite(tree_file, &tree_step_, sizeof(tree_step_));

        // Write the replication reports
        for (int64_t t = 0 ; t < tree_step_ ; t++)
            for (int32_t indiv_i = 0 ; indiv_i < nb_indivs_ ; indiv_i++) {
                assert(replics_[t][indiv_i] != NULL);
                replics_[t][indiv_i]->write_to_tree_file(tree_file);

            }

        gzclose(tree_file);
    }

void Tree::update_new_indiv(const Individual* new_indiv,
                            const Individual* parent,
                            int new_indiv_id,
                            int parent_id) {
  replics_[mod(AeTime::time() - 1, tree_step_)][new_indiv_id]->init(new_indiv, parent, new_indiv_id, parent_id);
}

void Tree::update_end_replication(const Individual* child, int new_indiv_id) {
  replics_[mod(AeTime::time() - 1, tree_step_)][new_indiv_id]->signal_end_of_replication(child);
}



// =================================================================
//                  Non-inline accessors' definition
// =================================================================

// =================================================================
//                           Protected Methods
// =================================================================
} // namespace aevol
