// ****************************************************************************
//
//          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 <cinttypes>
#include <list>
#include <cstdlib>
#include <cassert>

#include "DnaReplicationReport.h"
#include "InsertionHT.h"
#include "ReplacementHT.h"

#include "MutationReport.h"
#include "Duplication.h"
#include "Translocation.h"
#include "Inversion.h"
#include "Deletion.h"
#include "SmallDeletion.h"
#include "PointMutation.h"
#include "SmallInsertion.h"
#include "utility.h"

namespace aevol {

void DnaReplicationReport::clear() {
/*  for (auto it = ht_.begin(); it < ht_.end(); it++) {
    delete (*it);
  }*/
        ht_.clear();
        rearrangements_.clear();
        mutations_.clear();

    nb_mut_[std::to_underlying(MutationReport::Type::SWITCH)]  = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::S_INS)]   = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::S_DEL)]   = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::DUPL)]    = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::DEL)]     = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::TRANS)]   = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::INV)]     = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::INS_HT)]  = 0;
    nb_mut_[std::to_underlying(MutationReport::Type::REPL_HT)] = 0;
    }

DnaReplicationReport::DnaReplicationReport(const DnaReplicationReport& other) {
  for (auto& ht : other.ht_) {
    add_HT(ht->Clone());
  }

  for (auto& rear : other.rearrangements_) {
    add_rear(rear->Clone());
  }

  for (auto& pmut : other.mutations_) {
    add_local_mut(pmut->Clone());
  }
}

int32_t DnaReplicationReport::nb(MutationReport::Type t)  const {
  switch (t) {
    case MutationReport::Type::S_MUT:
      assert(mutations_.size() ==
             static_cast<size_t>(nb_mut_[std::to_underlying(MutationReport::Type::SWITCH)] +
                                 nb_mut_[std::to_underlying(MutationReport::Type::S_INS)] +
                                 nb_mut_[std::to_underlying(MutationReport::Type::S_DEL)]));
      return mutations_.size();
    case MutationReport::Type::REARR:
      assert(rearrangements_.size() ==
             static_cast<size_t>(nb_mut_[std::to_underlying(MutationReport::Type::DUPL)] +
                                 nb_mut_[std::to_underlying(MutationReport::Type::DEL)] +
                                 nb_mut_[std::to_underlying(MutationReport::Type::TRANS)] +
                                 nb_mut_[std::to_underlying(MutationReport::Type::INV)]));
      return rearrangements_.size();
    case MutationReport::Type::H_T:
      assert(ht_.size() ==
             static_cast<size_t>(nb_mut_[std::to_underlying(MutationReport::Type::INS_HT)] +
                                 nb_mut_[std::to_underlying(MutationReport::Type::REPL_HT)]));
      return ht_.size();
    case MutationReport::Type::INDEL:
      return nb_mut_[std::to_underlying(MutationReport::Type::S_INS)] +
             nb_mut_[std::to_underlying(MutationReport::Type::S_DEL)];
    default: // Simple mutation type.
      return nb_mut_[std::to_underlying(t)];
  };
}

void DnaReplicationReport::add_mut(std::unique_ptr<MutationReport> mut) {
  if (mut->is_local_mut()) {
    add_local_mut(std::move(mut));
  }
  else if (mut->is_rear()) {
    add_rear(std::move(mut));
  }
  else if (mut->is_ht()) {
    add_HT(std::move(mut));
  }
}

void DnaReplicationReport::add_local_mut(std::unique_ptr<MutationReport> mut) {
  assert(mut->is_local_mut());
  nb_mut_[std::to_underlying(mut->mut_type())]++;
  mutations_.push_back(std::move(mut));
}

void DnaReplicationReport::add_rear(std::unique_ptr<MutationReport> mut) {
  assert(mut->is_rear());
  nb_mut_[std::to_underlying(mut->mut_type())]++;
  rearrangements_.push_back(std::move(mut));
}

void DnaReplicationReport::add_HT(std::unique_ptr<MutationReport> mut) {
  assert(mut->is_ht());
  nb_mut_[std::to_underlying(mut->mut_type())]++;
  ht_.push_back(std::move(mut));
}


