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

#ifndef GB_ACI_H
#define GB_ACI_H

#ifndef ARB_STR_H
#include <arb_str.h>
#endif
#ifndef ARBDBT_H
#include "arbdbt.h"
#endif
#ifndef _GLIBCXX_VECTOR
#include <vector>
#endif
#ifndef _GLIBCXX_MAP
#include <map>
#endif

#define gb_assert(cond) arb_assert(cond)

typedef SmartMallocPtr(char) GBL;

class GBL_streams {
    std::vector<GBL> content;

public:
    void insert(char *copy) { content.push_back(copy); }
    void insert(GBL smartie) { content.push_back(smartie); }
    const char *get(int idx) const { gb_assert(idx<size()); return &*content[idx]; }
    GBL get_smart(int idx) const { gb_assert(idx<size()); return content[idx]; }
    int size() const { return content.size(); }
    bool empty() const { return content.empty(); }

    void erase() { content.clear(); }

    char *concatenated() const;
    void swap(GBL_streams& other) { std::swap(content, other.content); }
};

class GBL_command_arguments;
typedef GB_ERROR (*GBL_COMMAND)(GBL_command_arguments *args);

struct GBL_command_definition {
    const char  *identifier; // command identifier (alphanumeric)
    GBL_COMMAND  function;   // function to execute

    bool is_defined() const { return identifier && function; }
    bool is_sentinel() const { return !identifier && !function; }
};

// ------------------------
//      command lookup

class GBL_command_lookup_table : virtual Noncopyable {
    struct ccp_less {
        bool operator()(const char *s1, const char *s2) const {
            return ARB_stricmp(s1, s2) < 0;
        }
    };

    typedef std::map<const char*,GBL_COMMAND,ccp_less> CmdMap;

    CmdMap defined;

public:
    GBL_command_lookup_table(const GBL_command_definition *table, unsigned size);
    virtual ~GBL_command_lookup_table() {}

    virtual GBL_COMMAND lookup(const char *identifier) const {
        CmdMap::const_iterator found = defined.find(identifier);
        return found == defined.end() ? NULp : found->second;
    }
};

const GBL_command_lookup_table& ACI_get_standard_commands(); // provides access to commands defined in adlang1.cxx

enum GBL_customization_mode {
    DENY_SUBSTITUTION,
    PERMIT_SUBSTITUTION,
};

class GBL_custom_command_lookup_table : public GBL_command_lookup_table {
    const GBL_command_lookup_table& base_table;

    void warn_about_overwritten_commands(const GBL_command_definition *custom_table, unsigned custom_size) const;
public:
    GBL_custom_command_lookup_table(const GBL_command_definition    *custom_table,
                                    unsigned                         custom_size,
                                    const GBL_command_lookup_table&  extending_this,
                                    GBL_customization_mode           cmode = DENY_SUBSTITUTION) :
        GBL_command_lookup_table(custom_table, custom_size),
        base_table(extending_this)
    {
        if (cmode == DENY_SUBSTITUTION) {
            warn_about_overwritten_commands(custom_table, custom_size);
        }
    }
    ~GBL_custom_command_lookup_table() OVERRIDE {}

    GBL_COMMAND lookup(const char *identifier) const OVERRIDE {
        GBL_COMMAND cmd = GBL_command_lookup_table::lookup(identifier);
        if (!cmd) cmd   = base_table.lookup(identifier);
        return cmd;
    }
};

// -------------------------------
//      execution environment

class GBL_env : virtual Noncopyable {
    // provides an environment for running ACI commands

    GBDATA *gb_main;               // database to use
    char   *default_tree_name;     // if we have a default tree, its name is specified here (NULp otherwise)

    const GBL_command_lookup_table& std_cmds;

public:
    GBL_env(GBDATA *gbMain, const char *treeName, const GBL_command_lookup_table& cmds = ACI_get_standard_commands()) :
        gb_main(gbMain),
        default_tree_name(nulldup(treeName)),
        std_cmds(cmds)
    {}

    ~GBL_env() { free(default_tree_name); }

    GBDATA *get_gb_main() const { return gb_main; }
    const char *get_treename() const { return default_tree_name; }

