// ****************************************************************************
//
//          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_PROMOTERLIST_H_
#define AEVOL_PROMOTERLIST_H_

#include <array>
#include <list>

#include "Dna.h"
#include "Promoter.h"
#include "utility.h"

namespace aevol {

class PromoterList {
 public:
  using SingleStranded = std::list<Promoter>;
  using DoubleStranded = std::array<SingleStranded, 2>;

  PromoterList();
  PromoterList(const PromoterList&)            = delete;
  PromoterList(PromoterList&&)                 = default;
  PromoterList& operator=(const PromoterList&) = delete;
  PromoterList& operator=(PromoterList&&)      = default;
  virtual ~PromoterList()                      = default;

  auto operator[](Strand strand) -> SingleStranded&;
  auto operator[](Strand strand) const -> const SingleStranded&;

  auto at(size_t idx) const -> const Promoter*;
  auto at(size_t idx) -> Promoter*;
  void promoter_add(Promoter&& prom);
  int promoter_count() const;

  void remove_promoters_around(Dna::size_type pos_1, Dna::size_type dna_len);
  void remove_promoters_around(Dna::size_type pos_1, Dna::size_type pos_2, Dna::size_type dna_len);
  void remove_all_promoters();

  void look_for_new_promoters_around(const Dna& dna, Dna::size_type pos_1, Dna::size_type pos_2);
  void look_for_new_promoters_around(const Dna& dna, Dna::size_type pos);

  void locate_promoters(const Dna& dna);

  void move_all_promoters_after(Dna::size_type pos, Dna::size_type delta_pos, Dna::size_type dna_len);

  PromoterList duplicate_promoters_included_in(Dna::size_type pos_1,
                                               Dna::size_type pos_2,
                                               Dna::size_type dna_len) const;
  PromoterList extract_promoters_included_in(Dna::size_type pos_1, Dna::size_type pos_2);
  void insert_promoters(PromoterList&& promoters_to_insert);

  void invert_promoters_included_in(Dna::size_type pos1, Dna::size_type pos2);

  void shift_promoters(Dna::size_type delta_pos, Dna::size_type seq_length);
  void invert_promoters(Dna::size_type pos1, Dna::size_type pos2);

  void remove_leading_promoters_starting_between(Dna::size_type pos_1, Dna::size_type pos_2);
  void remove_leading_promoters_starting_after(Dna::size_type pos);
  void remove_leading_promoters_starting_before(Dna::size_type pos);

  void remove_lagging_promoters_starting_between(Dna::size_type pos_1, Dna::size_type pos_2, Dna::size_type dna_len);
  void remove_lagging_promoters_starting_after(Dna::size_type pos);
  void remove_lagging_promoters_starting_before(Dna::size_type pos);

  void move_all_leading_promoters_after(Dna::size_type pos, Dna::size_type delta_pos, Dna::size_type dna_len);
  void move_all_lagging_promoters_after(Dna::size_type pos, Dna::size_type delta_pos, Dna::size_type dna_len);

  void look_for_new_leading_promoters_starting_between(const Dna& dna, Dna::size_type pos_1, Dna::size_type pos_2);
  void look_for_new_leading_promoters_starting_after(const Dna& dna, Dna::size_type pos);
  void look_for_new_leading_promoters_starting_before(const Dna& dna, Dna::size_type pos);

  void look_for_new_lagging_promoters_starting_between(const Dna& dna, Dna::size_type pos_1, Dna::size_type pos_2);
  void look_for_new_lagging_promoters_starting_after(const Dna& dna, Dna::size_type pos);
  void look_for_new_lagging_promoters_starting_before(const Dna& dna, Dna::size_type pos);

  PromoterList promoters_included_in(Dna::size_type pos_1, Dna::size_type pos_2, Dna::size_type dna_len) const;

  void extract_leading_promoters_starting_between(Dna::size_type pos_1,
                                                  Dna::size_type pos_2,
                                                  SingleStranded& extracted_promoters);

  void extract_lagging_promoters_starting_between(Dna::size_type pos_1,
                                                  Dna::size_type pos_2,
                                                  SingleStranded& extracted_promoters);

 protected:
  enum class Position : std::uint8_t {
    BEFORE,
    BETWEEN,
    AFTER
  };

  inline SingleStranded& single_stranded_list(Strand strand);
  inline const SingleStranded& single_stranded_list(Strand strand) const;

  void lst_promoters(Strand strand,
                     Position before_after_btw,  // with regard to the strand's reading direction
                     Dna::size_type pos1,
                     Dna::size_type pos2,
                     SingleStranded& motif_list) const;

  DoubleStranded double_stranded_list_ = {{{}, {}}};
};

PromoterList::SingleStranded& PromoterList::single_stranded_list(Strand strand) {
  return double_stranded_list_[std::to_underlying(strand)];
}

const PromoterList::SingleStranded& PromoterList::single_stranded_list(Strand strand) const {
  return double_stranded_list_[std::to_underlying(strand)];
}

inline auto PromoterList::operator[](Strand strand) -> PromoterList::SingleStranded&{
  return single_stranded_list(strand);
}

inline auto PromoterList::operator[](Strand strand) const -> const PromoterList::SingleStranded&{
  return single_stranded_list(strand);
}

}  // namespace aevol

#endif  // AEVOL_PROMOTERLIST_H_
