// ==================================================================== //
//                                                                      //
//   File      : AW_helix.cxx                                           //
//   Purpose   : Wrapper for BI_helix + AW-specific functions           //
//                                                                      //
//                                                                      //
// Coded by Ralf Westram (coder@reallysoft.de) in December 2004         //
// Copyright Department of Microbiology (Technical University Munich)   //
//                                                                      //
// Visit our web site at: http://www.arb-home.de/                       //
//                                                                      //
// ==================================================================== //

#include "AW_helix.hxx"

#include <config_manager.hxx>

#include <aw_root.hxx>
#include <aw_window.hxx>
#include <aw_awar.hxx>
#include <aw_device.hxx>

#include <arbdbt.h>
#include <arb_strbuf.h>

#include <cctype>

#define AWAR_HELIX_REFRESH         "tmp/Helix/refresh" // used to trigger client refreshes
#define AWAR_HELIX_ENABLE          "Helix/enable"
#define HELIX_AWAR_SYMBOL_TEMPLATE "Helix/symbols/%s"
#define HELIX_AWAR_PAIR_TEMPLATE   "Helix/pairs/%s"

struct helix_pair_def {
    const char   *awar;
    BI_PAIR_TYPE  pair_type;
};

static helix_pair_def helix_awars[] = {
    { "Strong_Pair",      HELIX_STRONG_PAIR },
    { "Normal_Pair",      HELIX_NORMAL_PAIR },
    { "Weak_Pair",        HELIX_WEAK_PAIR },
    { "No_Pair",          HELIX_NO_PAIR },
    { "User_Pair",        HELIX_USER0 },
    { "User_Pair2",       HELIX_USER1 },
    { "User_Pair3",       HELIX_USER2 },
    { "User_Pair4",       HELIX_USER3 },
    { "User_Pair5",       HELIX_USER4 },
    { "User_Pair6",       HELIX_USER5 },
    { "Default",          HELIX_DEFAULT },
    { NULp,               BI_PAIR_TYPE(-1) },
};

inline const char *helix_symbol_awar(int idx) { return GBS_global_string(HELIX_AWAR_SYMBOL_TEMPLATE, helix_awars[idx].awar); }
inline const char *helix_pair_awar  (int idx) { return GBS_global_string(HELIX_AWAR_PAIR_TEMPLATE,   helix_awars[idx].awar); }

char *AW_helix::seq_2_helix(char *sequence, char undefsymbol) const {
    size_t size2 = strlen(sequence);
    arb_assert(size2<=size()); // if this fails there is a sequence longer than the alignment
    char *helix = ARB_calloc<char>(size()+1);
    for (size_t i=0; i<size2; i++) {
        if (is_pairpos(i)) {
            size_t j   = opposite_position(i);
            char   sym = get_symbol(sequence[i], sequence[j]);
            helix[i]   = sym == ' ' ? undefsymbol : sym;
        }
        else {
            helix[i] = undefsymbol;
        }
    }
    return helix;
}

void AW_helix::add_callback(const RootCallback& cb) const {
    // add client callback. triggered when helix or pair definition has changed.
    awar_refresh->add_callback(cb);
}

void AW_helix::remove_callback(const RootCallback& cb) const {
    awar_refresh->remove_callback(cb);
}

static void helix_setup_changed_cb(AW_root *aw_root, AW_helix *helix) { helix->setup_changed_cb(aw_root); }
void AW_helix::setup_changed_cb(AW_root *aw_root) {
    enabled = aw_root->awar(AWAR_HELIX_ENABLE)->read_int();

    for (int j=0; helix_awars[j].awar; j++) {
        int i = helix_awars[j].pair_type;

        set_pairs_def(i, aw_root->awar(helix_pair_awar(j))->read_char_pntr());
        set_char_bind(i, aw_root->awar(helix_symbol_awar(j))->read_char_pntr());
    }

    aw_root->awar(AWAR_HELIX_REFRESH)->touch(); //  trigger client refreshes (EDIT4, SECEDIT, ...)
}

