// ========================================================= //
//                                                           //
//   File      : ED4_detect_bad_ali.cxx                      //
//   Purpose   : detect alignment errors in helix regions    //
//                                                           //
//   Coded by Ralf Westram (coder@reallysoft.de) in May 23   //
//   http://www.arb-home.de/                                 //
//                                                           //
// ========================================================= //

#include "ed4_detect_bad_ali.hxx"
#include "ed4_class.hxx"

#include <AW_helix.hxx>

#include <aw_awar_defs.hxx>
#include <aw_awar.hxx>
#include <aw_msg.hxx>
#include <aw_select.hxx>
#include <aw_root.hxx>

#include <arbdbt.h>
#include <arb_defs.h>

#include <string>
#include <map>
#include <vector>
#include <items.h>
#include <item_sel_list.h>

using namespace std;

const long NOT_CALCULATED = -1;

typedef map<string, long>            BadPositionsForSpecies;
typedef const BadPositionsForSpecies BadPositionsForSpeciesConst;

typedef map<size_t, size_t> PairedPositions;

class HelixAlignmentQuality : virtual Noncopyable {
    GBDATA          *gb_main;
    const AW_helix&  helix;

    string ali;
    string error;

    char symbol2count[256];

    BadPositionsForSpecies bad_positions;    // list of examined species
    PairedPositions        paired_positions; // list of paring positions of all involved helices

    void note_error(GB_ERROR err) {
        if (err && !has_error()) error = err;
    }

    void init_bad_positions(const char *species_list) {
        bad_positions.clear();
        if (!has_error()) {
            ConstStrArray species;
            GBT_split_string(species, species_list, ";", SPLIT_DROPEMPTY);

            for (size_t i = 0; i<species.size(); ++i) {
                bad_positions[species[i]] = NOT_CALCULATED;
            }
            if (bad_positions.empty()) {
                note_error("No species found");
            }
        }
    }
    void add_pair_position(size_t pos, size_t pair_pos) {
        if (pos>pair_pos) swap(pos, pair_pos);
        arb_assert(pos<pair_pos);
        if (paired_positions.find(pos) != paired_positions.end()) {
            note_error(GBS_global_string("Duplicate entry for helix position %i", info2bio(pos)));
        }
        else {
            paired_positions[pos] = pair_pos;
        }
    }
    void init_paired_positions(const CharPtrArray& helices) {
        paired_positions.clear();
        if (!has_error()) {
            if (helices.size() == 1 && strcmp(helices[0], "*") == 0) {
                // use all helices
                for (long pos = helix.first_pair_position(); pos != -1; pos = helix.next_pair_position(pos)) {
                    const BI_helix_entry& entry = helix.entry(pos);
                    if (entry.helix_nr[0] == '-') { // only add left helices
                        add_pair_position(pos, entry.pair_pos);
                    }
                }
            }
            else {
                for (size_t i = 0; i<helices.size() && !has_error(); ++i) {
                    long firstPos = helix.first_position(helices[i]);
                    if (firstPos == -1) {
                        note_error(GBS_global_string("Invalid helix number '%s'", helices[i]));
                    }
                    else {
                        long lastPos = helix.last_position(helices[i]);
                        arb_assert(lastPos != -1); // otherwise BI_helix is corrupted

                        for (long pos = firstPos; pos<=lastPos && pos>0 && !has_error(); ) {
                            const BI_helix_entry& entry = helix.entry(pos);
                            add_pair_position(pos, entry.pair_pos);
                            pos = entry.next_pair_pos;
                        }
                    }
                }
            }

            if (paired_positions.empty()) {
                note_error("List of helix positions to examine is empty");
            }
        }
    }
    void init_bad_symbol_table(const char *bad_symbols) {
        memset(symbol2count, 0, 256);
        int count = 0;;
        for (int i = 0; bad_symbols[i]; ++i) {
            if (bad_symbols[i] != ' ') { // ignore space
                symbol2count[safeCharIndex(bad_symbols[i])] = 1;
                ++count;
            }
        }
        if (!count) {
            note_error("no 'bad' helix symbol defined");
        }
    }

