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

#include "NT_local.h"

#include <TreeDisplay.hxx>

#include <sel_boxes.hxx>
#include <selection_admin.h>
#include <config_manager.hxx>

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

#include <ad_config.h>
#include <ad_cb_prot.h>

#include <map>

using namespace std;

// AWT_canvas-local (%i=canvas-id):
#define AWAR_CL_SELECTED_CONFIGS       "configuration_data/win%i/selected"
#define AWAR_CL_DISPLAY_CONFIG_MARKERS "configuration_data/win%i/display"

#define AWAR_CONFIG_COMMENT "tmp/configuration/comment"
#define AWAR_CONFIGURATION  "focus/configuration"

typedef map<string, string> ConfigHits; // key=speciesname; value[markerIdx]==1 -> highlighted

class ConfigMarkerDisplay FINAL_TYPE : public MarkerDisplay, virtual Noncopyable {
    GBDATA                  *gb_main;
    SmartPtr<ConstStrArray>  config; // configuration names
    StrArray                 errors; // config load-errors
    ConfigHits               hits;

    void updateHits() {
        flush_cache();
        hits.clear();
        errors.erase();
        for (int c = 0; c<size(); ++c) {
            GB_ERROR   error;
            GBT_config cfg(gb_main, (*config)[c], error);

            for (int area = 0; area<=1 && !error; ++area) {
                GBT_config_parser cparser(cfg, area);

                while (1) {
                    const GBT_config_item& item = cparser.nextItem(error);
                    if (error || item.type == CI_END_OF_CONFIG) break;
                    if (item.type == CI_SPECIES) {
                        ConfigHits::iterator found = hits.find(item.name);
                        if (found == hits.end()) {
                            string h(size(), '0');
                            h[c]            = '1';
                            hits[item.name] = h;
                        }
                        else {
                            (found->second)[c] = '1';
                        }
                    }
                }
            }

            errors.put(ARB_strdup(null2empty(error)));
        }
    }

public:
    ConfigMarkerDisplay(SmartPtr<ConstStrArray> config_, GBDATA *gb_main_)
        : MarkerDisplay(config_->size()),
          gb_main(gb_main_),
          config(config_)
    {
        updateHits();
    }
    const char *get_marker_name(int markerIdx) const OVERRIDE {
        const char *error = errors[markerIdx];
        const char *name  = (*config)[markerIdx];
        if (error && error[0]) return GBS_global_string("%s (Error: %s)", name, error);
        return name;
    }
    void retrieve_marker_state(const char *speciesName, NodeMarkers& node) OVERRIDE {
        ConfigHits::const_iterator found = hits.find(speciesName);
        if (found != hits.end()) {
            const string& hit = found->second;

            for (int c = 0; c<size(); ++c) {
                if (hit[c] == '1') node.incMarker(c);
            }
        }
        node.incNodeSize();
    }

    void handle_click(int markerIdx, AW_MouseButton button, AWT_graphic_exports& exports) OVERRIDE {
        if (button == AW_BUTTON_LEFT || button == AW_BUTTON_RIGHT) {
            const char *markerName = get_marker_name(markerIdx);
            AW_root::SINGLETON->awar(AWAR_CONFIGURATION)->write_string(markerName); // select config of clicked marker
            if (button == AW_BUTTON_RIGHT) { // extract configuration
                extract_species_selection(GLOBAL.gb_main, markerName, SELECTION_EXTRACT);
                exports.request_structure_update(); // request to recalculate branch colors
            }
        }
    }
};

inline bool displays_config_markers(MarkerDisplay *md) { return dynamic_cast<ConfigMarkerDisplay*>(md); }

#define CONFIG_SEPARATOR "\1"

inline AW_awar *get_canvas_awar(const char *awar_name_format, int canvas_id) {
    return AW_root::SINGLETON->awar_no_error(GBS_global_string(awar_name_format, canvas_id));
}
inline AW_awar *get_config_awar        (int canvas_id) { return get_canvas_awar(AWAR_CL_SELECTED_CONFIGS,       canvas_id); }
inline AW_awar *get_display_toggle_awar(int canvas_id) { return get_canvas_awar(AWAR_CL_DISPLAY_CONFIG_MARKERS, canvas_id); }

