// =============================================================== //
//                                                                 //
//   File      : AWTI_edit.cxx                                     //
//   Purpose   : test and modify import/export filters             //
//                                                                 //
//   Coded by Ralf Westram (coder@reallysoft.de) in October 2017   //
//   http://www.arb-home.de/                                       //
//                                                                 //
// =============================================================== //

#include "awti_edit.hxx"
#include "awti_exp_local.hxx"
#include "awti_imp_local.hxx"

#include <seqio.hxx>
#include <db_scanner.hxx>

#include <prompt.hxx>

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

#include <BufferedFileReader.h>
#include <FileWatch.h>

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


enum FormatType { FT_IMPORT, FT_EXPORT };
enum FormatAction { EDIT, COPY, RENAME, DELETE };

static const char *action_name[] = {
    "edit",
    "copy",
    "rename",
    "delete",
};

class FormatTester : virtual Noncopyable {
    FormatType  type;
    AW_window  *aws;             // filter test window
    AW_awar    *awar_filtername; // contains full path of selected (=tested) filter

    const char *get_awarname() const { return type == FT_IMPORT ? AWAR_IMPORT_FORMATNAME : AWAR_EXPORT_FORMATNAME; }
    const char *ieport()       const { return type == FT_IMPORT ? "import" : "export"; }
    const char *get_suffix()   const { return type == FT_IMPORT ? "ift" : "eft"; }

    const char *get_selected_filter() const { return awar_filtername->read_char_pntr(); }

    char *user_filter_fullname(const char *name) {
        char       *name_suf = ARB_strdup(GB_append_suffix(name, get_suffix()));
        const char *user_dir = GB_path_in_arbprop("filter");
        char       *fullpath = ARB_strdup(GB_concat_path(user_dir, name_suf));
        free(name_suf);
        return fullpath;
    }

    // management button callbacks:
    GB_ERROR nameEntered_handler(FormatAction action, const char *targetName) {
        GB_ERROR    error          = NULp;
        char       *fullTargetName = user_filter_fullname(targetName);
        const char *fullSourceName = get_selected_filter();

        if (strcmp(fullSourceName, fullTargetName) == 0) {
            error = "source and destination are the same";
        }
        else if (action == RENAME) {
            error = GB_safe_rename_file(fullSourceName, fullTargetName);
        }
        else {
            awti_assert(action == COPY);
            error = GB_safe_copy_file(fullSourceName, fullTargetName);
        }
        if (!error) {
            awar_filtername->write_string(fullTargetName);
        }
        free(fullTargetName);
        return error;
    }
    static GB_ERROR nameEntered_wrapper(const char *targetName, FormatAction action, FormatTester *const tester) {
        return tester->nameEntered_handler(action, targetName);
    }
    void handle_copyRename(FormatAction action) {
        char *Action = ARB_strdup(action_name[action]);
        Action[0]    = toupper(Action[0]);

        char *title  = GBS_global_string_copy("%s %s format", Action, ieport());
        char *prompt = GBS_global_string_copy("Enter name of %s format:", ieport());

        char *nameOnly;
        GB_split_full_path(get_selected_filter(), NULp, NULp, &nameOnly, NULp);
        if (!nameOnly) nameOnly = ARB_strdup("");

        AWT_activate_prompt(title, prompt, nameOnly, Action, makeResultHandler(nameEntered_wrapper, action, this));

        free(nameOnly);
        free(prompt);
        free(title);
    }

public:
    FormatTester(FormatType type_, AW_window *aws_) :
        type(type_),
        aws(aws_)
    {
        awar_filtername = aws->get_root()->awar(get_awarname());
        awti_assert(awar_filtername);
    }

    void create_common_gui(const char *helpfile);

