// ============================================================= //
//                                                               //
//   File      : arb_calc_pvp.cxx                                //
//   Purpose   : calculate PVP from CLI                          //
//                                                               //
//   Coded by Ralf Westram (coder@reallysoft.de) in April 2018   //
//   http://www.arb-home.de/                                     //
//                                                               //
// ============================================================= //

#include <pvp.h>
#include <TreeRead.h>
#include <string>

using namespace std;

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

    // data from optional CLI arguments:
    string savename; // if non-empty -> used as savename
    string treefile; // if non-empty -> tree gets imported
    bool   tree_delete;
    string sainame; // if non-empty -> used as SAI name

    // non-optional CLI arguments:
    int fixed_args;
#define FIXED_ARGS_WANTED 3
    string database;
    string treename;
    string aliname;

    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;
    }
    void show_help() const {
        fputs("\n"
              "arb_calc_pvp -- calculate positional variability SAI\n"
              "Usage: arb_calc_pvp [switches] <database> <treename> <aliname>\n"
              "\n"
              "switches:\n"
              "--savename <dbname>        save database with different name\n"
              "                           (default is to overwrite input <database>)\n"
              "--tree-import <treefile>   load tree from <treefile> + store in database as <treename>\n"
              "                           (default: use tree existing in database)\n"
              "--tree-delete              delete tree <treename> (before saving database)\n"
              "--sainame <sai>            use alternate SAI name (default: POS_VAR_BY_PARSIMONY)\n"
              "\n"
              ,stderr);
    }


    void handle_fixed_argument(const char *arg) {
        arb_assert(!error);

        if (arg) {
            switch (fixed_args) {
                case 0: database = arg; break;
                case 1: treename = arg; break;
                case 2: aliname  = arg; break;

                default:
                    error = GBS_global_string("too many arguments ('%s')", arg);
                    break;
            }

            if (!error) ++fixed_args;
        }
        else { // done with parsing CLI -> check missing
            if (fixed_args != FIXED_ARGS_WANTED) {
                const char *miss = NULp;
                switch (fixed_args) {
                    case 0: miss = "database"; break;
                    case 1: miss = "treename"; break;
                    case 2: miss = "aliname"; break;
                    default: arb_assert(0); break;
                }
                error = GBS_global_string("Mandatory argument <%s> has not been provided", miss);
            }
        }
    }

    void parse(int& argc, const char**& argv) {
        const char *arg = getarg(argc, argv);
        if (arg) {
            if      (strcmp(arg, "--savename")    == 0) savename = expect_arg(argc, argv);
            else if (strcmp(arg, "--tree-import") == 0) treefile = expect_arg(argc, argv);
            else if (strcmp(arg, "--tree-delete") == 0) tree_delete = true;
            else if (strcmp(arg, "--sainame")     == 0) sainame = expect_arg(argc, argv);

            else if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) helpWanted = true;

            else handle_fixed_argument(arg);
        }
    }

public:
    CLI(int argc, const char **argv) :
        helpWanted(false),
        error(NULp),
        tree_delete(false),
        fixed_args(0)
    {
        --argc; ++argv;
        while (!error && argc>0 && !helpWanted) {
            parse(argc, argv);
        }
        if (!error && !helpWanted) handle_fixed_argument(NULp);
    }

    bool help_wanted() const { return helpWanted; }
    void show_help_if_useful() const { if (helpWanted) show_help(); }
    GB_ERROR get_error() const { return error; }

    const char *get_database() const { return database.c_str(); }
    const char *get_treename() const { return treename.c_str(); }
    const char *get_aliname()  const { return aliname.c_str(); }

    const char *get_database_savename() const { return savename.empty() ? get_database() : savename.c_str(); }
    const char *get_SAI_name() const { return sainame.empty() ? "POS_VAR_BY_PARSIMONY" : sainame.c_str(); }

    bool shall_import_tree() const { return !treefile.empty(); }
    const char *get_treeimport_filename() const { arb_assert(shall_import_tree()); return treefile.c_str(); }

    bool shall_delete_tree() const { return tree_delete; }
};

static GB_ERROR calc_pvp(const CLI& args) {
    GB_shell    shell;
    const char *dbname  = args.get_database();
    GBDATA     *gb_main = GB_open(dbname, "rw");
    GB_ERROR    error   = NULp;

    if (!gb_main) {
        error = GB_await_error();
    }
    else {
        if (!error && args.shall_import_tree()) {
            error = TREE_load_to_db(gb_main, args.get_treeimport_filename(), args.get_treename());
        }
        if (!error) {
            error = PVP_calculate(gb_main, args.get_aliname(), args.get_treename(), args.get_SAI_name());
        }
        if (!error && args.shall_delete_tree()) {
            GB_transaction ta(gb_main);

            GBDATA *gb_tree    = GBT_find_tree(gb_main, args.get_treename());
            if (gb_tree) error = GB_delete(gb_tree);
        }
        if (!error) {
            error = GB_save_as(gb_main, args.get_database_savename(), "b");
        }

        GB_close(gb_main);
    }

    return error;
}

int main(int argc, char **argv) {
    GB_ERROR error = NULp;
    CLI      args(argc, const_cast<const char**>(argv));

    if (!error) error = args.get_error();

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

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

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