static SmartPtr<ConstStrArray> get_selected_configs_from_awar(int canvas_id) {
    // returns configs stored in awar as array (empty array if awar undefined!)
    SmartPtr<ConstStrArray> config(new ConstStrArray);

    AW_awar *awar = get_config_awar(canvas_id);
    if (awar) {
        char *config_str = awar->read_string();
        GBT_splitNdestroy_string(*config, config_str, CONFIG_SEPARATOR, SPLIT_DROPEMPTY);
    }

    return config;
}
static void write_configs_to_awar(int canvas_id, const CharPtrArray& configs) {
    char *config_str = GBT_join_strings(configs, CONFIG_SEPARATOR[0]);
    AW_root::SINGLETON->awar(GBS_global_string(AWAR_CL_SELECTED_CONFIGS, canvas_id))->write_string(config_str);
    free(config_str);
}

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

static AW_selection *selected_configs_list[MAX_NT_WINDOWS] = { MAX_NT_WINDOWS_NULLINIT };
static bool allow_selection2awar_update = true;
static bool allow_to_activate_display   = false;

static void init_config_awar(AW_root *root) {
    root->awar_string(AWAR_CONFIGURATION, DEFAULT_CONFIGURATION, GLOBAL.gb_main);
}
static void selected_configs_awar_changed_cb(AW_root *aw_root, TREE_canvas *ntw) {
    AWT_graphic_tree        *agt    = DOWNCAST(AWT_graphic_tree*, ntw->gfx);
    int                      ntw_id = ntw->get_index();
    SmartPtr<ConstStrArray>  config = get_selected_configs_from_awar(ntw_id);
    bool                     redraw = false;

    if (config->empty() || get_display_toggle_awar(ntw_id)->read_int() == 0) {
        if (displays_config_markers(agt->get_marker_display())) { // only hide config markers
            agt->hide_marker_display();
            redraw = true;
        }
    }
    else {
        bool activate = allow_to_activate_display || displays_config_markers(agt->get_marker_display());

        if (activate) {
            init_config_awar(aw_root); // required here if markers are enabled in saved DB + then right-clicking a marker
            ConfigMarkerDisplay *disp = new ConfigMarkerDisplay(config, ntw->gb_main);
            agt->set_marker_display(disp);
            redraw = true;
        }
    }

    if (selected_configs_list[ntw_id]) { // if configuration_marker_window has been opened
        // update content of subset-selection (needed when reloading a config-set (not implemented yet) or after renaming a config)
        LocallyModify<bool> avoid(allow_selection2awar_update, false);
        awt_set_subset_selection_content(selected_configs_list[ntw_id], *config);
    }

    if (redraw) AW_root::SINGLETON->awar(AWAR_TREE_REFRESH)->touch();
}

static void selected_configs_display_awar_changed_cb(AW_root *root, TREE_canvas *ntw) {
    LocallyModify<bool> allowInteractiveActivation(allow_to_activate_display, true);
    selected_configs_awar_changed_cb(root, ntw);
}

static void configs_selectionlist_changed_cb(AW_selection *selected_configs, bool interactive_change, AW_CL ntw_id) {
    if (allow_selection2awar_update) {
        LocallyModify<bool> allowInteractiveActivation(allow_to_activate_display, interactive_change);

        StrArray config;
        selected_configs->get_values(config);
        write_configs_to_awar(ntw_id, config);
    }
}

static void config_modified_cb(GBDATA *gb_cfg_area) { // called with "top_area" AND "middle_area" entry!
    static GBDATA   *gb_lastname = NULp;
    static GB_ULONG  lastcall     = 0;

    GBDATA   *gb_name  = GB_entry(GB_get_father(gb_cfg_area), "name");
    GB_ULONG  thiscall = GB_time_of_day();

    bool is_same_modification = gb_name == gb_lastname && (thiscall == lastcall || thiscall == (lastcall+1));
    if (!is_same_modification) { // avoid duplicate check if "top_area" and "middle_area" changed (=standard case)
        // touch all canvas-specific awars that contain 'name'
        const char *name = GB_read_char_pntr(gb_name);

        for (int canvas_id = 0; canvas_id<MAX_NT_WINDOWS; ++canvas_id) {
            SmartPtr<ConstStrArray> config = get_selected_configs_from_awar(canvas_id);
            for (size_t c = 0; c<config->size(); ++c) {
                if (strcmp((*config)[c], name) == 0) {
                    get_config_awar(canvas_id)->touch();
                    break;
                }
            }
        }
    }
    gb_lastname = gb_name;
    lastcall    = thiscall;
}