    void handle_action_cb(FormatAction action) {
        char *filter_name = awar_filtername->read_string(); // use copy (may disappear)
        if (!GB_is_regularfile(filter_name)) {
            aw_message(GBS_global_string("Please select the filter to %s", action_name[action]));
        }
        else {
            if (action == COPY || action == RENAME) {
                handle_copyRename(action);
            }
            else {
                bool is_writeable = GB_is_writeablefile(filter_name);
                if (action == EDIT) {
                    if (!is_writeable) {
                        aw_message("The filter is write-protected (use COPY to create a local copy)");
                    }
                    else {
                        AW_edit(filter_name);
                    }
                }
                else if (action == DELETE) {
                    if (!is_writeable) {
                        aw_message("The filter is write-protected");
                    }
                    else {
                        awar_filtername->write_string(""); // deselect
                        if (GB_unlink(filter_name)<0) {
                            awar_filtername->write_string(filter_name); // re-select
                            aw_message(GB_await_error());
                        }
                    }
                }
                else {
                    awti_assert(0); // unhandled FormatAction
                }
            }
        }
        free(filter_name);
    }
};

typedef SmartPtr<FormatTester> FormatTesterPtr;

static void action_cb_wrapper(AW_window*, FormatTester *const tester, FormatAction action) {
    tester->handle_action_cb(action);
}

void FormatTester::create_common_gui(const char *helpfile) {
    aws->at("close");
    aws->callback(AW_POPDOWN);
    aws->create_button("CLOSE", "CLOSE", "C");

    aws->at("help");
    aws->callback(makeHelpCallback(helpfile));
    aws->create_button("HELP", "HELP", "H");

    aws->at("filter");
    aws->create_button(NULp, awar_filtername->awar_name, NULp, "+");

    aws->at("buttons");

    aws->auto_space(5, 5);

    aws->callback(makeWindowCallback(action_cb_wrapper, this, EDIT));   aws->create_button("EDIT",   "EDIT",   "E");
    aws->callback(makeWindowCallback(action_cb_wrapper, this, COPY));   aws->create_button("COPY",   "COPY",   "C");
    aws->callback(makeWindowCallback(action_cb_wrapper, this, RENAME)); aws->create_button("RENAME", "RENAME", "R");
    aws->callback(makeWindowCallback(action_cb_wrapper, this, DELETE)); aws->create_button("DELETE", "DELETE", "D");
}

static char *get_file_content_for_viewer(const char *filename) {
    char *result = NULp;
    FILE *in     = fopen(filename, "rt");
    if (in) {
        BufferedFileReader reader(filename, in);
        GBS_strstruct      view(10000);

        string line;
        while (reader.getLine(line)) {
            view.ncat(line.c_str(), line.size());
            view.put('\n');
        }

        result = view.release();
    }
    else {
        result = ARB_strdup(GB_IO_error("reading", filename));
    }
    return result;
}


#define TESTER_AWAR_PREFIX          "tmp/tester/"
#define AWAR_TESTER_LINK_EX2IMPORT  TESTER_AWAR_PREFIX "link"
#define AWAR_TESTER_EXPORT_RESULT   TESTER_AWAR_PREFIX "result"
#define AWAR_TESTER_IMPORT_FILENAME TESTER_AWAR_PREFIX "filename"
#define AWAR_TESTER_IMPORT_STATUS   TESTER_AWAR_PREFIX "status"
#define AWAR_TESTER_IMPORT_SPECIES  TESTER_AWAR_PREFIX "species"

static bool test_import_active = false;
static bool test_export_active = false;

static void disable_test_import(AW_window*) {
    test_import_active = false;
}
static void disable_test_export(AW_window *aww) {
    aww->get_root()->awar(AWAR_TESTER_LINK_EX2IMPORT)->write_int(0); // avoid confusion in export-test
    test_export_active = false;
}

static void create_tester_awars(AW_root *awr) {
    static bool initialized = false;
    if (!initialized) {
        awr->awar_string(AWAR_TESTER_EXPORT_RESULT,   "<undefined>");
        awr->awar_string(AWAR_TESTER_IMPORT_STATUS,   "<undefined>");
        awr->awar_string(AWAR_TESTER_IMPORT_FILENAME, "");
        awr->awar_string(AWAR_TESTER_IMPORT_SPECIES,  "");
        awr->awar_int   (AWAR_TESTER_LINK_EX2IMPORT,  0);
        initialized = true;
    }
}

