//  ==================================================================== //
//                                                                       //
//    File      : SQ_main.cxx                                            //
//    Purpose   : Entrypoint to Seq. Quality analysis; calls functions   //
//                                                                       //
//                                                                       //
//  Coded by Juergen Huber in July 2003 - February 2004                  //
//  Coded by Kai Bader (baderk@in.tum.de) in 2007 - 2008                 //
//  Copyright Department of Microbiology (Technical University Munich)   //
//                                                                       //
//  Visit our web site at: http://www.arb-home.de/                       //
//                                                                       //
//  ==================================================================== //

#include "seq_quality.h"
#include "SQ_functions.h"

#include <ali_filter.hxx>
#include <sel_boxes.hxx>

#include <aw_awars.hxx>
#include <aw_msg.hxx>
#include <aw_root.hxx>

#include <arb_progress.h>
#include <TreeNode.h>
#include <arb_global_defs.h>
#include <config_manager.hxx>

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

#define AWAR_SQ_PERM "seq_quality/"     // saved in properties
#define AWAR_SQ_TEMP "tmp/seq_quality/" // not saved in properties

#define AWAR_SQ_WEIGHT_BASES     AWAR_SQ_PERM "weight_bases"
#define AWAR_SQ_WEIGHT_DEVIATION AWAR_SQ_PERM "weight_deviation"
#define AWAR_SQ_WEIGHT_HELIX     AWAR_SQ_PERM "weight_helix"
#define AWAR_SQ_WEIGHT_CONSENSUS AWAR_SQ_PERM "weight_consensus"
#define AWAR_SQ_WEIGHT_IUPAC     AWAR_SQ_PERM "weight_iupac"
#define AWAR_SQ_WEIGHT_GC        AWAR_SQ_PERM "weight_gc"

#define AWAR_SQ_MARK_ONLY_FLAG AWAR_SQ_PERM "mark_only_flag"
#define AWAR_SQ_MARK_FLAG      AWAR_SQ_PERM "mark_flag"
#define AWAR_SQ_MARK_BELOW     AWAR_SQ_PERM "mark_below"
#define AWAR_SQ_REEVALUATE     AWAR_SQ_PERM "reevaluate"
#define AWAR_SQ_FILTER_NAME    AWAR_SQ_TEMP "filter/name"

void SQ_create_awars(AW_root *aw_root, AW_default aw_def) {
    aw_root->awar_int(AWAR_SQ_WEIGHT_BASES,     5,  aw_def);
    aw_root->awar_int(AWAR_SQ_WEIGHT_DEVIATION, 15, aw_def);
    aw_root->awar_int(AWAR_SQ_WEIGHT_HELIX,     15, aw_def);
    aw_root->awar_int(AWAR_SQ_WEIGHT_CONSENSUS, 50, aw_def);
    aw_root->awar_int(AWAR_SQ_WEIGHT_IUPAC,     5,  aw_def);
    aw_root->awar_int(AWAR_SQ_WEIGHT_GC,        10, aw_def);
    aw_root->awar_int(AWAR_SQ_MARK_ONLY_FLAG,   0,  aw_def);
    aw_root->awar_int(AWAR_SQ_MARK_FLAG,        1,  aw_def);
    aw_root->awar_int(AWAR_SQ_MARK_BELOW,       40, aw_def);
    aw_root->awar_int(AWAR_SQ_REEVALUATE,       0,  aw_def);

    awt_create_filter_awars(aw_root, aw_def, AWAR_SQ_FILTER_NAME, AWAR_DEFAULT_ALIGNMENT);
}

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


inline size_t count_nodes(TreeNode *node) {
    // calculate number of nodes in tree
    return GBT_count_leafs(node)*2-1;
}

