// ****************************************************************************
//
//          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 "PromoterList.h"

#include <algorithm>

#include "ExpSetup.h"
#include "macros.h"
#include "Rna.h"
#include "utils.h"

namespace aevol {

PromoterList::PromoterList() {
}

auto PromoterList::at(size_t idx) const -> const Promoter* {
  if (idx >= single_stranded_list(Strand::LEADING).size()) {
    auto it = single_stranded_list(Strand::LAGGING).begin();
    std::advance(it, idx - single_stranded_list(Strand::LEADING).size());
    return &*(it);
  } else {
    auto it = single_stranded_list(Strand::LEADING).begin();
    std::advance(it,idx);
    return &*(it);
  }
}

auto PromoterList::at(size_t idx) -> Promoter* {
  return const_cast<Promoter*>(static_cast<const PromoterList&>(*this).at(idx));
}

void PromoterList::promoter_add(Promoter&& prom) {
  if (prom.strand() == Strand::LEADING) {
    single_stranded_list(Strand::LEADING).push_back(std::move(prom));
  } else {
    single_stranded_list(Strand::LAGGING).push_front(std::move(prom));
  }
}

int PromoterList::promoter_count() const {
  return single_stranded_list(Strand::LEADING).size()+
         single_stranded_list(Strand::LAGGING).size();
}

void PromoterList::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 {
  auto it_begin = single_stranded_list(strand).begin();
  auto it_end   = single_stranded_list(strand).end();

  if (before_after_btw != Position::BEFORE) {
    it_begin = find_if(single_stranded_list(strand).begin(),
                       single_stranded_list(strand).end(),
                       [pos1, strand](const Promoter& p) {
                         if (strand == Strand::LEADING) {
                           return p.pos() >= pos1;
          } else {
            return p.pos() < pos1;
          }
        });
  }

  if (before_after_btw != Position::AFTER) {
    it_end = find_if(it_begin, single_stranded_list(strand).end(), [pos2, strand](const Promoter& p) {
      if (strand == Strand::LEADING) {
        return p.pos() >= pos2;
      } else {
        return p.pos() < pos2;
      }
    });
  }

  SingleStranded promoters_1D;
  for (auto it = it_begin; it != it_end; it++) {
    promoters_1D.emplace_back(*it);
  }

  motif_list.splice(motif_list.end(), promoters_1D);
}

void PromoterList::remove_promoters_around(Dna::size_type pos_1, Dna::size_type dna_len) {
  if (dna_len >= PROM_SIZE) {
    remove_leading_promoters_starting_between(mod(pos_1 - PROM_SIZE + 1, dna_len), pos_1);

    remove_lagging_promoters_starting_between(pos_1, mod(pos_1 + PROM_SIZE - 1, dna_len), dna_len);
  }
  else {
    remove_all_promoters();
  }
}

void PromoterList::remove_promoters_around(Dna::size_type pos_1, Dna::size_type pos_2, Dna::size_type dna_len) {
  if (mod(pos_1 - pos_2, dna_len) >= PROM_SIZE) {
    remove_leading_promoters_starting_between(mod(pos_1 - PROM_SIZE + 1, dna_len), pos_2);

    remove_lagging_promoters_starting_between(pos_1, mod(pos_2 + PROM_SIZE - 1, dna_len), dna_len);
  } else {
    remove_all_promoters();
  }
}

void PromoterList::remove_all_promoters() {
  for (auto& strand: double_stranded_list_) {
    strand.clear();
  }
}

void PromoterList::look_for_new_promoters_around(const Dna& dna, Dna::size_type pos_1, Dna::size_type pos_2) {
  if (dna.length() >= PROM_SIZE) {
    look_for_new_leading_promoters_starting_between(dna, mod(pos_1 - PROM_SIZE + 1, dna.length()), pos_2);
    look_for_new_lagging_promoters_starting_between(dna, pos_1, mod(pos_2 + PROM_SIZE - 1, dna.length()));
  }
}

void PromoterList::look_for_new_promoters_around(const Dna& dna, Dna::size_type pos) {
  if (dna.length() >= PROM_SIZE) {
    look_for_new_leading_promoters_starting_between(dna, mod(pos - PROM_SIZE + 1, dna.length()), pos);
    look_for_new_lagging_promoters_starting_between(dna, pos, mod(pos + PROM_SIZE - 1, dna.length()));
  }
}

void PromoterList::locate_promoters(const Dna& dna) {
  remove_all_promoters();

  if (dna.length() < PROM_SIZE) {
    return;
  }

  for (Dna::size_type i = 0 ; i < dna.length() ; ++i) {
    int8_t dist = Promoter::is_promoter_leading(dna, i);
    if (dist <= PROM_MAX_DIFF) {// dist takes the hamming distance of the sequence from the consensus
      single_stranded_list(Strand::LEADING).emplace_back(i, dist, Strand::LEADING);
    }

    dist = Promoter::is_promoter_lagging(dna, i);
    if (dist <= PROM_MAX_DIFF) {
      single_stranded_list(Strand::LAGGING).emplace_back(dna.length() - i - 1, dist, Strand::LAGGING);
    }
  }
}

void PromoterList::move_all_promoters_after(Dna::size_type pos, Dna::size_type delta_pos, Dna::size_type dna_len) {
  move_all_leading_promoters_after(pos, delta_pos, dna_len);
  move_all_lagging_promoters_after(pos, delta_pos, dna_len);
}

PromoterList PromoterList::duplicate_promoters_included_in(Dna::size_type pos_1,
                                                           Dna::size_type pos_2,
                                                           Dna::size_type dna_len) const {
  // NB: lagging promoter starting at pos2 excluded
  // 1) Copy promoters to be duplicated into a new list
  PromoterList duplicated_promoters = promoters_included_in(pos_1, pos_2, dna_len);

  // 2) Set promoters' position as their position on the duplicated segment
  duplicated_promoters.shift_promoters(-pos_1, dna_len);

  return duplicated_promoters;
}

PromoterList PromoterList::extract_promoters_included_in(Dna::size_type pos_1, Dna::size_type pos_2) {
  PromoterList extracted_promoters;

  if (pos_2 - pos_1 < PROM_SIZE) {
    return extracted_promoters;
  }

  extract_leading_promoters_starting_between(
      pos_1, pos_2 - PROM_SIZE + 1, extracted_promoters[Strand::LEADING]);

  extract_lagging_promoters_starting_between(
      pos_1 + PROM_SIZE - 1, pos_2, extracted_promoters[Strand::LAGGING]);

  return extracted_promoters;
}

void PromoterList::insert_promoters(PromoterList&& promoters_to_insert) {
  for (auto strand: {Strand::LEADING, Strand::LAGGING}) {
    if (promoters_to_insert[strand].empty()) {
      continue;
    }
    // Get to the right position in individual's list (first promoter after the inserted segment)
    auto from_pos = promoters_to_insert[strand].back().pos();

    auto insert_before = find_if(
        single_stranded_list(strand).cbegin(),
        single_stranded_list(strand).cend(),
        [from_pos, strand](const Promoter& r) {
          if (strand == Strand::LEADING) {
            return r.pos() >= from_pos;
          } else {
            return r.pos() < from_pos;
          }
        });

    // Insert the provided promoters in the list
    for (const auto& promoter_to_insert: promoters_to_insert[strand]) {
      single_stranded_list(strand).insert(insert_before, Promoter(promoter_to_insert));
    }
  }
}

void PromoterList::invert_promoters_included_in(Dna::size_type pos1, Dna::size_type pos2) {
  auto segment_length = pos2 - pos1;

  if (segment_length < PROM_SIZE) {
    return;
  }

  // 1) Extract the promoters completely included on the segment to be inverted
  PromoterList inverted_promoters = extract_promoters_included_in(pos1, pos2);

  // 2) Invert segment's promoters
  inverted_promoters.invert_promoters(pos1, pos2);

  // 3) Reinsert the inverted promoters
  insert_promoters(std::move(inverted_promoters));
}

void PromoterList::shift_promoters(Dna::size_type delta_pos, Dna::size_type seq_length) {
  for (auto& strand: {Strand::LEADING, Strand::LAGGING})
    for (auto& cur_prom: single_stranded_list(strand)) {
      cur_prom.set_pos(mod(cur_prom.pos() + delta_pos, seq_length));
      cur_prom.reset_rna();
    }
}

void PromoterList::invert_promoters(Dna::size_type pos1, Dna::size_type pos2) {
  // Exchange LEADING and LAGGING lists
  single_stranded_list(Strand::LEADING).swap(single_stranded_list(Strand::LAGGING));

  // Update the position and strand of each promoter to be inverted...
  for (auto strand: {Strand::LEADING, Strand::LAGGING})
    for (auto& cur_prom : single_stranded_list(strand)) {
      cur_prom.set_pos(pos1 + pos2 - cur_prom.pos() - 1);
      cur_prom.set_strand(strand);
      cur_prom.reset_rna();
    }
}

void PromoterList::remove_leading_promoters_starting_between(Dna::size_type pos_1, Dna::size_type pos_2) {
  if (pos_1 > pos_2) {
    remove_leading_promoters_starting_after(pos_1);
    remove_leading_promoters_starting_before(pos_2);
  } else {
    auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);

    // Delete promoters until we pass pos_2 (or we reach the end of the list)
    // STL Warning: don't erase the current iterator in the for-loop!
    auto init_loop = find_if(
        cur_strand_promoters.begin(), cur_strand_promoters.end(), [pos_1](Promoter& r) { return r.pos() >= pos_1; });

    auto range_end = init_loop;

    for (auto it = init_loop, nextit = it; it != cur_strand_promoters.end() and it->pos() < pos_2; it = nextit) {
      nextit = next(it);
      cur_strand_promoters.erase(it);
      range_end = nextit;
    }
  }
}

