// =============================================================== //
//                                                                 //
//   File      : arb_export_newick.cxx                             //
//   Purpose   : used by the SILVA pipeline to export trees for    //
//               which the tree leafs are labeled by NDS and not   //
//               by the species ID (name) of the sequence.         //
//                                                                 //
//   Institute of Microbiology (Technical University Munich)       //
//   http://www.arb-home.de/                                       //
//                                                                 //
// =============================================================== //

#include <TreeWrite.h>
#include <TreeNode.h>
#include <arb_handlers.h>
#include <arb_global_defs.h>
#include <gb_aci.h>
#include <string>
#include <cstdlib>

using namespace std;

class CLI : virtual Noncopyable {
    bool     helpWanted;
    GB_ERROR error;

    string       database;              // name of input database
    string       tree;                  // name of the tree to export
    string       newick_file;           // name of the file the newick tree is exported to
    string       leaf_aci;              // aci to generate the leaf names
    LabelQuoting quoting_mode;          // none, single or double. single and double will be forced
    bool         add_branch_length;     // true -> branch lengths added to the newick tree
    bool         add_bootstraps;        // true -> bootstrap values added to the newick tree
    bool         add_group_names;       // true -> group names added to the newick tree
    bool         replace_problem_chars; // true -> problem chars are replaced in the newick tree
    bool         pretty;                // true -> prettify the newick tree

    static inline const char *getarg(int& argc, const char**& argv) {
        return argc>0 ? (--argc,*argv++) : NULp;
    }
    inline const char *expect_arg(int& argc, const char**& argv) {
        const char *arg = getarg(argc, argv);
        if (!arg) {
            error = "expected argument missing";
            arg   = "";
        }
        return arg;
    }
    inline LabelQuoting parse_quoting_mode(int& argc, const char**& argv) {
        const char *quoting_mode_str= expect_arg(argc, argv);
        if (strcasecmp(quoting_mode_str, "none") == 0) {
            return LABEL_DISALLOW_QUOTES;
        } else if (strcasecmp(quoting_mode_str, "single") == 0) {
            return LabelQuoting(LABEL_SINGLE_QUOTES | LABEL_FORCE_QUOTES);
        } else if (strcasecmp(quoting_mode_str, "double") == 0) {
            return LabelQuoting(LABEL_DOUBLE_QUOTES | LABEL_FORCE_QUOTES);
        } else {
            error =  GBS_global_string("unknown quoting mode '%s'", quoting_mode_str);
            return LABEL_DISALLOW_QUOTES;
       }
    }

    void parse(int& argc, const char**& argv) {
        const char *arg = getarg(argc, argv);
        if (arg) {
            if      (strcmp(arg, "--db")                    == 0) database              = expect_arg(argc, argv);
            else if (strcmp(arg, "--tree")                  == 0) tree                  = expect_arg(argc, argv);
            else if (strcmp(arg, "--newick-file")           == 0) newick_file           = expect_arg(argc, argv);
            else if (strcmp(arg, "--leaf-aci")              == 0) leaf_aci              = expect_arg(argc, argv);
            else if (strcmp(arg, "--quoting")               == 0) quoting_mode          = parse_quoting_mode(argc, argv);
            else if (strcmp(arg, "--add-branch-lengths")    == 0) add_branch_length     = true;
            else if (strcmp(arg, "--add-bootstraps")        == 0) add_bootstraps        = true;
            else if (strcmp(arg, "--add-group-names")       == 0) add_group_names       = true;
            else if (strcmp(arg, "--replace-problem-chars") == 0) replace_problem_chars = true;
            else if (strcmp(arg, "--pretty")                == 0) pretty                = true;
            else if (strcmp(arg, "--help")                  == 0) helpWanted            = true;
            else {
                error = GBS_global_string("unexpected argument '%s'", arg);
            }
        }
    }
    void check_required_arguments() {
        if (database.empty()) error = "no input database specified";
        else if (tree.empty()) error = "no tree name specified";
        else if (newick_file.empty()) error = "no output file specified";
    }

public:
    CLI(int argc, const char **argv) :
        helpWanted(false),
        error(NULp),
        leaf_aci("readdb(\"name\")"),
        quoting_mode(LABEL_DISALLOW_QUOTES),
        add_branch_length(false),
        add_bootstraps(false),
        add_group_names(false),
        replace_problem_chars(false),
        pretty(false)

    {
        --argc; ++argv;
        while (!error && argc>0 && !helpWanted) {
            parse(argc, argv);
        }

        if (!helpWanted) { // do not check extended conditions, if '--help' seen
            if (!error) {
                check_required_arguments();
                if (error) helpWanted = true;
            }
        }
    }

