// ========================================================= //
//                                                           //
//   File      : LabelTranslator.cxx                         //
//   Purpose   : Translate tree labels                       //
//                                                           //
//   Coded by Ralf Westram (coder@reallysoft.de) in Sep 21   //
//   http://www.arb-home.de/                                 //
//                                                           //
// ========================================================= //

#include "LabelTranslator.h"
#include <TreeNode.h>
#include <gb_aci.h>
#include <arb_unordered_set.h>

using namespace std;

typedef arb_unordered_set<string> StringSet;

class TranslationReport : virtual Noncopyable {
    StringSet translatedLabels;

public:
    GB_ERROR notify_translate_label(string label) { // @@@ !also pass input translation
        // returns error if labels found in tree are not unique
        StringSet::const_iterator found = translatedLabels.find(label);
        if (found != translatedLabels.end()) {
            return GBS_global_string("Encountered duplicated label '%s' in tree.", label.c_str());
        }
        translatedLabels.insert(label);
        return NULp;
    }
};


GB_ERROR LabelTranslator::translate_unlinked_labels_in_tree(TreeNode *tree, TranslationReport& report) const {
    GB_ERROR error = NULp;
    if (tree->is_leaf()) {
        if (!tree->gb_node) { // only translate unlinked leafs (i.e. not-yet-linked nodes or real zombies)
            error = report.notify_translate_label(tree->name); // @@@ !need to do duplication check for original label (from tree) and for aci-modified label.
            if (!error) {
                ErrorOrLabel translation   = translate(tree->name);
                if (translation.hasError()) {
                    error = translation.getError().deliver(); // @@@R instead use ARB_ERROR in translate_unlinked_labels_in_tree?
                }
                else {
                    string translated = translation.getValue();
                    if (translated != tree->name) {
                        freedup(tree->name, translated.c_str());
                    }
                    // @@@L count translated (into report?)
                }
            }
        }
    }
    else {
        error             = translate_unlinked_labels_in_tree(tree->get_leftson(), report);
        if (!error) error = translate_unlinked_labels_in_tree(tree->get_rightson(), report);
    }
    return error;
}

inline bool generated_by_multiple_species(const char *species_list) {
    return strchr(species_list, ',') != NULp;
}

ErrorOrLabel ACI_LabelTranslator::translate(const char *label) const {
    StringMap::const_iterator found = identifier2shortname.find(label);
    if (found != identifier2shortname.end()) {
        const char *species_id = found->second.c_str();
        if (generated_by_multiple_species(species_id)) {
            return ErrorOrLabel(GBS_global_string("label '%s' is ambiguous (generated for multiple species: %s)", label, species_id), "");
        }
        return ErrorOrLabel(NULp, species_id);
    }

    // @@@ !need to make sure 'label' is not a species name (otherwise would accidentally link)
    // @@@L add option to ACI_LabelTranslator: refuse unmatched translation -> bail out with error here.
    return ErrorOrLabel(NULp, label); // no translation found -> return original
}

GB_ERROR ACI_LabelTranslator::generate_species_identifiers(GBDATA *gb_main) const {
    GB_transaction ta(gb_main);
    GB_ERROR       error = NULp;

    if (ta.ok()) {
        GBL_env env(gb_main, NULp);  // @@@ pass treename?

        for (GBDATA *gb_species = GBT_first_species(gb_main);
             gb_species && !error;
             gb_species = GBT_next_species(gb_species))
        {
            GBL_call_env  callEnv(gb_species, env);
            char         *identifier = GB_command_interpreter_in_env("", species_aci.c_str(), callEnv);
            const char   *shortname  = GBT_get_name(gb_species);

            if (!identifier) {
                error = GB_await_error();
            }
            else {
                if (!identifier[0]) {
                    error = GBS_global_string("empty identifier generated (for species '%s')", shortname);
                }
                else {
                    StringMap::const_iterator found = identifier2shortname.find(identifier);
                    if (found != identifier2shortname.end()) {
                        const string& prev_shortnames = found->second;
                        identifier2shortname[identifier] = prev_shortnames+','+shortname;
                    }
                    else {
                        identifier2shortname[identifier] = shortname;
                    }
                }
                free(identifier);
            }
        }
    }

    error = ta.close(error);

    return error;
}

GB_ERROR TREE_translate_labels(GBDATA *gb_main, TreeNode *tree, const LabelTranslator& translator) {
    // @@@R make this a member of LabelTranslator.
    // @@@R afterwards: make most of class interfaces private.

    GB_ERROR error = NULp;

    error = translator.generate_species_identifiers(gb_main); // @@@L ensure this is performed only once!

    if (!error) {
        // translate all unlinked labels in tree:
        TranslationReport report;
        error = translator.translate_unlinked_labels_in_tree(tree, report);
    }

    if (error) {
        error = GBS_global_string("failed to translate tree labels: %s", error);
    }

    return error;
}