void PromoterList::remove_leading_promoters_starting_after(Dna::size_type pos) {
  auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);

  auto init_loop = find_if(cur_strand_promoters.begin(), cur_strand_promoters.end(),
                           [pos](Promoter& r) { return r.pos() >= pos; });
  auto init_loop2 = init_loop;
  if (init_loop2 != cur_strand_promoters.begin())
    init_loop2 = prev(init_loop2);
  for (auto it = init_loop,
            nextit = it;
       it != cur_strand_promoters.end();
       it = nextit) {
    nextit = next(it);
    cur_strand_promoters.erase(it);
  }
}

void PromoterList::remove_leading_promoters_starting_before(Dna::size_type pos) {
  auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);
  // Delete promoters until we reach pos (or we reach the end of the list)
  auto range_end = cur_strand_promoters.begin();
  for (auto it = cur_strand_promoters.begin(),
            nextit = it;
       it != cur_strand_promoters.end() and it->pos() < pos;
       it = nextit) {
    nextit = next(it);
    cur_strand_promoters.erase(it);
    range_end = nextit;
  }
}

void PromoterList::remove_lagging_promoters_starting_between(Dna::size_type pos_1,
                                                             Dna::size_type pos_2,
                                                             Dna::size_type dna_len) {
  if (pos_1 == dna_len) pos_1 = 0;
  if (pos_2 == 0) pos_2 = dna_len;
  if (pos_1 >
      pos_2) { // vld: that's a weird case... really do this? used from remove_promoters_around()
    remove_lagging_promoters_starting_after(pos_1);
    remove_lagging_promoters_starting_before(pos_2);
  }
  else {
    auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);
    // Delete promoters until we pass pos_1 (or we reach the end of the list)
    auto init_loop = find_if(
        cur_strand_promoters.begin(), cur_strand_promoters.end(),
                             [pos_2](Promoter& r) {
                               return r.pos() < pos_2;
                             });
    auto range_end = cur_strand_promoters.begin();

    for (auto it = init_loop,
              nextit = it;
         it != cur_strand_promoters.end() and it->pos() >= pos_1;
         it = nextit) {
      nextit = next(it);
      cur_strand_promoters.erase(it);
      range_end = nextit;
    }
  }
}