    GBL_COMMAND lookup_command(const char *identifier) const { return std_cmds.lookup(identifier); }
};


class FieldTracker {
public:
    virtual ~FieldTracker() {}
    virtual void track_field(const char *fieldname) = 0;
};

class GBL_call_env : virtual Noncopyable {
    // provides a specific call environment (for one item)

    GBDATA         *gb_ref; // the database entry on which the command is applied (may be species, gene, experiment, group and maybe more)
    const GBL_env&  env;

    mutable RefPtr<FieldTracker> tracker;

public:
    GBL_call_env(GBDATA *gbd, const GBL_env& env_) : gb_ref(gbd), env(env_), tracker(NULp) {}
    virtual ~GBL_call_env() {}

    GBDATA *get_item_ref() const { return gb_ref; } // Warning: may return NULp (see also: EXPECT_ITEM_REFERENCED)
    const GBL_env& get_env() const { return env; }

    void useFieldTracker(FieldTracker *tracker_) { tracker = tracker_; }
    const char *track_field_access(const char *fieldname) const {
        if (tracker) tracker->track_field(fieldname); // track access to itemfield
        return fieldname;
    }

    GBDATA *get_gb_main() const { return env.get_gb_main(); }
    const char *get_treename() const { return env.get_treename(); }

    char *interpret_subcommand(const char *input, const char *command) const { // @@@ should take command in pre-parsed format
        return GB_command_interpreter_in_env(input, command, *this); // @@@ swap this function with GB_command_interpreter_in_env!
    }
};

class GBL_maybe_itemless_call_env : public GBL_call_env {
    GBL_env env;
public:
    GBL_maybe_itemless_call_env(GBDATA *gb_main, GBDATA *gb_item) :
        GBL_call_env(gb_item, env),
        env(gb_main, NULp)
    {
        gb_assert(gb_main);
        gb_assert(get_gb_main());
    }
};

struct GBL_simple_call_env : public GBL_maybe_itemless_call_env { // only useable if gb_item always exists (i.e. never is NULp)
    GBL_simple_call_env(GBDATA *gb_item) :
        GBL_maybe_itemless_call_env(GB_get_root(gb_item), gb_item)
    {}
};

// -------------------------
//      execution state

class GBL_command_arguments : virtual Noncopyable {
    // provides arguments, input, output + environment

    const GBL_call_env&  callEnv;
    const char          *cmdName; // the name of the current command (used for error messages)

    GBL_streams &param;
#if defined(ASSERTION_USED)
    bool params_checked;
#endif

public:

    GBL_streams &input;
    GBL_streams &output;

    GBL_command_arguments(const GBL_call_env& cenv_,
                          const char *command_,
                          GBL_streams& input_,
                          GBL_streams& param_,
                          GBL_streams& output_)
        : callEnv(cenv_),
          cmdName(command_),
          param(param_),
          input(input_),
          output(output_)
    {
#if defined(ASSERTION_USED)
        params_checked = false;
#endif
    }
    ~GBL_command_arguments() {
        gb_assert(params_checked);
    }

    GBDATA *get_item_ref() const { return callEnv.get_item_ref(); } // Warning: may return NULp (see also: EXPECT_ITEM_REFERENCED)
    const GBL_call_env& get_callEnv() const { return callEnv; }
    const GBL_env& get_env() const { return callEnv.get_env(); }

    const char *track_field_access(const char *fieldname) const { return get_callEnv().track_field_access(fieldname ); }

    GBDATA *get_gb_main() const { return callEnv.get_gb_main(); }
    const char *get_treename() const { return callEnv.get_treename(); }
    const char *get_cmdName() const { return cmdName; }

    const GBL_streams& get_param_streams() const { return param; }
    int param_count() const { return param.size(); }
    const char *get_param(int idx) const { return param.get(idx); }
    const char *get_optional_param(int idx, const char *defaultValue) const { return idx>=0 && idx<param.size() ? param.get(idx) : defaultValue; }
    GBL get_param_smart(int idx) const { return param.get_smart(idx); }

#if defined(ASSERTION_USED)
    bool set_params_checked() {
        if (params_checked) return false;
        params_checked = true;
        return true;
    }
#endif
};

#else
#error gb_aci.h included twice
#endif // GB_ACI_H