    long calculate_bad_positions(const string& species) {
        long bad = -1;

        {
            GB_transaction  ta(gb_main);
            GBDATA         *gb_species = GBT_find_species(gb_main, species.c_str());

            if (!gb_species) {
                note_error(GBS_global_string("no such species: '%s'", species.c_str()));
            }
            else {
                GBDATA *gb_seq = GBT_find_sequence(gb_species, ali.c_str());
                if (!gb_seq) {
                    note_error(GBS_global_string("species '%s' has no data in alignment '%s'",
                                                 species.c_str(), ali.c_str()));
                }
                else {
                    GB_CSTR      sequence = GB_read_char_pntr(gb_seq);
                    const size_t seq_len  = GB_read_string_count(gb_seq);

                    bad = 0;
                    for (PairedPositions::const_iterator pp = paired_positions.begin(); pp != paired_positions.end(); ++pp) {
                        const size_t p1 = pp->first;
                        const size_t p2 = pp->second;

                        const char b1 = p1<seq_len ? sequence[p1] : '.';
                        const char b2 = p2<seq_len ? sequence[p2] : '.';

                        const char sym  = helix.get_symbol(b1, b2);
                        bad            += symbol2count[safeCharIndex(sym)];
                    }
                }
            }
        }

        return bad;
    }

    void update_bad_position_counts() {
        for (BadPositionsForSpecies::iterator bp = bad_positions.begin(); bp != bad_positions.end(); ++bp) {
            if (bp->second == NOT_CALCULATED) {
                bp->second = calculate_bad_positions(bp->first);
            }
        }
    }

public:
    HelixAlignmentQuality(GBDATA     *gb_main_, const AW_helix& helix_, const char *ali_,
                          const char *species_list, // @@@ pass ConstStrArray instead?
                          const CharPtrArray& helices, // contains helix numbers to use (as text) or one entry containing a '*' (meaning: all)
                          const char *bad_symbols)
        : gb_main(gb_main_),
          helix(helix_),
          ali(ali_)
    {
        note_error(helix.get_error());
        init_bad_positions(species_list);
        init_paired_positions(helices);
        init_bad_symbol_table(bad_symbols);
    }

    bool has_error() const { return !error.empty(); }
    GB_ERROR get_error() const { return has_error() ? error.c_str() : NULp; }

    size_t size() const { arb_assert(!has_error()); return bad_positions.size(); }
    size_t positions() const { arb_assert(!has_error()); return paired_positions.size(); }

    BadPositionsForSpeciesConst results() {
        arb_assert(!has_error());
        update_bad_position_counts();
        return bad_positions;
    }
};

// --------------------------------------------------------------------------------

class BadPositionsOrder {
    BadPositionsForSpeciesConst bad_pos;

public:
    BadPositionsOrder(BadPositionsForSpeciesConst bad_pos_) :
        bad_pos(bad_pos_)
    {}

    bool operator()(const string& s1, const string& s2) const {
        long b1       = bad_pos.find(s1)->second;
        long b2       = bad_pos.find(s2)->second;
        long cmp      = b2 - b1;
        if (!cmp) cmp = s1.compare(s2);
        return cmp<0;
    }
};

typedef vector<string> StringVector;

static void getSpeciesSortedByBadPositions(BadPositionsForSpeciesConst& bad_pos, StringVector& result) {
    result.clear();

    for (BadPositionsForSpeciesConst::const_iterator s = bad_pos.begin(); s != bad_pos.end(); ++s) {
        long badCount = long(s->second);
        if (badCount>0) result.push_back(s->first);
    }

    sort(result.begin(), result.end(), BadPositionsOrder(bad_pos));
}

static void parse_helix_list(StrArray& result_helices, const char *helix_list) {
    ConstStrArray list_entries;
    GBT_split_string(list_entries, helix_list, ",", SPLIT_KEEPEMPTY);

    for (size_t i = 0; i<list_entries.size(); ++i) {
        const char *entry = list_entries[i];
        if (strcmp(entry, "*") == 0) {
            result_helices.erase();
            result_helices.put(strdup("*"));
            return;
        }
        else {
            const char *minus = strchr(entry, '-');
            if (minus) {
                int h1 = atoi(entry);
                int h2 = atoi(minus+1);

                if (h1>h2) swap(h1, h2);

                for (int h = h1; h<=h2; ++h) {
                    result_helices.put(GBS_global_string_copy("%i", h));
                }
            }
            else {
                // note that 'entry' may be an empty string here. leads to useful error "Invalid helix number ''"
                result_helices.put(strdup(entry));
            }
        }
    }
}

#define StrArray_FROM_HELIXLIST(varname, helixlist) StrArray varname; parse_helix_list(varname, helixlist)

