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

#include "awti_export.hxx"
#include "awti_exp_local.hxx"
#include "awti_edit.hxx"

#include <xfergui.h>
#include <FileWatch.h>
#include <seqio.hxx>
#include <AP_filter.hxx>
#include <db_scanner.hxx>

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

#include <arbdbt.h>
#include <gb_aci.h>

#include <arb_progress.h>
#include <arb_strbuf.h>
#include <arb_stdstring.h>

#include <set>
#include <string>

using namespace SEQIO;

static void export_go_cb(AW_window *aww, GBDATA *gb_main, adfiltercbstruct *acbs) {
    awti_assert(!GB_have_error());

    GB_transaction ta(gb_main);
    arb_progress   progress("Exporting data");

    AW_root *awr = aww->get_root();

    int multiple       = awr->awar(AWAR_EXPORT_MULTIPLE_FILES)->read_int();
    int cut_stop_codon = awr->awar(AWAR_EXPORT_CUTSTOP)->read_int();
    int compress       = awr->awar(AWAR_EXPORT_COMPRESS)->read_int();

    ExportWhich  which   = ExportWhich(awr->awar(AWAR_EXPORT_MARKED)->read_int());
    AP_filter   *filter  = awt_get_filter(acbs);
    const char  *ftsname = XFER_getFullFTS(awr->awar(AWAR_EXPORT_FTS)->read_char_pntr());

    char *formname = awr->awar(AWAR_EXPORT_FORMATNAME)->read_string();
    char *outname  = awr->awar(AWAR_EXPORT_FILENAME)->read_string();
    char *db_name  = awr->awar(AWAR_DB_NAME)->read_string();

    char *real_outname = NULp; // with suffix (name of first file if multiple)

    GB_ERROR error = export_by_format(gb_main, which, NULp,
                                      filter, cut_stop_codon, compress,
                                      db_name, formname, ftsname,
                                      outname, multiple, &real_outname);

    if (error) aw_message(error);
    if (real_outname) awr->awar(AWAR_EXPORT_FILENAME)->write_string(real_outname);

    AW_refresh_fileselection(awr, AWAR_EXPORT_FILEBASE);

    free(db_name);
    free(real_outname);
    free(outname);
    free(formname);
}

static void avoid_empty_target_name(char*& nameOnly) {
    static char *last_used_name = ARB_strdup("noname");
    if (nameOnly) freedup(last_used_name, nameOnly);
    else          nameOnly = ARB_strdup(last_used_name); // if name was lost (by changing directory) -> reuse last_used_name
}

static bool in_export_filename_changed_cb = false;
static void export_filename_changed_cb(AW_root *aw_root) {
    if (!in_export_filename_changed_cb) {
        LocallyModify<bool> dont_recurse(in_export_filename_changed_cb, true);

        AW_awar *awar_export = aw_root->awar(AWAR_EXPORT_FILENAME);
        char    *exportname  = awar_export->read_string();

        if (!GB_is_directory(exportname)) {
            char *path, *nameOnly, *suffix;
            GB_split_full_path(exportname, &path, NULp, &nameOnly, &suffix);

            avoid_empty_target_name(nameOnly);

            const char *new_exportname = GB_concat_path(path, GB_append_suffix(nameOnly, suffix));
            if (new_exportname) awar_export->write_string(new_exportname);
        }
        free(exportname);
    }
}

static void create_export_awars(AW_root *awr) {
    {
        GBS_strstruct path(500);
        path.cat(GB_path_in_arbprop("filter"));
        path.put(':');
        path.cat(GB_path_in_ARBLIB("export"));

        AW_create_fileselection_awars(awr, AWAR_EXPORT_FORMATBASE, path.get_data(), ".eft", "*");
        AW_create_fileselection_awars(awr, AWAR_EXPORT_FILEBASE,   "",              "",     "noname");
    }

    awr->awar_string(AWAR_EXPORT_FORMAT_DESC, "<no format selected>");
    awr->awar_string(AWAR_EXPORT_FTS);

    awr->awar_int(AWAR_EXPORT_MULTIPLE_FILES);
    awr->awar_int(AWAR_EXPORT_MARKED,   EBF_MARKED);
    awr->awar_int(AWAR_EXPORT_COMPRESS, 1); // vertical gaps
    awr->awar_int(AWAR_EXPORT_CUTSTOP);     // don't cut stop-codon

    awt_create_filter_awars(awr, AW_ROOT_DEFAULT, AWAR_EXPORT_FILTER_NAME, AWAR_DEFAULT_ALIGNMENT);

    awr->awar(AWAR_EXPORT_FILENAME)->add_callback(export_filename_changed_cb);
}

