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

#include "ExpSetup.h"
#include "macros.h"

namespace aevol {

Promoter::Promoter(int32_t pos, int8_t nb_errors, Strand strand) : pos_(pos), nb_errors_(nb_errors), strand_(strand) {
  basal_level_ = BASAL_LEVELS[nb_errors];
}

int8_t Promoter::is_promoter_leading(const Dna& dna, Dna::size_type pos) {
  if (exp_setup->linear_chrsm()) {
    if (pos > dna.length() - PROM_SIZE) {  // No room for a promoter, return max dist
      return PROM_SIZE;
    }
  }

#ifdef BASE_2
  #ifdef VANILLA_SEARCH
  int8_t prom_dist_leading[26];
  int len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist_leading[motif_id] =
        PROM_SEQ_LEAD[motif_id] ==
                dna.data_[pos + motif_id >= len ? pos + motif_id - len : pos + motif_id]
            ? 0 : 1;
  }

  return prom_dist_leading[0] +
         prom_dist_leading[1] +
         prom_dist_leading[2] +
         prom_dist_leading[3] +
         prom_dist_leading[4] +
         prom_dist_leading[5] +
         prom_dist_leading[6] +
         prom_dist_leading[7] +
         prom_dist_leading[8] +
         prom_dist_leading[9] +
         prom_dist_leading[10] +
         prom_dist_leading[11] +
         prom_dist_leading[12] +
         prom_dist_leading[13] +
         prom_dist_leading[14] +
         prom_dist_leading[15] +
         prom_dist_leading[16] +
         prom_dist_leading[17] +
         prom_dist_leading[18] +
         prom_dist_leading[19] +
         prom_dist_leading[20] +
         prom_dist_leading[21];
  #elif CMOD_SEARCH
  int8_t prom_dist_leading[26];
  int len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist_leading[motif_id] =
        PROM_SEQ_LEAD[motif_id] ==
                dna.data_[mod(pos + motif_id, len)]
            ? 0 : 1;
  }

  return prom_dist_leading[0] +
         prom_dist_leading[1] +
         prom_dist_leading[2] +
         prom_dist_leading[3] +
         prom_dist_leading[4] +
         prom_dist_leading[5] +
         prom_dist_leading[6] +
         prom_dist_leading[7] +
         prom_dist_leading[8] +
         prom_dist_leading[9] +
         prom_dist_leading[10] +
         prom_dist_leading[11] +
         prom_dist_leading[12] +
         prom_dist_leading[13] +
         prom_dist_leading[14] +
         prom_dist_leading[15] +
         prom_dist_leading[16] +
         prom_dist_leading[17] +
         prom_dist_leading[18] +
         prom_dist_leading[19] +
         prom_dist_leading[20] +
         prom_dist_leading[21];

  #elif AMOD_SEARCH
  int8_t prom_dist_leading[26];
  int len = dna.length();

  if (pos+22 >= len) {
    for (int motif_id = 0; motif_id < 22; motif_id++) {
      prom_dist_leading[motif_id] =
          PROM_SEQ_LEAD[motif_id] ==
                  dna.data_[mod(pos + motif_id, len)]
              ? 0 : 1;
    }
  } else {
    for (int motif_id = 0; motif_id < 22; motif_id++) {
      prom_dist_leading[motif_id] =
          PROM_SEQ_LEAD[motif_id] ==
                  dna.data_[pos + motif_id]
              ? 0 : 1;
    }
  }

  return prom_dist_leading[0] +
         prom_dist_leading[1] +
         prom_dist_leading[2] +
         prom_dist_leading[3] +
         prom_dist_leading[4] +
         prom_dist_leading[5] +
         prom_dist_leading[6] +
         prom_dist_leading[7] +
         prom_dist_leading[8] +
         prom_dist_leading[9] +
         prom_dist_leading[10] +
         prom_dist_leading[11] +
         prom_dist_leading[12] +
         prom_dist_leading[13] +
         prom_dist_leading[14] +
         prom_dist_leading[15] +
         prom_dist_leading[16] +
         prom_dist_leading[17] +
         prom_dist_leading[18] +
         prom_dist_leading[19] +
         prom_dist_leading[20] +
         prom_dist_leading[21];

  #elif B1MOD_SEARCH
  int8_t prom_dist_leading[26];
  int32_t len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist_leading[motif_id] =
        PROM_SEQ_LEAD[motif_id] ==
                dna.data_[(pos + motif_id) - ((pos + motif_id) >= len) * len]
            ? 0 : 1;
  }

  return prom_dist_leading[0] +
         prom_dist_leading[1] +
         prom_dist_leading[2] +
         prom_dist_leading[3] +
         prom_dist_leading[4] +
         prom_dist_leading[5] +
         prom_dist_leading[6] +
         prom_dist_leading[7] +
         prom_dist_leading[8] +
         prom_dist_leading[9] +
         prom_dist_leading[10] +
         prom_dist_leading[11] +
         prom_dist_leading[12] +
         prom_dist_leading[13] +
         prom_dist_leading[14] +
         prom_dist_leading[15] +
         prom_dist_leading[16] +
         prom_dist_leading[17] +
         prom_dist_leading[18] +
         prom_dist_leading[19] +
         prom_dist_leading[20] +
         prom_dist_leading[21];
  #elif B2MOD_SEARCH
  int8_t prom_dist_leading[26];
  int32_t len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist_leading[motif_id] =
        PROM_SEQ_LEAD[motif_id] ==
                dna.data_[(pos + motif_id) + ((((uint32_t)((pos + motif_id) - len)) >> 31) -1 )* len]
            ? 0 : 1;
  }

  return prom_dist_leading[0] +
         prom_dist_leading[1] +
         prom_dist_leading[2] +
         prom_dist_leading[3] +
         prom_dist_leading[4] +
         prom_dist_leading[5] +
         prom_dist_leading[6] +
         prom_dist_leading[7] +
         prom_dist_leading[8] +
         prom_dist_leading[9] +
         prom_dist_leading[10] +
         prom_dist_leading[11] +
         prom_dist_leading[12] +
         prom_dist_leading[13] +
         prom_dist_leading[14] +
         prom_dist_leading[15] +
         prom_dist_leading[16] +
         prom_dist_leading[17] +
         prom_dist_leading[18] +
         prom_dist_leading[19] +
         prom_dist_leading[20] +
         prom_dist_leading[21];
  #else // OLD_SEARCH
  int8_t prom_dist_leading = 0;
  int len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist_leading +=
        PROM_SEQ_LEAD[motif_id] ==
                dna.data_[pos + motif_id >= len ? pos + motif_id - len : pos + motif_id]
            ? 0 : 1;
    if (prom_dist_leading>PROM_MAX_DIFF)
      break;
  }

  return prom_dist_leading;
  #endif