// --------------------------------------------------------------------------------

#ifdef UNIT_TESTS
#ifndef TEST_UNIT_H
#include <test_unit.h>
#endif
#include <arb_strbuf.h>

static char *BadPositionsToString(BadPositionsForSpeciesConst& bad_pos) {
    StringVector sortedSpecies;
    getSpeciesSortedByBadPositions(bad_pos, sortedSpecies);

    GBS_strstruct result(sortedSpecies.size()*(8+1+3+1)+1);

    for (StringVector::const_iterator s = sortedSpecies.begin(); s != sortedSpecies.end(); ++s) {
        const string& species  = *s;
        long          badCount = bad_pos.find(species)->second;
        result.ncat(species.c_str(), species.length());
        result.put('=');
        result.putlong(badCount);
        result.put(',');
    }

    result.cut_tail(1);
    return result.release();
}



void TEST_count_bad_ali_positions() {
    GB_shell shell;
    {
        AW_root  aw_root("min_ascii.arb", UNITTEST_MOCK);
        GBDATA  *gb_main = GB_open("TEST_trees.arb", "r");

        char *ali = GBT_get_default_alignment(gb_main);

        AW_helix helix(&aw_root);
        helix.init(gb_main, ali);

        char *marked_species;
        char *all_species;
        {
            GB_transaction ta(gb_main);

            marked_species = GBT_store_marked_species(gb_main, false);
            GBT_mark_all(gb_main, 1);
            all_species    = GBT_store_marked_species(gb_main, false);
        }

        StrArray_FROM_HELIXLIST(helices_none, "");
        StrArray_FROM_HELIXLIST(helices_all, "*");
        StrArray_FROM_HELIXLIST(helices_1to5, "1-5");
        StrArray_FROM_HELIXLIST(helices_4to5, "4-5");
        StrArray_FROM_HELIXLIST(helices_1, "1");
        StrArray_FROM_HELIXLIST(helices_51, "5,1");
        StrArray_FROM_HELIXLIST(helices_514, "5,1,4");
        StrArray_FROM_HELIXLIST(helices_515, "5,1,5");

        {
            HelixAlignmentQuality marked_quality(gb_main, helix, ali, marked_species, helices_1,    "#");
            HelixAlignmentQuality all_quality   (gb_main, helix, ali, all_species,    helices_51,   "#");
            HelixAlignmentQuality all2_quality  (gb_main, helix, ali, all_species,    helices_514,  "#*+");
            HelixAlignmentQuality all3_quality  (gb_main, helix, ali, all_species,    helices_all,  "#*+");
            HelixAlignmentQuality all4_quality  (gb_main, helix, ali, all_species,    helices_4to5, "#*+");

            TEST_EXPECT_NO_ERROR(marked_quality.get_error());
            TEST_EXPECT_NO_ERROR(all_quality.get_error());
            TEST_EXPECT_NO_ERROR(all2_quality.get_error());
            TEST_EXPECT_NO_ERROR(all3_quality.get_error());
            TEST_EXPECT_NO_ERROR(all4_quality.get_error());

            // test number of examined species:
            TEST_EXPECT_EQUAL(marked_quality.size(),  6);
            TEST_EXPECT_EQUAL(all_quality.size(),    15);
            TEST_EXPECT_EQUAL(all2_quality.size(),   15);
            TEST_EXPECT_EQUAL(all3_quality.size(),   15);
            TEST_EXPECT_EQUAL(all4_quality.size(),   15);

            TEST_EXPECT_EQUAL(marked_quality.positions(), 10);
            TEST_EXPECT_EQUAL(all_quality.positions(), 18);
            TEST_EXPECT_EQUAL(all2_quality.positions(), 20);
            TEST_EXPECT_EQUAL(all3_quality.positions(), 20);
            TEST_EXPECT_EQUAL(all4_quality.positions(), 10);

            TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(BadPositionsToString(marked_quality.results()), "CorGluta=3,CorAquat=1,CytAquat=1");
            TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(BadPositionsToString(all_quality.results()), "CloTyro3=7,CytAquat=7,CloInnoc=3,CorGluta=3,CloBifer=1,CloCarni=1,CloPaste=1,CloTyro2=1,CloTyro4=1,CloTyrob=1,CorAquat=1");
            TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(BadPositionsToString(all2_quality.results()), "CloTyro3=10,CytAquat=8,CloTyro2=7,CloTyro4=7,CloTyrob=7,CorGluta=4,CloInnoc=3,CloBifer=2,CloButy2=1,CloButyr=1,CloCarni=1,CloPaste=1,CorAquat=1");
            TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(BadPositionsToString(all3_quality.results()), "CloTyro3=10,CytAquat=8,CloTyro2=7,CloTyro4=7,CloTyrob=7,CorGluta=4,CloInnoc=3,CloBifer=2,CloButy2=1,CloButyr=1,CloCarni=1,CloPaste=1,CorAquat=1");
            TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(BadPositionsToString(all4_quality.results()), "CytAquat=6,CloTyro3=5,CloInnoc=2,CloTyro2=2,CloTyro4=2,CloTyrob=2,CloBifer=1");
        }

        // test errors:
        {
            HelixAlignmentQuality err1(gb_main, helix, ali, "",             helices_51,   "#");
            HelixAlignmentQuality err2(gb_main, helix, ali, marked_species, helices_none, "#");
            HelixAlignmentQuality err3(gb_main, helix, ali, marked_species, helices_51,   "  "); // define no bad helix symbol (spaces are ignored)
            HelixAlignmentQuality err4(gb_main, helix, ali, marked_species, helices_515,  "#");
            HelixAlignmentQuality err5(gb_main, helix, ali, "fakeSpec",     helices_51,   "#");
            HelixAlignmentQuality err6(gb_main, helix, ali, marked_species, helices_1to5, "#");

            TEST_EXPECT_ERROR_CONTAINS(err1.get_error(), "No species found");
            TEST_EXPECT_ERROR_CONTAINS(err2.get_error(), "Invalid helix number ''");
            TEST_EXPECT_ERROR_CONTAINS(err3.get_error(), "no 'bad' helix symbol defined");
            TEST_EXPECT_ERROR_CONTAINS(err4.get_error(), "Duplicate entry for helix position 65");

            TEST_EXPECT_NO_ERROR(err5.get_error()); // no error yet
            TEST_EXPECT_EQUAL(err5.size(), 1);      // number of species
            TEST_EXPECT_EQUAL(err5.positions(), 18);
            err5.results();                         // triggers error
            TEST_EXPECT_ERROR_CONTAINS(err5.get_error(), "no such species: 'fakeSpec'");

            TEST_EXPECT_ERROR_CONTAINS(err6.get_error(), "Invalid helix number '2'");
        }

        free(all_species);
        free(marked_species);
        free(ali);

        GB_close(gb_main);
    }
}