static void sq_calc_seq_quality_cb(AW_window *aww, adfiltercbstruct *acbs, GBDATA *gb_main) {
    AW_root        *aw_root     = aww->get_root();
    GB_ERROR        error       = NULp;
    TreeNode       *tree        = NULp;
    bool            marked_only = (aw_root->awar(AWAR_SQ_MARK_ONLY_FLAG)->read_int() > 0);
    arb_progress    main_progress("Calculating sequence quality");
    GB_transaction  ta(gb_main);

    if (!error) {
        char *ali = GBT_get_default_alignment(gb_main);
        if (ali) {
            GB_alignment_type type = GBT_get_alignment_type(gb_main, ali);
            if (type == GB_AT_UNKNOWN) {
                error = GB_await_error();
            }
            else if (type != GB_AT_RNA && type != GB_AT_DNA) {
                error = "does only support RNA/DNA sequence data";
            }
            if (error) {
                error = GBS_global_string("Invalid alignment '%s' selected (%s)", ali, error);
            }
        }
        else {
            error = "No default alignment selected";
        }
        free(ali);
    }

    if (!error) {
        char *treename = aw_root->awar(AWAR_TREE)->read_string();

        if (treename && strcmp(treename, NO_TREE_SELECTED) != 0) {
            if (!error) {
                tree = GBT_read_tree(gb_main, treename, new SimpleRoot);
                if (!tree) error = GB_await_error();
                else {
                    error = GBT_link_tree(tree, gb_main, false, NULp, NULp);
                    if (!error) {
                        GBT_TreeRemoveType mode = marked_only ? GBT_KEEP_MARKED : GBT_REMOVE_ZOMBIES;
                        tree = GBT_remove_leafs(tree, mode, NULp, NULp, NULp);
                        if (!tree || tree->is_leaf()) {
                            error = GBS_global_string("Tree contains less than 2 species after removing zombies%s",
                                                      marked_only ? " and non-marked" : "");
                        }
                    }
                }
            }
        }
        free(treename);
    }

    // if tree == 0 -> do basic quality calculations that are possible without tree information
    // otherwise    -> use all groups found in tree and compare sequences against the groups they are contained in

    if (!error) {
        struct SQ_weights weights;

        weights.bases             = aw_root->awar(AWAR_SQ_WEIGHT_BASES)->read_int();
        weights.diff_from_average = aw_root->awar(AWAR_SQ_WEIGHT_DEVIATION)->read_int();
        weights.helix             = aw_root->awar(AWAR_SQ_WEIGHT_HELIX)->read_int();
        weights.consensus         = aw_root->awar(AWAR_SQ_WEIGHT_CONSENSUS)->read_int();
        weights.iupac             = aw_root->awar(AWAR_SQ_WEIGHT_IUPAC)->read_int();
        weights.gc                = aw_root->awar(AWAR_SQ_WEIGHT_GC)->read_int();

        int mark_flag  = aw_root->awar(AWAR_SQ_MARK_FLAG)->read_int();
        int mark_below = aw_root->awar(AWAR_SQ_MARK_BELOW)->read_int();
        int reevaluate = aw_root->awar(AWAR_SQ_REEVALUATE)->read_int();

        // Load and use Sequence-Filter
        AP_filter *filter = awt_get_filter(acbs);
        error             = awt_invalid_filter(filter);

        /*
          SQ_evaluate() generates the final estimation for the quality of an alignment.
          It takes the values from the different containers, which are generated by the other functions, weights them
          and calculates a final value. The final value is stored in "value_of_evaluation" (see options).
          With the values stored in "weights" one can customize how important a value stored in a container becomes
          for the final result.
        */

        if (!error) {
            if (!tree) {
                if (reevaluate) {
                    error = SQ_mark_species(gb_main, mark_below, marked_only);
                }
                else {
                    arb_progress  progress(GBT_get_species_count(gb_main)*2);
                    SQ_GroupData *globalData = new SQ_GroupData_RNA;

                    progress.subtitle("pass1");
                    error = SQ_pass1_no_tree(globalData, gb_main, filter, progress);
                    if (!error) {
                        progress.subtitle("pass2");
                        error = SQ_pass2_no_tree(globalData, gb_main, filter, progress);
                        if (!error) {
                            error = SQ_evaluate(gb_main, weights, marked_only);
                            if (mark_flag && !error) {
                                error = SQ_mark_species(gb_main, mark_below, marked_only);
                            }
                        }
                    }
                    if (error) progress.done();
                    delete globalData;
                }
            }
            else {
                SQ_TREE_ERROR check = SQ_check_tree_structure(tree);
                if (check != NONE) {
                    switch (check) {
                        case ZOMBIE:
                            error = "Found one or more zombies in the tree.\n"
                                "Please remove them or use another tree before running the quality check tool.";
                            break;
                        case MISSING_NODE:
                            error = "Missing node(s) or unusable tree structure.\n"
                                "Please fix the tree before running the quality check tool.";
                            break;
                        default:
                            error = "An error occurred while traversing the tree.\n"
                                "Please fix the tree before running the quality check tool.";
                            break;
                    }
                }
                else if (reevaluate) {
                    error = SQ_mark_species(gb_main, mark_below, marked_only);
                }
                else {
                    arb_progress progress(count_nodes(tree)*2);
                    SQ_GroupData *globalData = new SQ_GroupData_RNA;

                    progress.subtitle("pass1");
                    error = SQ_pass1_on_tree(tree, gb_main, globalData, filter, progress);
                    if (!error) {
                        progress.subtitle("pass2");
                        error = SQ_pass2_on_tree(tree, gb_main, globalData, filter, progress);
                        if (!error) {
                            error = SQ_evaluate(gb_main, weights, marked_only);
                            if (mark_flag && !error) {
                                error = SQ_mark_species(gb_main, mark_below, marked_only);
                            }
                        }
                    }
                    if (error) progress.done();
                    delete globalData;
                }
            }
        }
        awt_destroy_filter(filter);
    }

    error = ta.close(error);
    if (error) aw_message(error);

    SQ_clear_group_dictionary();
    UNCOVERED();
    destroy(tree);
}

