// =============================================================== //
//                                                                 //
//   File      : ali_misc.hxx                                      //
//   Purpose   :                                                   //
//                                                                 //
//   Institute of Microbiology (Technical University Munich)       //
//   http://www.arb-home.de/                                       //
//                                                                 //
// =============================================================== //

#ifndef ALI_MISC_HXX
#define ALI_MISC_HXX

#ifndef _GLIBCXX_CSTDIO
#include <cstdio>
#endif
#ifndef _GLIBCXX_CSTDLIB
#include <cstdlib>
#endif
#ifndef _UNISTD_H
#include <unistd.h>
#endif
#ifndef _MEMORY_H
#include <memory.h>
#endif

#ifndef ATTRIBUTES_H
#include <attributes.h>
#endif
#ifndef ARBTOOLS_H
#include <arbtools.h>
#endif

#define ALI_A_CODE     0
#define ALI_C_CODE     1
#define ALI_G_CODE     2
#define ALI_U_CODE     3
#define ALI_GAP_CODE   4
#define ALI_N_CODE     5
#define ALI_DOT_CODE   6
#define ALI_UNDEF_CODE 200

// -----------------------------
//      Some error functions

inline void ali_message(const char *message) { fprintf(stdout, "%s\n", message); }
inline void ali_warning(const char *message) { fprintf(stderr, "WARNING: %s\n", message); }

void ali_error(const char *message, const char *func = "") __ATTR__NORETURN;
void ali_fatal_error(const char *message, const char *func = "") __ATTR__NORETURN;

inline void ali_out_of_memory_if(bool cond) {
    if (cond) ali_fatal_error("out of memory");
}

inline void *CALLOC(long i, long j) {
    char *v = (char *)malloc(i*j);
    ali_out_of_memory_if(!v);
    memset(v, 0, i*j);
    return v;
}

// -------------------
//      Converters

inline int ali_is_base(char c) {
    return c == 'a' || c == 'A' || c == 'c' || c == 'C' ||
           c == 'g' || c == 'G' || c == 'u' || c == 'U' ||
           c == 't' || c == 'T' || c == 'n' || c == 'N';
}

inline int ali_is_base(unsigned char c) {
    return c <= 3 || c == 5;
}

inline int ali_is_real_base(char c) {
    return c == 'a' || c == 'A' || c == 'c' || c == 'C' ||
           c == 'g' || c == 'G' || c == 'u' || c == 'U' ||
           c == 't' || c == 'T';
}

inline int ali_is_real_base(unsigned char c) {
    return c <= 3;
}

inline int ali_is_real_base_or_gap(char c) {
    return c == 'a' || c == 'A' || c == 'c' || c == 'C' ||
           c == 'g' || c == 'G' || c == 'u' || c == 'U' ||
           c == 't' || c == 'T' || c == '-';
}

inline int ali_is_real_base_or_gap(unsigned char c) {
    return c <= 4;
}

inline int ali_is_dot(char c) {
    return c == '.';
}

inline int ali_is_dot(unsigned char c) {
    return c == 6;
}

inline int ali_is_nbase(char c) {
    return c == 'n';
}

inline int ali_is_nbase(unsigned char c) {
    return c == 5;
}

inline int ali_is_gap(char c) {
    return c == '-';
}

inline int ali_is_gap(unsigned char c) {
    return c == 4;
}

inline unsigned char ali_base_to_number(char c, int no_gap_flag = 0) {
    switch (c) {
        case 'a': case 'A': return 0;
        case 'c': case 'C': return 1;
        case 'g': case 'G': return 2;
        case 'u': case 'U':
        case 't': case 'T': return 3;
        case '-':           return no_gap_flag ? 6 : 4;
        case '.':           return 6;
        default: ali_warning("Replaced unknown base by 'n'"); FALLTHROUGH;
        case 'n': case 'N': return 5;
    }
}

inline char ali_number_to_base(unsigned char n) {
    switch (n) {
        case 0: return 'a';
        case 1: return 'c';
        case 2: return 'g';
        case 3: return 'u';
        case 4: return '-';
        case 5: return 'n';
        default:
            ali_warning("Replaced unknown number by '.'");
            printf("received %d\n", n);
            ali_fatal_error("STOP");
        case 6: return '.';
    }
}

inline void ali_string_to_sequence(char *sequence) {
    for (; *sequence != '\0' && !ali_is_base(*sequence); sequence++)
        *sequence = (char) ali_base_to_number(*sequence, 1);

    for (; *sequence != '\0'; sequence++)
        *sequence = (char) ali_base_to_number(*sequence);
}

inline void ali_sequence_to_string(unsigned char *sequence, unsigned long length) {
    for (; length-- > 0; sequence++)
        *sequence = (unsigned char) ali_number_to_base(*sequence);
}

inline void ali_sequence_to_postree_sequence(unsigned char *sequence, unsigned long length) {
    for (; length-- > 0; sequence++)
        if (ali_is_base(*sequence)) {
            if (ali_is_nbase(*sequence))
                *sequence = 4;
        }
        else {
            ali_warning("Unknowen symbol replaced by 'n'");
            *sequence = 4;
        }
}

inline void ali_print_sequence(unsigned char *sequence, unsigned long length) {
    for (; length-- > 0; sequence++)
        printf("%d ", *sequence);
}

#else
#error ali_misc.hxx included twice
#endif // ALI_MISC_HXX