static void update_exportTest_result_cb(AW_root *awr, GBDATA *gb_main, adfiltercbstruct *acbs) {
    if (test_export_active) {
        GB_transaction ta(gb_main);

        AW_awar *awar_result      = awr->awar(AWAR_TESTER_EXPORT_RESULT);
        char    *selected_species = awr->awar(AWAR_SPECIES_NAME)->read_string();
        bool     have_species     = GBT_find_species(gb_main, selected_species);

        if (have_species) {
            AP_filter *filter = awt_get_filter(acbs);

            int cut_stop_codon = awr->awar(AWAR_EXPORT_CUTSTOP)->read_int();
            int compress       = awr->awar(AWAR_EXPORT_COMPRESS)->read_int();
            int linked         = awr->awar(AWAR_TESTER_LINK_EX2IMPORT)->read_int();

            char *formname     = awr->awar(AWAR_EXPORT_FORMATNAME)->read_string();
            char *db_name      = awr->awar(AWAR_DB_NAME)->read_string();
            char *outname      = GB_create_tempfile("test_export");
            char *real_outname = NULp;

            const char *ftsname = XFER_getFullFTS(awr->awar(AWAR_EXPORT_FTS)->read_char_pntr());

            arb_suppress_progress here;

            GB_ERROR error = SEQIO::export_by_format(gb_main, SEQIO::EBF_ONE, selected_species,
                                                     filter, cut_stop_codon, compress,
                                                     db_name, formname, ftsname,
                                                     outname, 0, &real_outname);

            if (error) {
                awar_result->write_string(GBS_global_string("Error during export:\n%s", error));
            }
            else {
                char *content = get_file_content_for_viewer(real_outname);
                awar_result->write_string(content);
                free(content);

                if (linked) {
                    awr->awar(AWAR_TESTER_IMPORT_FILENAME)->write_string(real_outname);
                }
                else {
                    GB_unlink_or_warn(real_outname, NULp);
                }
            }

            free(real_outname);
            free(outname);
            free(selected_species);
            free(db_name);
            free(formname);
        }
        else {
            awar_result->write_string("<no species selected>");
        }
    }
}

static void exportImportLinkChanged_cb(AW_root *awr, GBDATA *gb_main, adfiltercbstruct *acbs) {
    if (awr->awar(AWAR_TESTER_LINK_EX2IMPORT)->read_int()) { // link activated
        update_exportTest_result_cb(awr, gb_main, acbs); // run export test again (will trigger AWAR_TESTER_IMPORT_FILENAME on success)
    }
    else { // link de-activated
        AW_awar *awar_ifilename = awr->awar_no_error(AWAR_IMPORT_FILENAME);
        if (awar_ifilename) { // does not exist if import window was not opened yet
            awar_ifilename->touch(); // reset AWAR_TESTER_IMPORT_FILENAME to "unlinked" value
        }
    }
}

struct ImportTestData : virtual Noncopyable {
    AW_selection_list *species_sellist;
    DbScanner         *field_scanner;
    GBDATA            *gb_main_lastImport;

    ImportTestData() :
        species_sellist(NULp),
        field_scanner(NULp),
        gb_main_lastImport(NULp)
    {
    }
    ~ImportTestData() {
        if (gb_main_lastImport) {
            GB_close(gb_main_lastImport);
        }
    }

    void forgetLastImport() {
        if (gb_main_lastImport) {
            GB_close(gb_main_lastImport);
            gb_main_lastImport = NULp;
        }
        species_sellist->clear();
        species_sellist->insert_default("", "");
        species_sellist->update();
    }
    void setNewImport(GBDATA *gb_new_main) {
        gb_main_lastImport = gb_new_main;
        field_scanner->RemapToDatabase(gb_main_lastImport);
    }

    void refill_species_list() {
        GB_transaction ta(gb_main_lastImport);
        for (GBDATA *gb_species = GBT_first_species(gb_main_lastImport);
             gb_species;
             gb_species = GBT_next_species(gb_species))
        {
            const char *name = GBT_get_name(gb_species);
            if (name) {
                species_sellist->insert(name, name);
            }
            else {
                species_sellist->insert("<unnamed species>", "");
            }
        }
        species_sellist->insert_default("", "");
        species_sellist->update();
    }

    void remap_scanner(AW_root *awr);