void PromoterList::remove_lagging_promoters_starting_after(Dna::size_type pos) {
  auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);
  // Delete promoters until we pass pos (or we reach the end of the list)
  auto range_end = cur_strand_promoters.begin();
  for (auto it = cur_strand_promoters.begin(),
            nextit = it;
       it != cur_strand_promoters.end() and it->pos() >= pos;
       it = nextit) {
    nextit = next(it);
    cur_strand_promoters.erase(it);
    range_end = nextit;
  }
}

void PromoterList::remove_lagging_promoters_starting_before(Dna::size_type pos) {
  auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);
  // Delete promoters until we reach pos (or we reach the end of the list)

  auto init_loop = find_if(cur_strand_promoters.begin(), cur_strand_promoters.end(),
                           [pos](Promoter& r) { return r.pos() < pos; });
  for (auto it = init_loop,
            nextit = it;
       it != cur_strand_promoters.end();
       it = nextit) {
    nextit = next(it);
    cur_strand_promoters.erase(it);
  }
}

void PromoterList::move_all_leading_promoters_after(Dna::size_type pos,
                                                    Dna::size_type delta_pos,
                                                    Dna::size_type dna_len) {
  auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);

  // Find first promoter after pos (included) if any
  auto first_prom_to_move = find_if(cur_strand_promoters.begin(), cur_strand_promoters.end(), [pos] (Promoter& r) { return r.pos() >= pos; });

  for (auto cur_prom = first_prom_to_move ; cur_prom != cur_strand_promoters.end() ; ++cur_prom) {
    cur_prom->set_pos(mod(cur_prom->pos() + delta_pos, dna_len));

    if (cur_prom->rna() != nullptr) {
      cur_prom->rna()->is_init_   = false;
      cur_prom->rna()->is_coding_ = false;
    }
  }
}