static void update_format_description_and_suffix(const char *eft) {
    // called when export format has changed (filter content or selected filter)
    // -> automatically correct filename suffix
    // -> restrict view to suffix
    // -> update description

    ExportFormatInfo eft_info;
    GB_ERROR         error = get_exportFormat_information(eft, eft_info);

    AW_root *aw_root   = AW_root::SINGLETON;
    AW_awar *awar_desc = aw_root->awar(AWAR_EXPORT_FORMAT_DESC);

    if (error) awar_desc->write_string(error); // display format-errors in description field
    else {
        const char  *current_suffix  = eft_info.suffix.content(); // Note: current_suffix may be NULp.. is that ok?
        static char *previous_suffix = NULp;

        // modify export filename and view
        AW_awar *awar_filter = aw_root->awar(AWAR_EXPORT_FILESUFFIX);
        AW_awar *awar_export = aw_root->awar(AWAR_EXPORT_FILENAME);

        awar_filter->write_string("");

        char *exportname = awar_export->read_string();

        {
            LocallyModify<bool> dont_recurse(in_export_filename_changed_cb, true);

            char *path, *nameOnly, *suffix;
            GB_split_full_path(exportname, &path, NULp, &nameOnly, &suffix);

            avoid_empty_target_name(nameOnly);

            if (suffix) {
                if (previous_suffix && ARB_stricmp(suffix, previous_suffix) == 0) freedup(suffix, current_suffix); // remove old suffix
                else freedup(suffix, GB_append_suffix(suffix, current_suffix)); // don't know existing suffix -> append
            }
            else suffix = ARB_strdup(current_suffix);

            const char *new_exportname = GB_concat_path(path, GB_append_suffix(nameOnly, suffix));
            if (new_exportname) awar_export->write_string(new_exportname);

            free(suffix);
            free(nameOnly);
            free(path);
        }

        free(exportname);

        awar_filter->write_string(current_suffix);

        // remember last applied suffix
        freedup(previous_suffix, current_suffix);

        // update description
        if (eft_info.description.isSet()) awar_desc->write_string(&*eft_info.description);
        else                              awar_desc->write_string("<no description>");
    }
}

class ExportFieldTracker : public FieldTracker, virtual Noncopyable {
    typedef std::set<std::string> StrSet;

    StrSet tracked; // fields

public:
    ExportFieldTracker() {}
    void track_field(const char *fieldname) OVERRIDE {
        tracked.insert(fieldname);
    }

    void add_tracked_to(StrArray& addTo) const {
        for (StrSet::const_iterator elem = tracked.begin(); elem != tracked.end(); ++elem) {
            char *copy = ARB_stringdup(*elem);
            addTo.put(copy);
        }
    }
};

class ExportFieldScanner : public AvailableFieldScanner {
    RefPtr<GBDATA> gb_main;

    static const char* fake_exported_sequence(GBDATA*, unsigned long *len, const char**) {
        *len = 5;
        return "faked";
    }

public:
    ExportFieldScanner() : gb_main(NULp) {}