    void countSpeciesAndData(const char *aliName, long& speciesCount, long& dataCount) {
        speciesCount = 0;
        dataCount    = 0;

        GB_transaction ta(gb_main_lastImport);
        for (GBDATA *gb_species = GBT_first_species(gb_main_lastImport);
             gb_species;
             gb_species = GBT_next_species(gb_species))
        {
            ++speciesCount;

            GBDATA *gb_seq = GBT_find_sequence(gb_species, aliName);
            awti_assert(gb_seq);
            const char *seq = GB_read_char_pntr(gb_seq);
            for (int p = 0; seq[p]; ++p) {
                dataCount += !!isalpha(seq[p]);
            }
        }
    }

};

static void neverCalledDummy_cb(AW_root*) { awti_assert(0); }

static void rerun_importTest_cb(AW_root *awr, ImportTestData *tdata) {
    if (test_import_active) {
        ArbImporter importer(makeRootCallback(neverCalledDummy_cb));

        arb_suppress_progress here;

        tdata->forgetLastImport();

        char     *mask           = awr->awar(AWAR_TESTER_IMPORT_FILENAME)->read_string(); // will use result of export (if linked)
        GB_ERROR  error          = importer.import_data(awr, mask, true);
        AW_awar  *awar_source    = awr->awar(AWAR_TESTER_IMPORT_STATUS);
        AW_awar  *awar_selected  = awr->awar(AWAR_TESTER_IMPORT_SPECIES);
        bool      touch_selected = true;

        if (error) {
            awar_source->write_string(error);
        }
        else {
            tdata->setNewImport(importer.takeImportDB());
            tdata->refill_species_list();

            const char *aliName = awr->awar(AWAR_IMPORT_ALI)->read_char_pntr();
            long        speciesCount, dataCount;
            tdata->countSpeciesAndData(aliName, speciesCount, dataCount);

            const char *status = GBS_global_string("Species imported: %li\n"
                                                   "Base count: %s",
                                                   speciesCount,
                                                   GBS_readable_size(dataCount, "bp"));
            awar_source->write_string(status);

            {
                GB_transaction ta(tdata->gb_main_lastImport);
                char *previously_selected = awar_selected->read_string();
                if (!GBT_find_species(tdata->gb_main_lastImport, previously_selected)) {
                    GBDATA *gb_first = GBT_first_species(tdata->gb_main_lastImport);
                    if (gb_first) {
                        awar_selected->write_string(null2empty(GBT_get_name(gb_first)));
                        touch_selected = false;
                    }
                }
                free(previously_selected);
            }
        }
        if (touch_selected) awar_selected->touch(); // remaps species fields
        free(mask);
    }
}

void ImportTestData::remap_scanner(AW_root *awr) {
    if (gb_main_lastImport) {
        GB_transaction ta(gb_main_lastImport);
        const ItemSelector&  selector   = field_scanner->get_selector();
        GBDATA              *gb_species = selector.get_selected_item(gb_main_lastImport, awr);
        field_scanner->Map(gb_species, selector.change_key_path);
    }
}
static void remap_scanner_cb(AW_root *awr, ImportTestData *tdata) { tdata->remap_scanner(awr); }

static GBDATA* get_selected_imported_species(GBDATA *gb_main, AW_root *aw_root) {
    char   *species_name = aw_root->awar(AWAR_TESTER_IMPORT_SPECIES)->read_string();
    GBDATA *gb_species   = NULp;
    if (species_name[0]) {
        gb_species = GBT_find_species(gb_main, species_name);
    }
    free(species_name);
    return gb_species;
}
static const ItemSelector& get_importedSpecies_selector() {
    static struct MutableItemSelector spec_sel = SPECIES_get_selector(); // copy species selector
    spec_sel.get_selected_item = get_selected_imported_species;
    return spec_sel;
}

static void update_import_filename_cb(AW_root *awr) {
    const char *currImportSource = awr->awar(AWAR_IMPORT_FILENAME)->read_char_pntr();
    awr->awar(AWAR_TESTER_IMPORT_FILENAME)->write_string(currImportSource);
}

static void import_file_changed_cb(const char *, ChangeReason, ImportTestData *tdata) {
    rerun_importTest_cb(AW_root::SINGLETON, tdata);
}