static void helix_pairs_changed_cb(AW_root *aw_root, int changed_idx) {
    static bool recursion = false;

    if (!recursion) {
        AW_awar       *awar_pair  = aw_root->awar(helix_pair_awar(changed_idx));
        const char    *pairdef_in = awar_pair->read_char_pntr();
        GBS_strstruct  pairdef_out(strlen(pairdef_in)+2);

        {
            LocallyModify<bool> flag(recursion, true);
            for (int i = 0;;) {
                while (pairdef_in[i] == ' ') ++i; // skip spaces
                char left = toupper(pairdef_in[i++]); if (!left) break;

                if (!pairdef_out.empty()) {
                    pairdef_out.put(' ');
                }
                pairdef_out.put(left);

                while (pairdef_in[i] == ' ') ++i; // skip spaces
                char right = toupper(pairdef_in[i++]); if (!right) break; // Note: in case of break this accepts single base @ tail

                pairdef_out.put(right);

                // remove (first) of duplicated pair definitions:
                {
                    char pair[] = "xx";
                    pair[0]     = left;
                    pair[1]     = right;

                    for (int pass = 1; pass<=2; ++pass) {
                        const char *data = pairdef_out.get_data();

                        while (1) {
                            const char *found = strstr(data, pair);
                            if (!found) break; // not found -> done

                            size_t pos = pairdef_out.get_position();
                            if (found == data+pos-2) break; // reached current pair -> done

                            // cut unwanted pair:
                            size_t found_pos = found-data;
                            pairdef_out.cut(found_pos, 3);
                        }

                        std::swap(pair[0], pair[1]);
                    }
                }

                for (int j = 0; helix_awars[j].awar; j++) {
                    if (j != changed_idx) {
                        AW_awar *awar_pair2 = aw_root->awar(helix_pair_awar(j));
                        char    *pd2        = awar_pair2->read_string();
                        int      dst        = 0;
                        bool     modified   = false;

                        for (int k = 0; ; k += 3) {
                            char l = toupper(pd2[k]); if (!l) break;
                            pd2[dst] = l;

                            char r = toupper(pd2[k+1]);
                            if (!r) {
                                // do NOT silently remove trailing single base @ tail
                                dst += 2; // only has effect if modified
                                break;
                            }
                            pd2[dst+1] = r;

                            if ((left == l && right == r) || (left == r && right == l)) {
                                // remove duplicated pair
                                modified = true;
                            }
                            else {
                                dst += 3;
                            }
                            if (!pd2[k+2]) break;
                        }

                        if (modified) {
                            int endS = dst>0 ? dst-1 : dst;

                            arb_assert(endS>=0);
                            pd2[endS] = 0;
                            awar_pair2->write_string(pd2);
                        }

                        free(pd2);
                    }
                }
            }
            awar_pair->write_string(pairdef_out.get_data()); // write back uppercase version
        }
        aw_root->awar(AWAR_HELIX_ENABLE)->touch(); // trigger reload of config + refresh
    }
}

AW_helix::AW_helix(AW_root *aw_root)
    : enabled(0)
{
    // Note: effectively AW_helix is/hasToBe a SINGLETON
    // (awar generation will fail on 2nd instance)

    RootCallback rcb = makeRootCallback(helix_setup_changed_cb, this);

    for (int j=0; helix_awars[j].awar; j++) {
        int i = helix_awars[j].pair_type;

        aw_root->awar_string(helix_pair_awar(j),   get_pairs_def(i))->add_callback(makeRootCallback(helix_pairs_changed_cb, i));
        aw_root->awar_string(helix_symbol_awar(j), get_char_bind(i))->add_callback(rcb);
    }
    aw_root->awar_int(AWAR_HELIX_ENABLE, 1)->add_callback(rcb);

    awar_refresh = aw_root->awar_int(AWAR_HELIX_REFRESH, 1);

    setup_changed_cb(aw_root); // init from AWARs
}

static void setup_helix_config(AWT_config_definition& cdef) {
    cdef.add(AWAR_HELIX_ENABLE, "enable");

    for (int j=0; helix_awars[j].awar; j++) {
        int i = helix_awars[j].pair_type;

        const char *name = helix_awars[j].awar;
        if (i != HELIX_DEFAULT) {
            cdef.add(helix_pair_awar(j), name);
        }
        cdef.add(helix_symbol_awar(j), GBS_global_string("%s_symbol", name));
    }
}

