// ****************************************************************************
//
//          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_CODON_H_
#define AEVOL_CODON_H_

#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <iostream>

#include "macros.h"
#include "Strand.h"
#ifdef BASE_4
#include "aevol_4b/biochemistry/AminoAcid.h"
#endif

namespace aevol {

class Codon {
 public:
  Codon();
  explicit Codon(const Codon& model);
  explicit Codon(int8_t value);
  virtual ~Codon();

  #ifdef BASE_4
  explicit Codon(char b1, char b2, char b3);
  #endif

  int8_t value() { return value_; }

  #ifdef BASE_4
  auto amino_acid() { return amino_acid_; }
  #endif
  #ifdef BASE_2
  bool is_start() { return value_ == CODON_START; }
  bool is_stop() { return value_ == CODON_STOP; }
  #elif BASE_4
  bool is_start() { return amino_acid_ == aevol_4b::AminoAcid::METHIONINE; }
  bool is_stop() { return amino_acid_ == aevol_4b::AminoAcid::STOP; }
  #endif

  Codon* copy() { return new Codon(value_); }  // TODO(dpa) use copy ctor instead!

  #ifdef BASE_4
  inline auto to_aminoacid() const -> aevol_4b::AminoAcid;
  #endif

 protected:
  int8_t value_;

  #ifdef BASE_4
  aevol_4b::AminoAcid amino_acid_;
  #endif
};

#ifdef BASE_4
inline int8_t bases_to_codon_value(char b1, char b2, char b3) {
  return ((b1 - '0') << 4) | ((b2 - '0') << 2) | (b3 - '0');
}

inline auto bases_to_aminoacid(char base_1, char base_2, char base_3) -> aevol_4b::AminoAcid {
  if (base_2 == BASE_T)  // ** -T- **
  {
    if (base_1 == BASE_G)
      return aevol_4b::AminoAcid::VALINE;  // GT-
    if (base_1 == BASE_C)
      return aevol_4b::AminoAcid::LEUCINE;  // CT-

    if (base_1 == BASE_A) {
      if (base_3 == BASE_G)
        return aevol_4b::AminoAcid::METHIONINE;  // ATG
      else
        return aevol_4b::AminoAcid::ISOLEUCINE;  // AT(T/C/A)
    }

    // base_1 == BASE_T
    if (base_3 == BASE_T || base_3 == BASE_C)
      return aevol_4b::AminoAcid::PHENYLALANINE;  // TT(T/C)
    else
      return aevol_4b::AminoAcid::LEUCINE;  // TT(A/G)
  } else if (base_2 == BASE_C)              // ** -C- **
  {
    switch (base_1) {
      case BASE_T:
        return aevol_4b::AminoAcid::SERINE;  // TC-
      case BASE_C:
        return aevol_4b::AminoAcid::PROLINE;  // CC-
      case BASE_A:
        return aevol_4b::AminoAcid::THREONINE;  // AC-
      case BASE_G:
        return aevol_4b::AminoAcid::ALANINE;  // GC-
    }
  } else if (base_2 == BASE_A)  // ** -A- **
  {
    switch (base_1) {
      case BASE_T:
        if (base_3 == BASE_T || base_3 == BASE_C)
          return aevol_4b::AminoAcid::TYROSINE;  // TA(T/C)
        else
          return aevol_4b::AminoAcid::STOP;  // TA(A/G)
      case BASE_C:
        if (base_3 == BASE_T || base_3 == BASE_C)
          return aevol_4b::AminoAcid::HISTIDINE;  // CA(T/C)
        else
          return aevol_4b::AminoAcid::GLUTAMINE;  // CA(A/G)
      case BASE_A:
        if (base_3 == BASE_T || base_3 == BASE_C)
          return aevol_4b::AminoAcid::ASPARAGINE;  // AA(T/C)
        else
          return aevol_4b::AminoAcid::LYSINE;  // AA(A/G)
      case BASE_G:
        if (base_3 == BASE_T || base_3 == BASE_C)
          return aevol_4b::AminoAcid::ASPARTIC_ACID;  // GA(T/C)
        else
          return aevol_4b::AminoAcid::GLUTAMIC_ACID;  // GA(A/G)
    }
  } else if (base_2 == BASE_G)  // ** -G- **
  {
    switch (base_1) {
      case BASE_T:
        if (base_3 == BASE_A)
          return aevol_4b::AminoAcid::STOP;  // TGA
        if (base_3 == BASE_G)
          return aevol_4b::AminoAcid::TRYPTOPHAN;  // TGG
        return aevol_4b::AminoAcid::CYSTEINE;  // TG(T/C)
      case BASE_C:
        return aevol_4b::AminoAcid::ARGININE;  // CG-
      case BASE_A:
        if (base_3 == BASE_A || base_3 == BASE_G)
          return aevol_4b::AminoAcid::ARGININE;  // CG(A/G)
        return aevol_4b::AminoAcid::SERINE;  // CG(T/C)
      case BASE_G:
        return aevol_4b::AminoAcid::GLYCINE;  // GG-
    }
  }

  std::cerr << "An error occurred: codon " << base_1 << "," << base_2 << "," << base_3
            << " cannot be translated to aminoacid. Aborting." << std::endl;
  exit(1);
}

inline auto codon_value_to_aminoacid(int8_t value) -> aevol_4b::AminoAcid {
  return bases_to_aminoacid('0' + ((value >> 4) & 3), '0' + ((value >> 2) & 3), '0' + (value & 3));
}

inline bool is_start_codon(int8_t value) {
  return codon_value_to_aminoacid(value) == aevol_4b::AminoAcid::METHIONINE;
}

inline bool is_stop_codon(char b1, char b2, char b3) {
  return bases_to_aminoacid(b1, b2, b3) == aevol_4b::AminoAcid::STOP;
}

inline auto Codon::to_aminoacid() const -> aevol_4b::AminoAcid {
  return codon_value_to_aminoacid(value_);
}
#else
inline bool is_stop_codon(char b1, char b2, char b3) {
  return b1 == STOP_CODON[0] && b2 == STOP_CODON[1] && b3 == STOP_CODON[2];
}
#endif

}  // namespace aevol

#endif  // AEVOL_CODON_H_
