// ****************************************************************************
//
//          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/>.
//
// ****************************************************************************

#ifndef AEVOL_DNA_H
#define AEVOL_DNA_H


#include <cstdint>
#include <list>
#include <memory>

#include "ae_types.h"
#include "MutationReport.h"
#include "MutationMetrics.h"
#include "Strand.h"
#include "utils.h"
#include "macros.h"

namespace aevol {

#define BLOCK_SIZE INT32_C(1024)
class AnnotatedChromosome;
class DnaFactory;
class MutationEvent;

class Dna final {
  friend class DnaFactory;

 public:
  using size_type = aevol::dna_size_type;

 protected:
  Dna() = delete;
  Dna(const Dna&) = delete;
  Dna(Dna&&) = delete;
  Dna& operator=(const Dna&) = delete;
  Dna& operator=(Dna&&) = delete;

  Dna(size_type length);
  ~Dna();

 public:
  auto operator[](std::size_t idx) const { return data_[idx]; }

  static Dna* make_dna(size_t request_size);
  static Dna* make_from_sequence(const std::string& seq);
  static Dna* make_recombinant(const Dna& dna1, Dna::size_type pos1, const Dna& dna2, Dna::size_type pos2);
  static void release(Dna*&& dna);

  void reserve(size_type new_capacity);
  void reset_sequence(const char* seq, size_type len);

    void reset_length(size_type length);
    void set_chrsm(AnnotatedChromosome* ann_chrsm);

  char get_lead(size_type pos) const;
  char get_lag(size_type pos) const;

    auto nb_block() const { return nb_blocks_; }

    void undergo_mutation(const MutationReport& mut);

    auto apply_mutation(const MutationEvent& mutation_to_apply) -> std::unique_ptr<MutationReport>;
    auto apply_mutations(const std::list<std::unique_ptr<MutationEvent>>& mutations_to_apply)
        -> std::list<std::unique_ptr<MutationReport>>;

    #ifdef BASE_2
    auto do_switch(size_type pos) -> std::unique_ptr<MutationReport>;
    #else
    auto do_switch(size_type pos, char new_base) -> std::unique_ptr<MutationReport>;
    #endif

    auto do_small_insertion(size_type pos, int16_t nb_insert, const char* seq) -> std::unique_ptr<MutationReport>;
    auto do_small_deletion(size_type pos, int16_t nb_del) -> std::unique_ptr<MutationReport>;
    auto do_duplication(size_type pos_1, size_type pos_2, size_type pos_3) -> std::unique_ptr<MutationReport>;
    auto do_inversion(size_type pos_1, size_type pos_2) -> std::unique_ptr<MutationReport>;
    auto do_translocation(size_type pos_1, size_type pos_2, size_type pos_3, size_type pos_4, bool invert)
        -> std::unique_ptr<MutationReport>;
    auto do_deletion(size_type pos_1, size_type pos_2) -> std::unique_ptr<MutationReport>;

    void remove(size_type first, size_type last);
    void insert(size_type pos, const char* seq, size_type seq_length);
    void replace(size_type pos, char* seq, size_type seq_length);

    void reset_stat();

    const char* data() const {return data_;}
    std::string subseq(size_type first, size_type count, Strand strand) const;
    auto length() const {
      return length_;
    }

    auto to_char() const -> const char* {
      return data_;
    }

    static int32_t nb_required_blocks(size_type length);

    void ABCDE_to_ADCBE(size_type pos_B, size_type pos_C, size_type pos_D, size_type pos_E);
    void ABCDE_to_ADBpCpE(size_type pos_B, size_type pos_C, size_type pos_D, size_type pos_E);
    void ABCDE_to_ACpDpBE(size_type pos_B, size_type pos_C, size_type pos_D, size_type pos_E);

    std::list<MutationEvent*> mutation_list;

    char* data_;
    size_type length_;

    MutationMetrics mutation_metrics_;

    AnnotatedChromosome* ann_chrsm_ = nullptr; // Raw pointer acceptable here because this DM is but a loop-back to the
                                               // object owning the current one.

 private:
  int32_t nb_blocks_;
};

std::ostream& operator<<(std::ostream&, const Dna&);

inline int32_t Dna::nb_required_blocks(size_type length) {
  return length/BLOCK_SIZE + 1;
}

} // namespace aevol

#endif // AEVOL_DNA_H