static AWT_predefined_config predefined_helix_configs[] = {
    { "*traditional",
      "Traditional helix settings used up to arb 7.0\n(GG is defined as \"No pair\" here)\n",
      "Default_symbol='';No_Pair='AA AC CC CT CU GG TU';No_Pair_symbol='#';Normal_Pair='GT GU';Normal_Pair_symbol='-';Strong_Pair='CG AT AU';Strong_Pair_symbol='~';"
      "User_Pair='.A .C .G .T .U';User_Pair2='-A -C -G -T -U';User_Pair2_symbol='#';User_Pair3='UU TT';User_Pair3_symbol='+';"
      "User_Pair4='';User_Pair4_symbol='';User_Pair5='';User_Pair5_symbol='';User_Pair6='';User_Pair6_symbol='';User_Pair_symbol='*';Weak_Pair='GA';Weak_Pair_symbol='=';enable='1'" },
    { "*IUPAC_ambiguity_best",
      "Base pairings using ambiguity codes.\nUses best case, i.e. strongest of possible bonds.\nNote: most combinations form a strong bond \nfollowing that approach. They are NOT explicitely \nlisted, instead they use the default symbol.",
      "Default_symbol='~';No_Pair='AA AC CC CT CU TT UU';No_Pair_symbol='#';Normal_Pair='GT GU';Normal_Pair_symbol='-';Strong_Pair='CG AT AU';Strong_Pair_symbol='~';"
      "User_Pair='.A .C .G .T .U .M .R .W .S .Y .K .V .H .D .B .N';User_Pair2='-A -C -G -T -U -M -R -W -S -Y -K -V -H -D -B -N';User_Pair2_symbol='#';User_Pair3='-- .. -.';User_Pair3_symbol='';"
      "User_Pair4='AM CH CM CW CY MM TY UY YY';User_Pair4_symbol='#';User_Pair5='AR AS AV GR RR';User_Pair5_symbol='=';User_Pair6='BT BU DG GK GW KK KT KU ST SU SW';User_Pair6_symbol='-';User_Pair_symbol='*';Weak_Pair='GA GG';Weak_Pair_symbol='=';enable='1'" },
    { "*IUPAC_ambiguity_mean",
      "Base pairings using ambiguity codes.\nUses mean of possible bonds.\nNote: most combinations form a normal bond \nfollowing that approach. They are NOT explicitely \nlisted, instead they use the default symbol.",
      "Default_symbol='=';No_Pair='AA AC CC CT CU TT UU';No_Pair_symbol='#';Normal_Pair='GT GU';Normal_Pair_symbol='-';Strong_Pair='CG AT AU';Strong_Pair_symbol='~';"
      "User_Pair='.A .C .G .T .U .M .R .W .S .Y .K .V .H .D .B .N';User_Pair2='-A -C -G -T -U -M -R -W -S -Y -K -V -H -D -B -N';User_Pair2_symbol='#';User_Pair3='-- .. -.';User_Pair3_symbol='';"
      "User_Pair4='AM AV CH CM CW CY HM HY MM TY UY YY';User_Pair4_symbol='#';User_Pair5='AK BG BR BS CK CR CS GH GM GN GS GV KM KR KV RT RU RY SS';User_Pair5_symbol='-';User_Pair6='GY';User_Pair6_symbol='~';User_Pair_symbol='*';Weak_Pair='GA GG';Weak_Pair_symbol='=';enable='1'" },
    { NULp, NULp, NULp }
};

AW_window *create_helix_props_window(AW_root *awr) {
    AW_window_simple *aws = new AW_window_simple;

    aws->reset_layout("20230607");
    aws->init(awr, "HELIX_PROPS", "HELIX_PROPERTIES");

    aws->at(10, 10);
    aws->auto_space(3, 3);

    aws->callback(AW_POPDOWN);
    aws->create_button("CLOSE", "CLOSE", "C");

    aws->callback(makeHelpCallback("helixsym.hlp"));
    aws->create_button("HELP", "HELP", "H");

    AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "helix", makeConfigSetupCallback(setup_helix_config), NULp, predefined_helix_configs);

    aws->at_newline();

    const size_t max_awar_len = 11;
    aws->label_length(max_awar_len);

    aws->label("Show helix?");
    aws->create_toggle(AWAR_HELIX_ENABLE);

    aws->at_newline();

    {
        int x, y;
        aws->get_at_position(&x, &y);
        aws->at(100, 30); // set minimum window size
        aws->at(x, y);
    }

    const int IF_YSIZE = 32; // lineheight of attached input field
    for (int j = 0; helix_awars[j].awar; j++) {
        int  i = helix_awars[j].pair_type;

        arb_assert(strlen(helix_awars[j].awar) <= max_awar_len);
        aws->label(helix_awars[j].awar);

        aws->create_input_field(helix_symbol_awar(j), 3);

        if (i != HELIX_DEFAULT) {
            aws->at_attach_to(true, false, -10, IF_YSIZE);
            aws->create_input_field(helix_pair_awar(j), 30);
        }

        aws->at_newline();
    }
    aws->window_fit();

    return aws;
}