void PromoterList::move_all_lagging_promoters_after(Dna::size_type pos,
                                                    Dna::size_type delta_pos,
                                                    Dna::size_type dna_len) {
  auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);

  // Update cur_strand_promoters until we pass pos (or we reach the end of the list)
  for (auto cur_prom = cur_strand_promoters.begin() ; cur_prom != cur_strand_promoters.end() and cur_prom->pos() >= pos; ++cur_prom) {
    cur_prom->set_pos(mod(cur_prom->pos() + delta_pos, dna_len));
    if (cur_prom->rna() != nullptr) {
      cur_prom->rna()->is_init_   = false;
      cur_prom->rna()->is_coding_ = false;
    }
  }
}

void PromoterList::look_for_new_leading_promoters_starting_between(const Dna& dna,
                                                                   Dna::size_type pos_1,
                                                                   Dna::size_type pos_2) {
  if (pos_1 >= pos_2) {
    look_for_new_leading_promoters_starting_after(dna, pos_1);
    look_for_new_leading_promoters_starting_before(dna, pos_2);
    return;
  }
  int8_t dist; // Hamming distance of the sequence from the promoter consensus

  for (auto i = pos_1 ; i < pos_2 ; i++) {
    dist = Promoter::is_promoter_leading(dna, i);
    if (dist <= PROM_MAX_DIFF) // dist takes the hamming distance of the sequence from the consensus
    {

      // Look for the right place to insert the new promoter in the list
      auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);

      auto first = find_if(cur_strand_promoters.begin(), cur_strand_promoters.end(),
                           [i](Promoter& r) { return r.pos() >= i; });


      if (first == cur_strand_promoters.end() or first->pos() != i) {
        single_stranded_list(Strand::LEADING).emplace(first, i, dist, Strand::LEADING);
      }
    }
  }
}

void PromoterList::look_for_new_leading_promoters_starting_after(const Dna& dna, Dna::size_type pos) {
  // Hamming distance of the sequence from the promoter consensus
  int8_t dist;

  for (auto i = pos ; i < dna.length() ; i++) {
    dist = Promoter::is_promoter_leading(dna, i);
    if (dist <= PROM_MAX_DIFF) { // dist takes the hamming distance of the sequence from the consensus
      // Look for the right place to insert the new promoter in the list
      auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);
      auto first = find_if(cur_strand_promoters.begin(), cur_strand_promoters.end(),
                           [i](Promoter& r) {
                             return r.pos() >= i;
                           });

      if (first == cur_strand_promoters.end() or first->pos() != i) {
        single_stranded_list(Strand::LEADING).emplace(first, i, dist, Strand::LEADING);
      }
    }
  }
}

void PromoterList::look_for_new_leading_promoters_starting_before(const Dna& dna, Dna::size_type pos) {
  // Hamming distance of the sequence from the promoter consensus
  int8_t dist;

  auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);
  auto first = cur_strand_promoters.begin(); // TODO vld: should it not be reset at each loop step?

  for (decltype(pos) i = 0 ; i < pos ; ++i) {
    dist = Promoter::is_promoter_leading(dna, i);
    if (dist <= PROM_MAX_DIFF) {
      // Look for the right place to insert the new promoter in the list

      first = find_if(first, cur_strand_promoters.end(),
                      [i](Promoter& r) { return r.pos() >= i; });

      if (first == cur_strand_promoters.end() or first->pos() != i) {
        single_stranded_list(Strand::LEADING).emplace(first, i, dist, Strand::LEADING);

      }
    }
  }
}