#endif // UNIT_TESTS

// --------------------------------------------------------------------------------

#define AWAR_BADALI_BASE      "badali/"
#define AWAR_BADALI_HELIXLIST AWAR_BADALI_BASE "helixlist" // helix-numbers (comma-separated)
#define AWAR_BADALI_SYMBOLS   AWAR_BADALI_BASE "symbols"   // helix-symbols which count as "bad"

#define AWAR_BADALI_TEMP    "tmp/" AWAR_BADALI_BASE
#define AWAR_BADALI_SPECIES AWAR_BADALI_TEMP "selected" // selected species
#define AWAR_BADALI_FIELD   AWAR_BADALI_TEMP "field"    // name of field to store bad positions

void ED4_create_detect_bad_alignment_awars(AW_root *aw_root, AW_default aw_def) {
    aw_root->awar_string(AWAR_BADALI_HELIXLIST, "",  aw_def)->set_srt(" =,:,,=,");
    aw_root->awar_string(AWAR_BADALI_SYMBOLS,   "#", aw_def);
    aw_root->awar_string(AWAR_BADALI_SPECIES,   "",  aw_def);
    aw_root->awar_string(AWAR_BADALI_FIELD,     "",  aw_def);
}

static ARB_ERROR add_species_to_list_cb(ED4_base *base, StrArray *species) {
    ARB_ERROR error = NULp;

    if (base->is_species_manager()) {
        ED4_species_manager       *species_manager       = base->to_species_manager();
        ED4_species_name_terminal *species_name_terminal = species_manager->search_spec_child_rek(LEV_SPECIES_NAME)->to_species_name_terminal();

        if (species_name_terminal->get_species_pointer() && !species_manager->is_SAI_manager()) {
            char *species_name = GB_read_as_string(species_name_terminal->get_species_pointer());
            species->put(species_name);
        }
    }

    return error;
}