/// Useful when we inspect a tree file
/// because stats are not saved in the file.
void DnaReplicationReport::compute_stats()
{
  nb_mut_[std::to_underlying(MutationReport::Type::SWITCH)] = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::S_INS)]  = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::S_DEL)]  = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::DUPL)]   = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::DEL)]    = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::TRANS)]  = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::INV)]    = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::INS_HT)] = 0;
  nb_mut_[std::to_underlying(MutationReport::Type::REPL_HT)]= 0;

  for (const auto& ht : ht_) {
    assert(ht->mut_type() == MutationReport::Type::INS_HT or
           ht->mut_type() == MutationReport::Type::REPL_HT);
    nb_mut_[std::to_underlying(ht->mut_type())]++;
  }

  for (const auto& rear : rearrangements_) {
    assert(rear->mut_type() == MutationReport::Type::DUPL or
           rear->mut_type() == MutationReport::Type::DEL or
           rear->mut_type() == MutationReport::Type::TRANS or
           rear->mut_type() == MutationReport::Type::INV);
    nb_mut_[std::to_underlying(rear->mut_type())]++;
  }

  for (const auto& mut : mutations_) {
    assert(mut->mut_type() == MutationReport::Type::SWITCH or
           mut->mut_type() == MutationReport::Type::S_INS or
           mut->mut_type() == MutationReport::Type::S_DEL);
    nb_mut_[std::to_underlying(mut->mut_type())]++;
  }
}

void DnaReplicationReport::write_to_tree_file(gzFile tree_file) const {
  // Write the mutations and rearrangements undergone during replication
  // Store HT
  int32_t nb_HT = nb(MutationReport::Type::H_T);
  gzwrite(tree_file, &nb_HT, sizeof(nb_HT));
  for (const auto& ht : ht_) {
    switch(ht->mut_type()) {
      case MutationReport::Type::INS_HT:
        ht->save(tree_file);
        break;
      case MutationReport::Type::REPL_HT:
        ht->save(tree_file);
        break;
      default:
        ht->save(tree_file);
        break;
    }
  }


  // Store rearrangements
  int32_t nb_rears = nb(MutationReport::Type::REARR);
  gzwrite(tree_file, &nb_rears, sizeof(nb_rears));
  for (const auto& rear : rearrangements_) {
    switch(rear->mut_type()) {
      case MutationReport::Type::DUPL:
        rear->save(tree_file);
        break;
      case MutationReport::Type::DEL:
        rear->save(tree_file);
        break;
      case MutationReport::Type::TRANS:
        rear->save(tree_file);
        break;
      case MutationReport::Type::INV:
        rear->save(tree_file);
        break;
      default:
        rear->save(tree_file);
        break;
    }
  }

  // Store mutations
  int32_t nb_muts = nb(MutationReport::Type::S_MUT);
  gzwrite(tree_file, &nb_muts, sizeof(nb_muts));
  for (const auto& mut : mutations_)
    switch(mut->mut_type()) {
      case MutationReport::Type::SWITCH:
        mut->save(tree_file);
        break;
      case MutationReport::Type::S_DEL:
        mut->save(tree_file);
        break;
      case MutationReport::Type::S_INS:
        mut->save(tree_file);
        break;
      default:
        mut->save(tree_file);
        break;
    }
}

void DnaReplicationReport::read_from_tree_file(gzFile tree_file) {
  int32_t nb_rears, nb_muts, nb_HT;

  gzread(tree_file, &nb_HT, sizeof(nb_HT));
  for (int i = 0 ; i < nb_HT ; i++) {
    add_HT(MutationReport::Load(tree_file));
  }

  gzread(tree_file, &nb_rears, sizeof(nb_rears));
  for (int i = 0 ; i < nb_rears ; i++) {
    add_rear(MutationReport::Load(tree_file));
  }

  gzread(tree_file, &nb_muts, sizeof(nb_muts));
  for(int i = 0 ; i < nb_muts ; i++) {
    add_mut(MutationReport::Load(tree_file));
  }
}

} // namespace aevol
