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

#include "macros.h"

namespace aevol {

auto read_fasta_sequence(std::ifstream& is, const std::string& file_name) noexcept(false)
    -> std::tuple<std::string, std::string, FastaModifiers> {
  // Read fasta definition line (ignore blank lines)
  auto line = std::string{};
  do {
    std::getline(is, line);
  } while (line.empty() or line[0] == ';');

  // Inspect fasta definition line
  std::smatch seqid_match;
  std::regex_search(line, seqid_match, std::regex(R"(^>([\w\-\.:*#]+))"));
  if (seqid_match.size() == 0) {
    throw fasta_error(std::format("file {} does not appear to be a fasta file", file_name));
  }
  auto seqid = std::string(seqid_match[1]);
  auto modifiers_str = std::string(seqid_match.suffix());
  std::smatch aevol_org_match;
  std::regex_search(modifiers_str, aevol_org_match, std::regex(R"(\[organism=aevol\w*\])"));
  if (aevol_org_match.size() == 0) {
    throw non_aevol_fasta_error(
        std::format("sequence from file {} does not appear to be an aevol sequence", file_name));
  }
  auto modifiers = FastaModifiers();
  std::regex modifiers_regex(R"(\[(\w+)=([^\]]+)\])");
  for (std::smatch m; std::regex_search(modifiers_str, m, modifiers_regex);) {
    modifiers.emplace_back(m[1], m[2]);
    modifiers_str = m.suffix();
  }

  // Read sequence
  std::getline(is, line);
  if (is.fail() or line.length() == 0) {
    throw std::runtime_error(std::string("failed to read sequence from file ") + file_name);
  }
  #ifdef BASE_4
  // Transform from ATCG to 0123
  std::transform(line.cbegin(), line.cend(), line.begin(), atcg_to_num);
  #endif

  return {seqid, line, modifiers};
}

auto read_fasta_file(const std::string& file_name) noexcept(false) -> std::vector<std::string> {
  assert(file_name.length() != 0);

  auto sequences = decltype(read_fasta_file(file_name)){};

  auto is = std::ifstream(file_name);
  if (not is) {
    throw std::runtime_error(std::string("failed to open sequence file ") + file_name);
  }

  // Set stream to trigger an exception on EOF
  is.exceptions(std::ifstream::eofbit);

  // Read sequences in file
  while (not is.eof()) {
    try {
      // Read sequence
      auto [seqid, seq, modifiers] = read_fasta_sequence(is, file_name);
      sequences.push_back(seq);
    } catch (const std::ios_base::failure& fail) {
      if (not is.eof()) {
        throw;
      }
    }
  }

  return sequences;
}

}  // namespace aevol