    void scanFields(StrArray& fields, FieldsToScan whatToScan) const OVERRIDE {
        if (whatToScan & SCAN_INPUT_FIELDS) {
            if (gb_main) {
                collectKeysRegisteredInDatabase(fields, gb_main, SPECIES_get_selector(), true, false);
            }
            else {
                fields.put(ARB_strdup("<no known source database>"));
            }
        }

        if (whatToScan & SCAN_OUTPUT_FIELDS) {
            char *eft = AW_root::SINGLETON->awar(AWAR_EXPORT_FORMATNAME)->read_string();
            if (eft[0]) {
                GB_ERROR scan_error = NULp;
                {
                    char *eftName;
                    GB_split_full_path(eft, NULp, &eftName, NULp, NULp);
                    free(eftName);
                }

                if (gb_main) {
                    GB_transaction ta(gb_main);

                    ItemSelector&  itemtype             = SPECIES_get_selector();
                    GBDATA        *gb_someSpecies       = itemtype.get_selected_item(gb_main, AW_root::SINGLETON);
                    if (!gb_someSpecies) gb_someSpecies = MutableBoundItemSel(gb_main, itemtype).get_any_item();

                    if (gb_someSpecies) {
                        GB_ERROR  error = NULp;
                        char     *form  = get_exportFormat_evalForm(eft, error);

                        if (!error) {
                            if (form) {
                                // create 'callEnv' to track fields:
                                ExportFieldTracker tracker;
                                GBL_env            env(gb_main, "tree_faked");
                                {
                                    GBL_call_env callEnv(gb_someSpecies, env);

                                    callEnv.useFieldTracker(&tracker);
                                    GB_set_export_sequence_hook(fake_exported_sequence);

                                    // evaluate form (causes fields to become tracked by 'tracker')
                                    {
                                        char *result = GBS_string_eval_in_env(" ", form, callEnv);
                                        awti_assert(!GB_have_error()); // if eval reports error -> tracker did not see fields behind error-command
                                        free(result);
                                    }

                                    GB_set_export_sequence_hook(NULp);
                                }
                                // collect tracked fields:
                                tracker.add_tracked_to(fields);
                            }
                            else {
                                scan_error = "unknown form access failure";
                            }
                        }
                        else {
                            scan_error = error;
                        }
                        free(form);
                    }
                    else {
                        scan_error = "no species";
                    }
                }
                else {
                    scan_error = "no database";
                }

                if (scan_error) {
                    fields.put(GBS_global_string_copy("<scan filter: %s>", scan_error));
                }
            }
            else {
                fields.put(ARB_strdup("<no export filter selected>"));
            }
            free(eft);
        }
    }

    void announce_source_database(GBDATA *gb_src) { gb_main = gb_src; }
};

static ExportFieldScanner fieldScanner;

AW_window *create_AWTC_export_window(AW_root *awr, GBDATA *gb_main) {
    static AW_window_simple *aws = NULp;

    if (!aws) {
        create_export_awars(awr);

        aws = new AW_window_simple;

        aws->init(awr, "ARB_EXPORT", "ARB EXPORT");
        aws->load_xfig("awt/export_db.fig");

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

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

        AW_create_fileselection(aws, AWAR_EXPORT_FILEBASE,   "f", "PWD",     ANY_DIR,    false); // select export filename
        AW_create_fileselection(aws, AWAR_EXPORT_FORMATBASE, "",  "ARBHOME", MULTI_DIRS, false); // select export filter

        aws->at("allmarked");
        aws->create_option_menu(AWAR_EXPORT_MARKED);
        aws->insert_option("all",    "a", EBF_ALL);
        aws->insert_option("marked", "m", EBF_MARKED);
        aws->update_option_menu();

        aws->at("compress");
        aws->create_option_menu(AWAR_EXPORT_COMPRESS);
        aws->insert_option("no", "n", 0);
        aws->insert_option("vertical gaps", "v", 1);
        aws->insert_option("all gaps", "a", 2);
        aws->update_option_menu();

        aws->at("seqfilter");
        adfiltercbstruct *filtercd = awt_create_select_filter(awr, gb_main, AWAR_EXPORT_FILTER_NAME);
        aws->callback(makeCreateWindowCallback(awt_create_select_filter_win, filtercd));
        aws->create_button("SELECT_FILTER", AWAR_EXPORT_FILTER_NAME);

        aws->at("cutstop");
        aws->create_toggle(AWAR_EXPORT_CUTSTOP);

        aws->at("multiple");
        aws->create_toggle(AWAR_EXPORT_MULTIPLE_FILES);

        aws->at("desc");
        aws->create_text_field(AWAR_EXPORT_FORMAT_DESC);

        aws->at("go");
        aws->highlight();
        aws->callback(makeWindowCallback(export_go_cb, gb_main, filtercd));
        aws->create_button("GO", "GO", "G", "+");

        aws->at("test");
        aws->callback(makeWindowCallback(AWTI_activate_export_test_window, gb_main, filtercd));
        aws->create_button("TEST", "Test", "T");

        aws->at("fts");
        aws->callback(makeWindowCallback(XFER_select_RuleSet, AWAR_EXPORT_FTS, static_cast<AvailableFieldScanner*>(&fieldScanner)));
        aws->create_button("SELECT_FTS", AWAR_EXPORT_FTS);

        static FileWatch fwatch(AWAR_EXPORT_FORMATNAME, makeFileChangedCallback(update_format_description_and_suffix));
        awr->awar(AWAR_EXPORT_FORMATNAME)->add_callback(makeRootCallback(XFER_refresh_available_fields, static_cast<AvailableFieldScanner*>(&fieldScanner), SCAN_OUTPUT_FIELDS));
    }

    fieldScanner.announce_source_database(gb_main);

    return aws;
}