static char *create_list_of_loaded_species() {
    GB_transaction ta(ED4_ROOT->get_gb_main());
    StrArray       species;
    ARB_ERROR      error    = ED4_ROOT->root_group_man->route_down_hierarchy(makeED4_route_cb(add_species_to_list_cb, &species));
    aw_message_if(error.deliver());
    return GBT_join_strings(species, ';');
}

static void calc_and_update_alignment_errors_cb(AW_window *aww, AW_selection_list *sellst) {
    // Performs the following jobs:
    // - calculate alignment errors.
    // - update the list of worst helix alignments (in GUI).
    // - update/write contents of database field (alignment error count; optional)

    AW_root    *aw_root = aww->get_root();
    GBDATA     *gb_main = ED4_ROOT->get_gb_main();
    const char *target_field;

    {
        GB_transaction ta(gb_main);

        target_field = prepare_and_get_selected_itemfield(aw_root, AWAR_BADALI_FIELD, gb_main, SPECIES_get_selector(), FIF_ALLOW_NONE);
        if (!target_field) {
            if (GB_have_error()) {
                aw_message(GBS_global_string("Invalid target field selected: %s", GB_await_error()));
                return;
            }
        }
    }

    char *species_list = create_list_of_loaded_species();
    char *bad_symbols  = aw_root->awar(AWAR_BADALI_SYMBOLS)->read_string();

    StrArray_FROM_HELIXLIST(helices, aw_root->awar(AWAR_BADALI_HELIXLIST)->read_char_pntr());

    // @@@ better show progress (next command may need much time)
    HelixAlignmentQuality quality(gb_main,
                                  *ED4_ROOT->helix,
                                  ED4_ROOT->get_alignment_name(),
                                  species_list,
                                  helices,
                                  bad_symbols);

    sellst->clear();
    if (!quality.has_error()) {
        BadPositionsForSpeciesConst& bad_pos = quality.results();
        if (!quality.has_error()) {
            const bool writeToField = target_field;
            if (writeToField) {
                GB_transaction  ta(gb_main);
                GB_ERROR        error           = NULp;
                GBDATA         *gb_species_data = GBT_get_species_data(gb_main);

                for (BadPositionsForSpeciesConst::const_iterator bp = bad_pos.begin(); bp != bad_pos.end() && !error; ++bp) { // this loop includes zero counts
                    const char* species_name  = bp->first.c_str();
                    long        bad_positions = bp->second;

                    GBDATA *gb_species = GBT_find_species_rel_species_data(gb_species_data, species_name);
                    if (!gb_species) {
                        error = GBS_global_string("Could not find species '%s' (Reason: %s)", species_name, GB_await_error());
                    }
                    else {
                        GBDATA *gb_field     = GBT_searchOrCreate_itemfield_according_to_changekey(gb_species, target_field, SPECIES_get_selector().change_key_path);
                        if (!gb_field) error = GB_await_error();
                        if (!error) error    = GB_write_lossless_int(gb_field, bad_positions);
                    }
                }

                aw_message_if(error);
            }

            StringVector order;
            getSpeciesSortedByBadPositions(bad_pos, order);

            for (StringVector::const_iterator species = order.begin(); species != order.end(); ++species) { // this loop excludes zero counts
                const char *species_name = species->c_str();
                size_t      bad          = bad_pos.find(*species)->second;

                sellst->insert(GBS_global_string("%-8s | %zu", species_name, bad), species_name);
            }
            sellst->insert_default("<acceptable helix alignment>", "");
        }
    }

    if (quality.has_error()) {
        sellst->clear();
        sellst->insert_default(GBS_global_string("<Error: %s>", quality.get_error()), "");
    }

    sellst->update();

    free(bad_symbols);
    free(species_list);
}

enum JumpWhy { BY_SELECTION, BY_BUTTON };