#define CONFIG_BASE_PATH "/configuration_data/configuration"

static void install_config_change_callbacks(GBDATA *gb_main) {
    static bool installed = false;
    if (!installed) {
        DatabaseCallback dbcb = makeDatabaseCallback(config_modified_cb);
        ASSERT_NO_ERROR(GB_add_hierarchy_callback(gb_main, CONFIG_BASE_PATH "/middle_area", GB_CB_CHANGED, dbcb));
        ASSERT_NO_ERROR(GB_add_hierarchy_callback(gb_main, CONFIG_BASE_PATH "/top_area",    GB_CB_CHANGED, dbcb));

        installed = true;
    }
}

void NT_activate_configMarkers_display(TREE_canvas *ntw) {
    GBDATA *gb_main = ntw->gb_main;

    int      ntw_idx      = ntw->get_index();
    AW_awar *awar_selCfgs = ntw->awr->awar_string(GBS_global_string(AWAR_CL_SELECTED_CONFIGS, ntw_idx), "", gb_main);
    awar_selCfgs->add_callback(makeRootCallback(selected_configs_awar_changed_cb, ntw));

    AW_awar *awar_dispCfgs = ntw->awr->awar_int(GBS_global_string(AWAR_CL_DISPLAY_CONFIG_MARKERS, ntw_idx), 1, gb_main);
    awar_dispCfgs->add_callback(makeRootCallback(selected_configs_display_awar_changed_cb, ntw));

    awar_selCfgs->touch(); // force initial refresh
    install_config_change_callbacks(gb_main);
}

// define where to store config-sets (using config-manager):
#define MANAGED_CONFIGSET_SECTION "configmarkers"
#define MANAGED_CONFIGSET_ENTRY   "selected_configs"

static void setup_configmarker_config_cb(AWT_config_definition& config, int ntw_id) {
    AW_awar *selcfg_awar = get_config_awar(ntw_id);
    nt_assert(selcfg_awar);
    if (selcfg_awar) {
        config.add(selcfg_awar->awar_name, MANAGED_CONFIGSET_ENTRY);
    }
}

struct ConfigModifier : virtual Noncopyable {
    virtual ~ConfigModifier() {}
    virtual const char *modify(const char *old) const = 0;

    bool modifyConfig(ConstStrArray& config) const {
        bool changed = false;
        for (size_t i = 0; i<config.size(); ++i) {
            const char *newContent = modify(config[i]);
            if (!newContent) {
                config.remove(i);
                changed = true;
            }
            else if (strcmp(newContent, config[i]) != 0) {
                config.replace(i, newContent);
                changed = true;
            }
        }
        return changed;
    }
};
class ConfigRenamer : public ConfigModifier { // derived from Noncopyable
    const char *oldName;
    const char *newName;
    const char *modify(const char *name) const OVERRIDE {
        return strcmp(name, oldName) == 0 ? newName : name;
    }
public:
    ConfigRenamer(const char *oldName_, const char *newName_)
        : oldName(oldName_),
          newName(newName_)
    {}
};
class ConfigDeleter : public ConfigModifier { // derived from Noncopyable
    const char *toDelete;
    const char *modify(const char *name) const OVERRIDE {
        return strcmp(name, toDelete) == 0 ? NULp : name;
    }
public:
    ConfigDeleter(const char *toDelete_)
        : toDelete(toDelete_)
    {}
};

