#include "fasta_mods.h"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include "composition.h"
#include "element_lookup.h"
#include "element_tables.h"
#include "fasta.h"
namespace IsoSpec {
namespace {
constexpr int kAccumulatorSize = ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES;
struct AminoAcidElementIndex {
int first_index[6];
AminoAcidElementIndex() {
static const char* const kSymbols[6] = {"C", "H", "N", "O", "S", "Se"};
for (int i = 0; i < 6; i++)
first_index[i] = find_element_table_first_index(kSymbols[i], strlen(kSymbols[i]));
}
};
const AminoAcidElementIndex& amino_acid_element_index() {
static const AminoAcidElementIndex instance;
return instance;
}
void add_residue(std::int64_t accumulator[kAccumulatorSize], char residue) {
int counts[6] = {0, 0, 0, 0, 0, 0};
parse_fasta(std::string_view(&residue, 1), counts);
const AminoAcidElementIndex& idx = amino_acid_element_index();
for (int i = 0; i < 6; i++)
if (counts[i] != 0)
accumulator[idx.first_index[i]] += counts[i];
}
void consume_unimod_bracket(const char*& p, const char* const end, const UnimodTable& mods, std::int64_t accumulator[kAccumulatorSize]) {
constexpr std::string_view kPrefix = "UNIMOD:";
const auto rest = [p, end] { return std::string(p, end); };
const char* q = p + 1; if (static_cast<size_t>(end - q) < kPrefix.size() || std::string_view(q, kPrefix.size()) != kPrefix)
throw std::invalid_argument("Malformed modification bracket (expected '[UNIMOD:<id>]'): " + rest());
q += kPrefix.size();
const char* digits_start = q;
while (q < end && isdigit(static_cast<unsigned char>(*q)))
q++;
if (q == digits_start)
throw std::invalid_argument("Malformed modification bracket (missing numeric id): " + rest());
if (q == end || *q != ']')
throw std::invalid_argument("Malformed modification bracket (missing closing ']'): " + rest());
uint64_t id;
try {
id = std::stoull(std::string(digits_start, static_cast<size_t>(q - digits_start)));
} catch (const std::exception&) {
throw std::invalid_argument("Malformed modification bracket (id out of range): " + rest());
}
const UnimodEntry* entry = (id > std::numeric_limits<unsigned int>::max())
? nullptr
: mods.lookup(static_cast<unsigned int>(id));
if (entry == nullptr)
throw std::invalid_argument("Unknown or unsupported UNIMOD id in sequence: UNIMOD:" + std::to_string(id));
for (size_t i = 0; i < entry->element_first_index.size(); i++)
accumulator[entry->element_first_index[i]] += entry->element_delta_count[i];
p = q + 1; }
void accumulator_to_composition_into(const std::int64_t accumulator[kAccumulatorSize], ElementComposition& out) {
out.clear();
for (int i = 0; i < kAccumulatorSize; i++) {
if (accumulator[i] != 0) {
out.element_first_index.push_back(i);
out.count.push_back(static_cast<int>(std::clamp<std::int64_t>(
accumulator[i], -AA_ATOM_COUNT_CEILING, AA_ATOM_COUNT_CEILING)));
}
}
}
}
void parse_fasta_with_mods_into(const char* sequence, ElementComposition& out, const UnimodTable& mods) {
parse_fasta_with_mods_into(std::string_view(sequence), out, mods);
}
void parse_fasta_with_mods_into(std::string_view sequence, ElementComposition& out, const UnimodTable& mods) {
out.clear();
if (sequence.find('[') == std::string_view::npos) {
int counts[6] = {0, 0, 0, 0, 0, 0};
parse_fasta(sequence, counts);
const AminoAcidElementIndex& idx = amino_acid_element_index();
for (int i = 0; i < 6; i++) {
if (counts[i] != 0) {
out.element_first_index.push_back(idx.first_index[i]);
out.count.push_back(counts[i]);
}
}
return;
}
std::int64_t accumulator[kAccumulatorSize] = {0};
const char* p = sequence.data();
const char* const end = p + sequence.size();
while (p < end) {
if (*p == '[') {
consume_unimod_bracket(p, end, mods, accumulator);
} else {
add_residue(accumulator, *p);
p++;
}
}
accumulator_to_composition_into(accumulator, out);
}
void parse_fasta_with_mods_full_into(const char* sequence, ElementComposition& out, const UnimodTable& mods) {
parse_fasta_with_mods_full_into(std::string_view(sequence), out, mods);
}
void parse_fasta_with_mods_full_into(std::string_view sequence, ElementComposition& out, const UnimodTable& mods) {
parse_fasta_with_mods_into(sequence, out, mods);
const AminoAcidElementIndex& idx = amino_acid_element_index();
const int h_index = idx.first_index[1];
const int o_index = idx.first_index[3];
bool has_h = false, has_o = false;
for (size_t i = 0; i < out.element_first_index.size(); i++) {
if (out.element_first_index[i] == h_index) {
out.count[i] += 2;
has_h = true;
} else if (out.element_first_index[i] == o_index) {
out.count[i] += 1;
has_o = true;
}
}
if (!has_h) {
out.element_first_index.push_back(h_index);
out.count.push_back(2);
}
if (!has_o) {
out.element_first_index.push_back(o_index);
out.count.push_back(1);
}
}
ElementComposition parse_fasta_with_mods(std::string_view sequence, const UnimodTable& mods) {
ElementComposition result;
parse_fasta_with_mods_into(sequence, result, mods);
return result;
}
ElementComposition parse_fasta_with_mods(const char* sequence, const UnimodTable& mods) {
return parse_fasta_with_mods(std::string_view(sequence), mods);
}
ElementComposition parse_fasta_with_mods_full(std::string_view sequence, const UnimodTable& mods) {
ElementComposition result;
parse_fasta_with_mods_full_into(sequence, result, mods);
return result;
}
ElementComposition parse_fasta_with_mods_full(const char* sequence, const UnimodTable& mods) {
return parse_fasta_with_mods_full(std::string_view(sequence), mods);
}
Iso Iso::FromFASTAWithMods(const char* sequence, const UnimodTable& mods, bool use_nominal_masses, bool add_water) {
return FromFASTAWithMods(std::string_view(sequence), mods, use_nominal_masses, add_water);
}
Iso Iso::FromFASTAWithMods(std::string_view sequence, const UnimodTable& mods, bool use_nominal_masses, bool add_water) {
if (sequence.find('[') == std::string_view::npos)
return Iso::FromFASTA(sequence, use_nominal_masses, add_water);
ElementComposition composition =
add_water ? parse_fasta_with_mods_full(sequence, mods) : parse_fasta_with_mods(sequence, mods);
return build_iso_from_composition(composition, use_nominal_masses);
}
Iso Iso::FromFASTAWithMods(const char* sequence, bool use_nominal_masses, bool add_water, const char* unimod_db_path) {
return FromFASTAWithMods(std::string_view(sequence), use_nominal_masses, add_water, unimod_db_path);
}
Iso Iso::FromFASTAWithMods(std::string_view sequence, bool use_nominal_masses, bool add_water, const char* unimod_db_path) {
if (sequence.find('[') == std::string_view::npos)
return Iso::FromFASTA(sequence, use_nominal_masses, add_water);
const UnimodTable& mods = (unimod_db_path == nullptr || unimod_db_path[0] == '\0')
? embedded_unimod_table()
: unimod_table_for_path(unimod_db_path);
return FromFASTAWithMods(sequence, mods, use_nominal_masses, add_water);
}
}