void PromoterList::look_for_new_lagging_promoters_starting_between(const Dna& dna,
                                                                   Dna::size_type pos_1,
                                                                   Dna::size_type pos_2) {
  if (pos_1 >= pos_2) {
    look_for_new_lagging_promoters_starting_after(dna, pos_1);
    look_for_new_lagging_promoters_starting_before(dna, pos_2);
    return;
  }

  int8_t dist; // Hamming distance of the sequence from the promoter consensus
  for (auto i = pos_2 - 1 ; i >= pos_1 ; i--) {
    dist = Promoter::is_promoter_lagging(dna, i);
    if (dist <= PROM_MAX_DIFF) {
      // Look for the right place to insert the new promoter in the list
      auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);

      auto first = find_if(cur_strand_promoters.begin(), cur_strand_promoters.end(),
                           [i](Promoter& r) { return r.pos() <= i; });

      if (first == cur_strand_promoters.end() or first->pos() != i) {
        single_stranded_list(Strand::LAGGING).emplace(first, i, dist, Strand::LAGGING);
      }
    }
  }
}

void PromoterList::look_for_new_lagging_promoters_starting_after(const Dna& dna, Dna::size_type pos) {
  // Hamming distance of the sequence from the promoter consensus
  int8_t dist;
  auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);
  auto first = cur_strand_promoters.begin();

  for (auto i = dna.length() - 1 ; i >= pos ; i--) {
    dist = Promoter::is_promoter_lagging(dna, i);
    if (dist <= PROM_MAX_DIFF) {
      // Look for the right place to insert the new promoter in the list
      first = find_if(first, cur_strand_promoters.end(),
                      [i](Promoter& r) { return r.pos() <= i; });

      if (first == cur_strand_promoters.end() or first->pos() != i) {
        single_stranded_list(Strand::LAGGING).emplace(first, i, dist, Strand::LAGGING);
      }
    }
  }
}

void PromoterList::look_for_new_lagging_promoters_starting_before(const Dna& dna, Dna::size_type pos) {
  int8_t dist;

  auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);
  auto first = cur_strand_promoters.begin();

  for (auto i = pos - 1 ; i >= 0 ; i--) {
    dist = Promoter::is_promoter_lagging(dna, i);
    if (dist <= PROM_MAX_DIFF) {
      assert (i >= 0 && i < dna.length());
      // Look for the right place to insert the new promoter in the list
      first = find_if(first, cur_strand_promoters.end(),
                      [i](Promoter& r) {
                        return r.pos() <= i;
                      });

      if (first == cur_strand_promoters.end() or first->pos() != i) {
        single_stranded_list(Strand::LAGGING).emplace(first, i, dist, Strand::LAGGING);
      }
    }
  }
}