static char *correct_managed_configsets_cb(const char *key, const char *value, AW_CL cl_ConfigModifier) {
    char *modified_value = NULp;
    if (strcmp(key, MANAGED_CONFIGSET_ENTRY) == 0) {
        const ConfigModifier *mod = (const ConfigModifier*)cl_ConfigModifier;
        ConstStrArray         config;
        GBT_split_string(config, value, CONFIG_SEPARATOR, SPLIT_DROPEMPTY);
        if (mod->modifyConfig(config)) {
            modified_value = GBT_join_strings(config, CONFIG_SEPARATOR[0]);
        }
    }
    return modified_value ? modified_value : ARB_strdup(value);
}
static void modify_configurations(const ConfigModifier& mod) {
    for (int canvas_id = 0; canvas_id<MAX_NT_WINDOWS; ++canvas_id) {
        // modify currently selected configs:
        SmartPtr<ConstStrArray> config = get_selected_configs_from_awar(canvas_id);
        if (mod.modifyConfig(*config)) {
            write_configs_to_awar(canvas_id, *config);
        }
    }
    // change all configuration-sets stored in config-manager (shared by all windows)
    AWT_modify_managed_configs(GLOBAL.gb_main, MANAGED_CONFIGSET_SECTION, correct_managed_configsets_cb, AW_CL(&mod));
}

static AW_window *create_configuration_marker_window(AW_root *root, TREE_canvas *ntw) {
    AW_window_simple *aws = new AW_window_simple;
    int ntw_id = ntw->get_index();
    {
        const char *title  = ntw->suffixed_title("Highlight species selections in tree");
        const char *id     = GBS_global_string("MARK_CONFIGS_%i", ntw_id);
        aws->init(root, id, title);
    }
    aws->load_xfig("mark_configs.fig");

    aws->auto_space(10, 10);

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

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


    aws->at("list");
    AW_DB_selection *all_configs = awt_create_CONFIG_selection_list(GLOBAL.gb_main, aws, AWAR_CONFIGURATION);
    AW_selection *sub_sel;
    {
        LocallyModify<bool> avoid(allow_selection2awar_update, false); // avoid awar gets updated from empty sub-selectionlist
        sub_sel = awt_create_subset_selection_list(aws, all_configs->get_sellist(), "selected", "add", "sort", false, configs_selectionlist_changed_cb, ntw->get_index());
    }

    awt_set_subset_selection_content(sub_sel, *get_selected_configs_from_awar(ntw_id));
    selected_configs_list[ntw_id] = sub_sel;

    // @@@ would like to use ntw-specific awar for this selection list (opening two lists links them)

    aws->at("show");
    aws->label("Display?");
    aws->create_toggle(get_display_toggle_awar(ntw_id)->awar_name);

    aws->at("settings");
    aws->callback(TREE_create_marker_settings_window);
    aws->create_autosize_button("SETTINGS", "Settings", "S");

    AWT_insert_config_manager(aws, GLOBAL.gb_main, MANAGED_CONFIGSET_SECTION, makeConfigSetupCallback(setup_configmarker_config_cb, ntw_id));

    return aws;
}

class NtSelectionAdmin: public SelectionAdmin {
    RefPtr<TREE_canvas> ntw;

public:
    NtSelectionAdmin(TREE_canvas *ntw_) : ntw(ntw_) {}

    const char *get_macro_suffix() const { return GBS_global_string("%i", ntw->get_index()); }
    GBDATA *get_gb_main() const { return ntw->get_graphic_tree()->get_gbmain(); }

    const char *get_selection_awarname() const { return AWAR_CONFIGURATION; }
    const char *get_selectionComment_awarname() const { return AWAR_CONFIG_COMMENT; }

    const char *get_window_title() const { return ntw->suffixed_title("Species selections"); }

    const char *get_name_of_tree() const { return ntw->get_awar_tree()->read_char_pntr(); }
    TreeNode *get_tree_root() const { return ntw->get_tree_root_node(); }

    const char *get_toparea_SAIs() const {
        return AW_root::SINGLETON->awar(AWAR_TOPAREA_SAIS)->read_char_pntr();
    }

    void speciesSelection_renamed_cb(const char *old_name, const char *new_name) const { modify_configurations(ConfigRenamer(old_name, new_name)); }
    void speciesSelection_deleted_cb(const char *name)                           const { modify_configurations(ConfigDeleter(name));               }
};