static void sq_remove_quality_entries_cb(AW_window*, GBDATA *gb_main) {
    GB_ERROR error = SQ_remove_quality_entries(gb_main);
    aw_message_if(error);
}

static AWT_config_mapping_def seq_quality_config_mapping[] = {
    { AWAR_SQ_WEIGHT_BASES,     "wbases" },
    { AWAR_SQ_WEIGHT_DEVIATION, "wdeviation" },
    { AWAR_SQ_WEIGHT_HELIX,     "whelix" },
    { AWAR_SQ_WEIGHT_CONSENSUS, "wconsens" },
    { AWAR_SQ_WEIGHT_IUPAC,     "wiupac" },
    { AWAR_SQ_WEIGHT_GC,        "wgc" },
    { AWAR_SQ_MARK_ONLY_FLAG,   "onlymarked" },
    { AWAR_SQ_MARK_FLAG,        "markbad" },
    { AWAR_SQ_MARK_BELOW,       "markbelow" },
    { AWAR_SQ_REEVALUATE,       "reeval" },

    { NULp, NULp }
};

AW_window *SQ_create_seq_quality_window(AW_root *aw_root, GBDATA *gb_main) {
    // create window for sequence quality calculation (called only once)

    AW_window_simple *aws = new AW_window_simple;

    aws->init(aw_root, "CALC_SEQ_QUALITY", "CALCULATE SEQUENCE QUALITY");
    aws->load_xfig("seq_quality.fig");

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

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

    aws->at("base");
    aws->create_input_field(AWAR_SQ_WEIGHT_BASES, 3);

    aws->at("deviation");
    aws->create_input_field(AWAR_SQ_WEIGHT_DEVIATION, 3);

    aws->at("no_helices");
    aws->create_input_field(AWAR_SQ_WEIGHT_HELIX, 3);

    aws->at("consensus");
    aws->create_input_field(AWAR_SQ_WEIGHT_CONSENSUS, 3);

    aws->at("iupac");
    aws->create_input_field(AWAR_SQ_WEIGHT_IUPAC, 3);

    aws->at("gc_proportion");
    aws->create_input_field(AWAR_SQ_WEIGHT_GC, 3);

    aws->at("monly");
    aws->create_toggle(AWAR_SQ_MARK_ONLY_FLAG);

    aws->at("mark");
    aws->create_toggle(AWAR_SQ_MARK_FLAG);

    aws->at("mark_below");
    aws->create_input_field(AWAR_SQ_MARK_BELOW, 3);

    aws->at("tree");
    awt_create_TREE_selection_list(gb_main, aws, AWAR_TREE);

    aws->at("filter");
    adfiltercbstruct *adfilter = awt_create_select_filter(aws->get_root(), gb_main, AWAR_SQ_FILTER_NAME);
    aws->callback(makeCreateWindowCallback(awt_create_select_filter_win, adfilter));
    aws->create_button("SELECT_FILTER", AWAR_SQ_FILTER_NAME);

    aws->at("go");
    aws->callback(makeWindowCallback(sq_calc_seq_quality_cb, adfilter, gb_main));
    aws->highlight();
    aws->create_button("GO", "GO", "G");

    aws->at("config");
    AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "seq_quality", seq_quality_config_mapping);

    aws->at("reevaluate");
    aws->label("Re-Evaluate only");
    aws->create_toggle(AWAR_SQ_REEVALUATE);

    aws->at("remove");
    aws->callback(makeWindowCallback(sq_remove_quality_entries_cb, gb_main));
    aws->create_button("Remove", "Remove", "R");

    return aws;
}