PromoterList PromoterList::promoters_included_in(Dna::size_type pos_1,
                                                 Dna::size_type pos_2,
                                                 Dna::size_type dna_len) const {
  if (exp_setup->linear_chrsm() and pos_1 == pos_2) {
    return PromoterList();
  }

  // NB: lagging promoter starting at pos2 excluded
  PromoterList promoter_list;

  if (pos_1 < pos_2) {
    auto seg_length = pos_2 - pos_1;

    if (seg_length >= PROM_SIZE) {
      lst_promoters(Strand::LEADING, Position::BETWEEN, pos_1, pos_2 - PROM_SIZE + 1, promoter_list[Strand::LEADING]);
      lst_promoters(Strand::LAGGING, Position::BETWEEN, pos_2, pos_1 + PROM_SIZE - 1, promoter_list[Strand::LAGGING]);
    }
  } else {
    auto seg_length = dna_len + pos_2 - pos_1;

    if (seg_length >= PROM_SIZE) {
      bool is_near_end_of_genome       = (pos_1 + PROM_SIZE > dna_len);
      bool is_near_beginning_of_genome = (pos_2 - PROM_SIZE < 0);

      if (!is_near_end_of_genome && !is_near_beginning_of_genome) {
        lst_promoters(Strand::LEADING, Position::AFTER, pos_1, -1, promoter_list[Strand::LEADING]);
        lst_promoters(Strand::LEADING, Position::BEFORE, -1, pos_2 - PROM_SIZE + 1, promoter_list[Strand::LEADING]);
        lst_promoters(Strand::LAGGING, Position::AFTER, pos_2, -1, promoter_list[Strand::LAGGING]);
        lst_promoters(Strand::LAGGING, Position::BEFORE, -1, pos_1 + PROM_SIZE - 1, promoter_list[Strand::LAGGING]);
      } else if (!is_near_end_of_genome)  // => && is_near_beginning_of_genome
      {
        lst_promoters(
            Strand::LEADING, Position::BETWEEN, pos_1, pos_2 - PROM_SIZE + 1 + dna_len, promoter_list[Strand::LEADING]);
        lst_promoters(Strand::LAGGING, Position::AFTER, pos_2, -1, promoter_list[Strand::LAGGING]);
        lst_promoters(Strand::LAGGING, Position::BEFORE, -1, pos_1 + PROM_SIZE - 1, promoter_list[Strand::LAGGING]);
      } else if (!is_near_beginning_of_genome)  // => && is_near_end_of_genome
      {
        lst_promoters(Strand::LEADING, Position::AFTER, pos_1, -1, promoter_list[Strand::LEADING]);
        lst_promoters(Strand::LEADING, Position::BEFORE, -1, pos_2 - PROM_SIZE + 1, promoter_list[Strand::LEADING]);
        lst_promoters(
            Strand::LAGGING, Position::BETWEEN, pos_2, pos_1 + PROM_SIZE - 1 - dna_len, promoter_list[Strand::LAGGING]);
      } else  // is_near_end_of_genome && is_near_beginning_of_genome
      {
        lst_promoters(
            Strand::LEADING, Position::BETWEEN, pos_1, pos_2 - PROM_SIZE + 1 + dna_len, promoter_list[Strand::LEADING]);
        lst_promoters(
            Strand::LAGGING, Position::BETWEEN, pos_2, pos_1 + PROM_SIZE - 1 - dna_len, promoter_list[Strand::LAGGING]);
      }
    }
  }

  return promoter_list;
}

void PromoterList::extract_leading_promoters_starting_between(Dna::size_type pos_1,
                                                              Dna::size_type pos_2,
                                                              SingleStranded& extracted_promoters) {
  // Find the first promoters in the interval
  auto& cur_strand_promoters = single_stranded_list(Strand::LEADING);

  auto first = find_if(
      cur_strand_promoters.begin(), cur_strand_promoters.end(), [pos_1](Promoter& p) { return p.pos() >= pos_1; });

  if (first == cur_strand_promoters.end() or first->pos() >= pos_2) {
    return;
  }

  // Find the last promoters in the interval

  auto end = find_if(first, cur_strand_promoters.end(), [pos_2](Promoter& p) { return p.pos() >= pos_2; });

  // Extract the promoters (remove them from the individual's list and put them in extracted_promoters)
  SingleStranded promoters_1D;
  for (auto it = first; it != end; it++) {
    promoters_1D.emplace_back(*it);
  }

  extracted_promoters.splice(extracted_promoters.end(), promoters_1D);

  cur_strand_promoters.erase(first, end);
}

void PromoterList::extract_lagging_promoters_starting_between(Dna::size_type pos_1,
                                                              Dna::size_type pos_2,
                                                              SingleStranded& extracted_promoters) {
  auto& cur_strand_promoters = single_stranded_list(Strand::LAGGING);

  // Find the first promoter in the interval (if any)
  auto to_extract_begin =
      find_if(
      cur_strand_promoters.begin(), cur_strand_promoters.end(), [pos_2](Promoter& r) { return r.pos() < pos_2; });

  // If there were no promoters in the interval
  if (to_extract_begin == cur_strand_promoters.end() or to_extract_begin->pos() < pos_1) {
    return;
  }

  // Find the past-the-last promoter in the interval
  auto to_extract_end =
      find_if(to_extract_begin, cur_strand_promoters.end(), [pos_1](Promoter& r) { return r.pos() < pos_1; });

  // Extract the promoters into extracted_promoters
  extracted_promoters.splice(extracted_promoters.end(), cur_strand_promoters, to_extract_begin, to_extract_end);
}

} // namespace aevol