void AWTI_activate_import_test_window(AW_window *awp) {
    static AW_window_simple *aws = NULp;

    test_import_active = true;

    AW_root *awr = awp->get_root();
    if (!aws) {
        create_tester_awars(awr);

        aws = new AW_window_simple;
        aws->init(awr, "ARB_IMPORT_TEST", "Test import filter");
        aws->load_xfig("awt/import_test.fig");

        static FormatTesterPtr tester = new FormatTester(FT_IMPORT, aws);

        tester->create_common_gui("import_test.hlp");

        aws->at("stat");
        aws->create_text_field(AWAR_TESTER_IMPORT_STATUS);

        static SmartPtr<ImportTestData> tdataPtr = new ImportTestData; // (cleaned up on exit)
        ImportTestData *const           tdata    = &*tdataPtr;

        aws->at("spec");
        tdata->species_sellist = aws->create_selection_list(AWAR_TESTER_IMPORT_SPECIES);

        tdata->field_scanner = DbScanner::create(NULp, "test_import", aws, "fields", NULp, NULp, DB_SCANNER, NULp, get_importedSpecies_selector());

        RootCallback test_import_cb = makeRootCallback(rerun_importTest_cb, tdata);
        awr->awar(AWAR_IMPORT_FORMATNAME)->add_callback(test_import_cb);
        awr->awar(AWAR_IMPORT_ALI)       ->add_callback(test_import_cb);
        awr->awar(AWAR_IMPORT_FTS)       ->add_callback(test_import_cb);

        awr->awar(AWAR_IMPORT_FILENAME)->add_callback(update_import_filename_cb);
        static SmartPtr<FileWatch> fwatch = new FileWatch(AWAR_TESTER_IMPORT_FILENAME, makeFileChangedCallback(import_file_changed_cb, tdata));

        awr->awar(AWAR_TESTER_IMPORT_SPECIES)->add_callback(makeRootCallback(remap_scanner_cb, tdata));

        aws->on_hide(disable_test_import);
    }

    update_import_filename_cb(awr); // avoid error message after startup
    awr->awar(AWAR_IMPORT_FORMATNAME)->touch(); // trigger callback once (needed after re-opening window)
    aws->activate();
}

void AWTI_activate_export_test_window(AW_window *awp, GBDATA *gb_main, adfiltercbstruct *acbs) {
    static AW_window_simple *aws = NULp;

    test_export_active = true;

    AW_root *awr = awp->get_root();
    if (!aws) {
        create_tester_awars(awr);

        aws = new AW_window_simple;
        aws->init(awr, "ARB_EXPORT_TEST", "Test export filter");
        aws->load_xfig("awt/export_test.fig");

        static FormatTesterPtr tester = new FormatTester(FT_EXPORT, aws);

        tester->create_common_gui("export_test.hlp");

        aws->label("Link to import");
        aws->create_toggle(AWAR_TESTER_LINK_EX2IMPORT);

        aws->at("res");
        aws->create_text_field(AWAR_TESTER_EXPORT_RESULT);

        RootCallback test_export_cb = makeRootCallback(update_exportTest_result_cb, gb_main, acbs);
        awr->awar(AWAR_SPECIES_NAME)          ->add_callback(test_export_cb);
        awr->awar(AWAR_EXPORT_FORMATNAME)     ->add_callback(test_export_cb);
        awr->awar(AWAR_EXPORT_CUTSTOP)        ->add_callback(test_export_cb);
        awr->awar(AWAR_EXPORT_COMPRESS)       ->add_callback(test_export_cb);
        awr->awar(AWAR_TESTER_LINK_EX2IMPORT) ->add_callback(makeRootCallback(exportImportLinkChanged_cb, gb_main, acbs));
        awr->awar(acbs->def_filter)           ->add_callback(test_export_cb); // filter string
        awr->awar(AWAR_EXPORT_FTS)            ->add_callback(test_export_cb);

        aws->on_hide(disable_test_export);
    }

    awr->awar(AWAR_EXPORT_FORMATNAME)->touch(); // trigger callback once (needed after re-opening window)
    aws->activate();
}