void NT_popup_configuration_admin(AW_window *aw_main, TREE_canvas *ntw) {
    // Note: these windows have to be tree-canvas-related,
    // because species selections need to reflect the topology of the tree shown in canvas.
    //
    // We have to use a custom popup method, because there exist multiple callbacks for the same canvas.

    static AW_window *existing_aws[MAX_NT_WINDOWS] = { MAX_NT_WINDOWS_NULLINIT };

    int ntw_id = ntw->get_index();
    if (!existing_aws[ntw_id]) {
        AW_root *root = aw_main->get_root();

        NtSelectionAdmin * const  admin = new NtSelectionAdmin(ntw); // bound to callbacks (inside create_species_selection_window)
        AW_window                *aws   = create_species_selection_window(root, admin);

        aws->at("highlight");
        aws->callback(makeCreateWindowCallback(create_configuration_marker_window, ntw));
        aws->create_autosize_button(GBS_global_string("HIGHLIGHT_%i", ntw_id), "Highlight in tree", "t");
        // @@@ no need to localize HIGHLIGHT button id (alias as in ../SL/SPECSEL/selection_admin.cxx@alias_remote_command)

        existing_aws[ntw_id] = aws;
    }
    existing_aws[ntw_id]->activate();
}

// -----------------------------------------
//      various ways to start the editor

static void nt_start_editor_on_configuration(AW_window *aww) {
    aww->hide();

    const char *cfgName   = aww->get_root()->awar(AWAR_CONFIGURATION)->read_char_pntr();
    char       *quotedCfg = GBK_singlequote(cfgName);

    AW_system(GBS_global_string("arb_edit4 -c %s &", quotedCfg));

    free(quotedCfg);
}

AW_window *NT_create_startEditorOnOldConfiguration_window(AW_root *awr) {
    static AW_window_simple *aws = NULp;
    if (!aws) {
        init_config_awar(awr);

        aws = new AW_window_simple;
        aws->init(awr, "SELECT_CONFIGURATION", "SELECT A CONFIGURATION");
        aws->at(10, 10);
        aws->auto_space(0, 0);
        awt_create_CONFIG_selection_list(GLOBAL.gb_main, aws, AWAR_CONFIGURATION);
        aws->at_newline();

        aws->callback(nt_start_editor_on_configuration);
        aws->create_button("START", "START");

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

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

void NT_start_editor_on_tree(AW_window*, int use_species_aside, TREE_canvas *ntw) {
    NtSelectionAdmin def(ntw);
    GB_ERROR error    = create_species_selection(def, DEFAULT_CONFIGURATION, use_species_aside, BY_CALLING_THE_EDITOR);
    if (!error) error = GBK_system("arb_edit4 -c " DEFAULT_CONFIGURATION " &");
    aw_message_if(error);
}

inline void nt_create_config_after_import(TREE_canvas *ntw) {
    const char *dated_suffix = ARB_dateTime_suffix();
    char       *configName   = GBS_global_string_copy("imported_%s", dated_suffix);

    // ensure unique config-name
    {
        int unique = 1;
        GB_transaction ta(ntw->gb_main);
        while (GBT_find_configuration(ntw->gb_main, configName)) {
            freeset(configName, GBS_global_string_copy("imported_%s_%i", dated_suffix, ++unique));
        }
    }

    NtSelectionAdmin def(ntw);
    GB_ERROR error = create_species_selection(def, configName, 0, FROM_IMPORTER);
    aw_message_if(error);

    free(configName);
}

void NT_create_config_after_import(TREE_canvas *ntw, bool imported_from_scratch) {
    /*! create a new config after import
     * @param imported_from_scratch if true -> DB was created from scratch, all species in DB are marked.
     *                              if false -> data was imported into existing DB. Other species may be marked as well, imported species are "queried".
     */

    if (imported_from_scratch) {
        nt_create_config_after_import(ntw);
    }
    else {
        GB_transaction ta(ntw->gb_main);

        // remember marks + mark queried species:
        for (GBDATA *gb_species = GBT_first_species(ntw->gb_main); gb_species; gb_species = GBT_next_species(gb_species)) {
            GB_write_user_flag(gb_species, GB_USERFLAG_WASMARKED, GB_read_flag(gb_species));
            GB_write_flag(gb_species, GB_user_flag(gb_species, GB_USERFLAG_QUERY));
        }

        nt_create_config_after_import(ntw);

        // restore old marks:
        for (GBDATA *gb_species = GBT_first_species(ntw->gb_main); gb_species; gb_species = GBT_next_species(gb_species)) {
            GB_write_flag(gb_species, GB_user_flag(gb_species, GB_USERFLAG_WASMARKED));
            GB_clear_user_flag(gb_species, GB_USERFLAG_WASMARKED);
        }
    }
}