    void show_help() const {
        fputs("\n"
              "arb_export_newick -- export a tree in newick format\n"
              "Usage: arb_export_newick [switches]\n"
              "\n"
              "mandatory arguments:\n"
              "--db <dbname>              ARB database to export from\n"
              "--tree <treename>          name of the tree to export\n"
              "--newick-file <outname>    name of generated newick file\n"
              "\n"
              "switches:\n"
              "--leaf-aci <aci>           specify content for the leaf names using ACI\n"
              "                           (default: \"readdb(name)\"; see http://help.arb-home.de/aci.html)\n"
              "--quoting <mode>           none, single, double. Single and double are forced.\n"
              "                           (default: none)\n"
              "--add-branch-lengths       add the branch lengths to the newick file.\n"
              "                           (default: branch lengths are omitted)\n"
              "--add-bootstraps           add the bootstrap values to the newick file.\n"
              "                           (default: bootstrap values are omitted)\n"
              "--add-group-names          add the group names to the newick file.\n"
              "                           (default: group names are omitted)\n"
              "--replace-problem-chars    problematic characters in names will be replaced\n"
              "                           (default: no characters are replaced)\n"
              "--pretty                   prettify the newick tree\n"
              "                           (default: tree is not prettified)\n"
              "--help                     show this help message\n"
              "\n"
              ,stderr);
    }

    bool help_wanted()                     const { return helpWanted; }
    GB_ERROR get_error()                   const { return error; }

    const char *get_database()             const { return database.c_str(); }
    const char *get_tree()                 const { return tree.c_str(); }
    const char *get_newick_file()          const { return newick_file.c_str(); }
    const char *get_leaf_aci()             const { return leaf_aci.c_str(); }
    LabelQuoting get_quoting_mode()        const { return quoting_mode; }

    bool shall_add_branch_length()         const { return add_branch_length; }
    bool shall_add_bootstraps()            const { return add_bootstraps; }
    bool shall_add_group_names()           const { return add_group_names; }
    bool shall_replace_problem_chars()     const { return replace_problem_chars; }
    bool shall_be_pretty()                 const { return pretty; }
};


class ACI_Labeler: public TreeLabeler {
    SmartCharPtr leaf_aci;

public:
    explicit ACI_Labeler(const char *leaf_aci_) : leaf_aci(strdup(leaf_aci_)) {}

    const char *speciesLabel(GBDATA *gb_main, GBDATA *gb_species, TreeNode *, const char *tree_name) const OVERRIDE {
        GBL_env      env(gb_main, tree_name);
        GBL_call_env callEnv(gb_species, env);

        char* node_text = GB_command_interpreter_in_env("", leaf_aci.content(), callEnv);
        if (!node_text) {
            GB_ERROR ndsError = GB_await_error();
            node_text = GBS_global_string_copy("<error: %s>", ndsError);
            GB_export_error(ndsError);
        }

        RETURN_LOCAL_ALLOC(node_text);
    }
    const char *groupLabel(GBDATA *, GBDATA *, TreeNode *innerNode, const char *) const {
        // ACI is not used for group names
        return innerNode->name;
    }
};

static GB_ERROR export_newick(const CLI& args) {

    ARB_redirect_handlers_to(stderr, stderr);
    GB_ERROR error = NULp;

    const char *dbname  = args.get_database();
    GB_shell    shell;
    GBDATA     *gb_main = GB_open(dbname, "r");

    if (!gb_main) {
        error = GB_await_error();
    }
    else {
        ACI_Labeler labeler(args.get_leaf_aci());

        LabelQuoting quoting_mode = args.get_quoting_mode();
        if (args.shall_replace_problem_chars()) {
            quoting_mode = LabelQuoting(quoting_mode|LABEL_FORCE_REPLACE);
        }

        error = TREE_write_Newick(gb_main,
                                  args.get_tree(),
                                  labeler,
                                  args.shall_add_branch_length(),
                                  args.shall_add_bootstraps(),
                                  args.shall_add_group_names(),
                                  args.shall_be_pretty(),
                                  quoting_mode,
                                  args.get_newick_file());

        // get possible NDS error, too
        if (!error) error = GB_incur_error();
        GB_close(gb_main);
    }

    return error;
}

int main(int argc, char **argv) {

    CLI args(argc, const_cast<const char**>(argv));
    GB_ERROR error = args.get_error();

    if (!error && args.help_wanted()) {
        args.show_help();
        return EXIT_FAILURE;
    }

    if (!error) error = export_newick(args);

    if (error) {
        fprintf(stderr, "Error: %s\n", error);
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