#elif BASE_4
  int8_t prom_dist_leading = 0;
  int len = dna.length();

  for (int motif_id = 0; motif_id < PROM_SIZE; motif_id++) {
    prom_dist_leading +=
        PROM_SEQ[motif_id] != dna.data_[
                                  pos + motif_id >= len ?
                                                        pos - len + motif_id : pos + motif_id
    ];

    if (prom_dist_leading > PROM_MAX_DIFF)
      break;
  }

  return prom_dist_leading;
#endif
}

int8_t Promoter::is_promoter_lagging(const Dna& dna, Dna::size_type pos) {
  if (exp_setup->linear_chrsm()) {
    if (pos < PROM_SIZE) { // No room for a promoter, return max dist
      return PROM_SIZE;
    }
  }

#ifdef BASE_2
  #ifdef VANILLA_SEARCH
  int8_t prom_dist[26];
  int len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist[motif_id] =
        PROM_SEQ_LAG[motif_id] ==
                dna.data_[pos - motif_id < 0 ? len + pos - motif_id : pos - motif_id]
            ? 0 : 1;
  }

  return prom_dist[0] +
         prom_dist[1] +
         prom_dist[2] +
         prom_dist[3] +
         prom_dist[4] +
         prom_dist[5] +
         prom_dist[6] +
         prom_dist[7] +
         prom_dist[8] +
         prom_dist[9] +
         prom_dist[10] +
         prom_dist[11] +
         prom_dist[12] +
         prom_dist[13] +
         prom_dist[14] +
         prom_dist[15] +
         prom_dist[16] +
         prom_dist[17] +
         prom_dist[18] +
         prom_dist[19] +
         prom_dist[20] +
         prom_dist[21];
  #elif CMOD_SEARCH
  int8_t prom_dist[26];
  int len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist[motif_id] =
        PROM_SEQ_LAG[motif_id] ==
                dna.data_[mod(pos - motif_id,len)]
            ? 0 : 1;
  }

  return prom_dist[0] +
         prom_dist[1] +
         prom_dist[2] +
         prom_dist[3] +
         prom_dist[4] +
         prom_dist[5] +
         prom_dist[6] +
         prom_dist[7] +
         prom_dist[8] +
         prom_dist[9] +
         prom_dist[10] +
         prom_dist[11] +
         prom_dist[12] +
         prom_dist[13] +
         prom_dist[14] +
         prom_dist[15] +
         prom_dist[16] +
         prom_dist[17] +
         prom_dist[18] +
         prom_dist[19] +
         prom_dist[20] +
         prom_dist[21];
  #elif AMOD_SEARCH
  int8_t prom_dist[26];
  int len = dna.length();

  if (pos-22 < 0) {

    for (int motif_id = 0; motif_id < 22; motif_id++) {
      prom_dist[motif_id] =
          PROM_SEQ_LAG[motif_id] ==
                  dna.data_[mod(pos - motif_id,len)]
              ? 0 : 1;
    }
  } else {
    for (int motif_id = 0; motif_id < 22; motif_id++) {
      prom_dist[motif_id] =
          PROM_SEQ_LAG[motif_id] ==
                  dna.data_[pos - motif_id]
              ? 0 : 1;
    }
  }

  return prom_dist[0] +
         prom_dist[1] +
         prom_dist[2] +
         prom_dist[3] +
         prom_dist[4] +
         prom_dist[5] +
         prom_dist[6] +
         prom_dist[7] +
         prom_dist[8] +
         prom_dist[9] +
         prom_dist[10] +
         prom_dist[11] +
         prom_dist[12] +
         prom_dist[13] +
         prom_dist[14] +
         prom_dist[15] +
         prom_dist[16] +
         prom_dist[17] +
         prom_dist[18] +
         prom_dist[19] +
         prom_dist[20] +
         prom_dist[21];
  #elif B1MOD
  int8_t prom_dist[26];
  int32_t len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist[motif_id] =
        PROM_SEQ_LAG[motif_id] ==
                dna.data_[(pos - motif_id) + ((pos - motif_id) < 0) * len]
            ? 0 : 1;
  }

  return prom_dist[0] +
         prom_dist[1] +
         prom_dist[2] +
         prom_dist[3] +
         prom_dist[4] +
         prom_dist[5] +
         prom_dist[6] +
         prom_dist[7] +
         prom_dist[8] +
         prom_dist[9] +
         prom_dist[10] +
         prom_dist[11] +
         prom_dist[12] +
         prom_dist[13] +
         prom_dist[14] +
         prom_dist[15] +
         prom_dist[16] +
         prom_dist[17] +
         prom_dist[18] +
         prom_dist[19] +
         prom_dist[20] +
         prom_dist[21];

  #elif B2MOD
  int8_t prom_dist[26];
  int32_t len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist[motif_id] =
        PROM_SEQ_LAG[motif_id] ==
                dna.data_[ (pos - motif_id) + (((uint32_t)((pos - motif_id))) >> 31) * len]
            ? 0 : 1;
  }

  return prom_dist[0] +
         prom_dist[1] +
         prom_dist[2] +
         prom_dist[3] +
         prom_dist[4] +
         prom_dist[5] +
         prom_dist[6] +
         prom_dist[7] +
         prom_dist[8] +
         prom_dist[9] +
         prom_dist[10] +
         prom_dist[11] +
         prom_dist[12] +
         prom_dist[13] +
         prom_dist[14] +
         prom_dist[15] +
         prom_dist[16] +
         prom_dist[17] +
         prom_dist[18] +
         prom_dist[19] +
         prom_dist[20] +
         prom_dist[21];
  #else
  int8_t prom_dist = 0;
  int len = dna.length();

  for (int motif_id = 0; motif_id < 22; motif_id++) {
    prom_dist+=
        PROM_SEQ_LAG[motif_id] ==
                dna.data_[mod(pos - motif_id,len)]
            ? 0 : 1;
    if (prom_dist>PROM_MAX_DIFF)
      break;
  }

  return prom_dist;
  #endif
#elif BASE_4
  int8_t prom_dist = 0;
  int len = dna.length();

  for (int motif_id = 0; motif_id < PROM_SIZE; motif_id++) {
    prom_dist +=
        PROM_SEQ[motif_id]
        != get_complementary_base(dna.data_[  // this is the same as comparing the two opposites (comparing opposite PROM with opposite strand)
               pos - motif_id < 0 ?
                                  pos + len - motif_id : pos - motif_id
    ]);
    if (prom_dist > PROM_MAX_DIFF)
      break;
  }

  return prom_dist;
#endif
}

}  // namespace aevol