static void jump_to_next_helix_cb(AW_window *, JumpWhy called) {
    ED4_MostRecentWinContext context;

    ED4_window *win     = current_ed4w();
    AW_root    *aw_root = win->aww->get_root();

    StrArray_FROM_HELIXLIST(helices, aw_root->awar(AWAR_BADALI_HELIXLIST)->read_char_pntr());

    AW_awar *awar_helixnr    = aw_root->awar(win->awar_path_for_helixNr);
    int      current_helixnr = atoi(awar_helixnr->read_char_pntr());
    int      wanted_helixnr  = 0;
    int      preference      = current_helixnr>0 ? 1 : -1;

    if (current_helixnr == 0) {
        wanted_helixnr = atoi(helices[0]);
    }
    else {
        if (called == BY_SELECTION) {
            wanted_helixnr = abs(current_helixnr);
        }
        else { // if called == BY_BUTTON => select new helix
            wanted_helixnr = atoi(helices[0]);
            for (size_t i = 0; i<helices.size()-1; ++i) {
                if (abs(current_helixnr) == abs(atoi(helices[i])))  {
                    wanted_helixnr = atoi(helices[i+1]);
                }
            }
        }
    }

    wanted_helixnr *= preference; // choose left or right helix

    if (wanted_helixnr != 0) {
        if (wanted_helixnr != current_helixnr || called == BY_BUTTON) { // avoid triggering callback w/o real change
            awar_helixnr->write_string(GBS_global_string("%i", wanted_helixnr));
            // callback is NOT bound to AWAR (but to inputfield) -> call it manually:
            ED4_set_helixnr(win->aww, win->awar_path_for_helixNr);
        }
    }
    else {
        aw_message("No helix to jump to.");
    }
}

enum SelectedAwar { SELECTED_SPECIES, SELECTED_BAD_ALI };

static void selected_changed_cb(AW_root *aw_root, SelectedAwar whatChanged) {
    static bool in_callback = false;

    if (!in_callback) {
        LocallyModify<bool> flag(in_callback, true);

        const char *source_awar_name = AWAR_BADALI_SPECIES;
        const char *dest_awar_name   = AWAR_SPECIES_NAME;

        if (whatChanged == SELECTED_SPECIES) {
            swap(source_awar_name, dest_awar_name);
        }

        const char *selected_species = aw_root->awar(source_awar_name)->read_char_pntr();
        if (selected_species[0]) { // if nothing selected => do not change other AWAR
            aw_root->awar(dest_awar_name)->write_string(selected_species);
            if (SELECTED_BAD_ALI) {
                jump_to_next_helix_cb(NULp, BY_SELECTION);
            }
        }
    }
}

void ED4_popup_detect_bad_alignment_window(AW_window *editor_window, const WindowCallback *helixSettings_cb) {
    static AW_window_simple *aws = NULp;

    ED4_LocalWinContext uses(editor_window);

    if (!aws) {
        AW_root *aw_root = editor_window->get_root();

        aws = new AW_window_simple;

        aws->init(aw_root, "DETECT_BAD_ALI", "Detect bad helix alignment");
        aws->load_xfig("edit4/detect_bad_ali.fig");

        aws->button_length(10);

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

        aws->at("help");
        aws->callback(makeHelpCallback("bad_alignment.hlp"));
        aws->create_button("HELP", "HELP", "H");

        aws->at("list");
        AW_selection_list *sellst = aws->create_selection_list(AWAR_BADALI_SPECIES);
        sellst->insert_default("<not calculated>", "");
        sellst->update();

        aws->at("config");
        aws->auto_space(5, 5);
        aws->label_length(20);

        aws->label("Helices to examine\n"
                   "(comma-separated list)");
        aws->create_input_field(AWAR_BADALI_HELIXLIST, 15);

        aws->callback(makeWindowCallback(jump_to_next_helix_cb, BY_BUTTON));
        aws->create_autosize_button("JUMP", "Jump", "J");

        aws->at_newline();

        aws->label("Detected symbols");
        aws->create_input_field(AWAR_BADALI_SYMBOLS, 5);

        aws->callback(*helixSettings_cb);
        aws->create_autosize_button("HELIX_SETTINGS", "Helix settings", "H");

        aws->at_newline();
        aws->label("Write to database field");
        create_itemfield_selection_button(aws, FieldSelDef(AWAR_BADALI_FIELD, ED4_ROOT->get_gb_main(), SPECIES_get_selector(), FIELD_FILTER_INT_WRITEABLE, "target field", SF_ALLOW_NEW), NULp);

        aws->at("calc");
        aws->callback(makeWindowCallback(calc_and_update_alignment_errors_cb, sellst));
        aws->create_button("CALCULATE", "Calculate", "C");

        aw_root->awar(AWAR_BADALI_SPECIES)->add_callback(makeRootCallback(selected_changed_cb, SELECTED_BAD_ALI));
        aw_root->awar(AWAR_SPECIES_NAME)->add_callback(makeRootCallback(selected_changed_cb, SELECTED_SPECIES));
    }

    e4_assert(aws);
    aws->activate();

}


