// ================================================================= //
//                                                                   //
//   File      : gb_aci_impl.h                                       //
//   Purpose   : provide code useful to implement ACI commands       //
//                                                                   //
//   Institute of Microbiology (Technical University Munich)         //
//   http://www.arb-home.de/                                         //
//                                                                   //
// ================================================================= //

#ifndef GB_ACI_IMPL_H
#define GB_ACI_IMPL_H

#ifndef GB_ACI_H
#include <gb_aci.h>
#endif

// export stream
#define PASS_2_OUT(args,s)  (args)->output.insert(s)
#define COPY_2_OUT(args,s)  PASS_2_OUT(args, ARB_strdup(s))
#define IN_2_OUT(args,i)    PASS_2_OUT(args, args->input.get_smart(i))
#define PARAM_2_OUT(args,i) PASS_2_OUT(args, args->get_param_smart(i))

#define FORMAT_2_OUT(args,fmt,value)  PASS_2_OUT(args, GBS_global_string_copy(fmt, value))

// ----------------------------
//      Parameter functions

namespace GBL_IMPL {

    const char *search_matching_parenthesis(const char *source);
    inline char *search_matching_parenthesis(char *source) {
        return const_cast<char*>(search_matching_parenthesis(const_cast<const char*>(source)));
    }


    struct gbl_param {
        gbl_param  *next;
        GB_TYPES    type;                               // type of variable
        void       *varaddr;                            // address of variable where value gets stored
        const char *param_name;                         // parameter name (e.g. 'include=')
        const char *help_text;                          // help text for parameter
    };

#define GBL_BEGIN_PARAMS gbl_param *params = NULp

    inline void gbl_new_param(gbl_param **pp, GB_TYPES type, void *vaddr, const char *param_name, const char *help_text) {
        gbl_param *gblp = ARB_calloc<gbl_param>(1);

        gblp->next = *pp;
        *pp         = gblp;

        gblp->type       = type;
        gblp->varaddr    = vaddr;
        gblp->param_name = param_name;
        gblp->help_text  = help_text;
    }

    typedef const char   *String;
    typedef int           bit;
    typedef unsigned int  nat;

    inline int gbl_param_int(const char *param_name, int def, const char *help_text, gbl_param **pp, int *vaddr) {
        gbl_new_param(pp, GB_INT, vaddr, param_name, help_text);
        return def;
    }

    inline char gbl_param_char(const char *param_name, char def, const char *help_text, gbl_param **pp, char *vaddr) {
        gbl_new_param(pp, GB_BYTE, vaddr, param_name, help_text);
        return def;
    }

    inline nat gbl_param_nat(const char *param_name, nat def, const char *help_text, gbl_param **pp, nat *vaddr) {
        gbl_new_param(pp, GB_INT, vaddr, param_name, help_text);
        return def;
    }

    inline const char *gbl_param_String(const char *param_name, const char *def, const char *help_text, gbl_param **pp, String *vaddr) {
        gbl_new_param(pp, GB_STRING, vaddr, param_name, help_text);
        return def;
    }

    inline int gbl_param_bit(const char *param_name, int def, const char *help_text, gbl_param **pp, bit *vaddr) {
        gbl_new_param(pp, GB_BIT, vaddr, param_name, help_text);
        return def;
    }

    GB_ERROR trace_params(const GBL_streams& param, gbl_param *ppara, const char *com);
};

#define GBL_PARAM_TYPE(type, var, param_name, def, help_text) type  var = gbl_param_##type(param_name, def, help_text, &params, &var)
#define GBL_STRUCT_PARAM_TYPE(type, strct, member, param_name, def, help_text) strct.member = gbl_param_##type(param_name, def, help_text, &params, &strct.member)

// use PARAM_IF for parameters whose existence depends on condition
#define PARAM_IF(cond,param) ((cond) ? (param) : NULp)

#define GBL_PARAM_INT(var,    param_name, def, help_text) GBL_PARAM_TYPE(int,    var, param_name, def, help_text)
#define GBL_PARAM_CHAR(var,   param_name, def, help_text) GBL_PARAM_TYPE(char,   var, param_name, def, help_text)
#define GBL_PARAM_UINT(var,   param_name, def, help_text) GBL_PARAM_TYPE(nat,    var, param_name, def, help_text)
#define GBL_PARAM_STRING(var, param_name, def, help_text) GBL_PARAM_TYPE(String, var, param_name, def, help_text)
#define GBL_PARAM_BIT(var,    param_name, def, help_text) GBL_PARAM_TYPE(bit,    var, param_name, def, help_text)

#define GBL_STRUCT_PARAM_INT(strct,    member, param_name, def, help_text) GBL_STRUCT_PARAM_TYPE(int,    strct, member, param_name, def, help_text)
#define GBL_STRUCT_PARAM_CHAR(strct,   member, param_name, def, help_text) GBL_STRUCT_PARAM_TYPE(char,   strct, member, param_name, def, help_text)
#define GBL_STRUCT_PARAM_UINT(strct,   member, param_name, def, help_text) GBL_STRUCT_PARAM_TYPE(nat,    strct, member, param_name, def, help_text)
#define GBL_STRUCT_PARAM_STRING(strct, member, param_name, def, help_text) GBL_STRUCT_PARAM_TYPE(String, strct, member, param_name, def, help_text)
#define GBL_STRUCT_PARAM_BIT(strct,    member, param_name, def, help_text) GBL_STRUCT_PARAM_TYPE(bit,    strct, member, param_name, def, help_text)

#define GBL_TRACE_PARAMS(args)                                                          \
    do {                                                                                \
        GB_ERROR def_error =                                                            \
            trace_params((args)->get_param_streams(), params, (args)->get_cmdName());   \
        gb_assert((args)->set_params_checked());                                        \
        if (def_error) { GBL_END_PARAMS; return def_error; }                            \
    } while(0)

#define GBL_END_PARAMS                                                  \
    do {                                                                \
        gbl_param *_gblp;                                               \
        while (params) {                                                \
            _gblp = params;                                             \
            params = params->next;                                      \
            free(_gblp);                                                \
        }                                                               \
    } while (0)

#define GBL_CHECK_FREE_PARAM(nr, cnt)           \
    do {                                        \
        if ((nr)+(cnt) >= GBL_MAX_ARGUMENTS) {  \
            /* gb_assert(0); */                 \
            return "max. parameters exceeded";  \
        }                                       \
    } while (0)

// @@@ remove GBL_MAX_ARGUMENTS - instead allocate dynamic

// --------------------------------
//      parameter/stream checks

namespace GBL_IMPL {

    extern int traceACI;
    extern int traceIndent;

    void print_trace(const char *text);

    inline void modify_trace_indent(int diff) {
        traceIndent += diff;
        gb_assert(traceIndent>=0);
    }

    inline GB_ERROR check_no_parameter(GBL_command_arguments *args) {
        gb_assert(args->set_params_checked());
        if (args->param_count() == 0) return NULp;
        return GBS_global_string("syntax: %s (no parameters)", args->get_cmdName());
    }
    inline GB_ERROR check_has_parameters(GBL_command_arguments *args, const char *param_syntax) {
        gb_assert(args->set_params_checked());
        if (args->param_count() > 0) return NULp;
        return GBS_global_string("syntax: %s(%s)", args->get_cmdName(), param_syntax);
    }
    inline GB_ERROR check_parameters(GBL_command_arguments *args, int expected, const char *parameterList) {
        gb_assert(args->set_params_checked());
        if (args->param_count() == expected) return NULp;
        return GBS_global_string("syntax: %s(%s)", args->get_cmdName(), parameterList);
    }
    inline GB_ERROR check_optional_parameters(GBL_command_arguments *args, int fix, const char *fixParam, int opt, const char *optParam, bool opt_trailing, bool opt_expect_all) {
        // if opt_expect_all == true -> checks whether either NO or ALL optional parameters were specified
        // if opt_expect_all == false -> accept any number of parameters -> specify 'optParam' e.g. like "p3[,p4[,p5]]"
        // if opt_trailing == false -> optional parameters are BEFORE fixed (don't use!)
        gb_assert(args->set_params_checked());

        int params = args->param_count();
        if (params == fix || params == (fix+opt)) return NULp;
        if (!opt_expect_all && params >= fix && params <= (fix+opt)) return NULp;
        if (!fix) return GBS_global_string("syntax: %s[(%s)]", args->get_cmdName(), optParam);
        if (opt_trailing) return GBS_global_string("syntax: %s(%s[,%s])", args->get_cmdName(), fixParam, optParam);
        return GBS_global_string("syntax: %s([%s,]%s)", args->get_cmdName(), optParam, fixParam);
    }

    inline GB_ERROR check_valid_index(int number, const char *what, int min, int max) {
        if (number >= min && number <= max) return NULp;
        return GBS_global_string("Illegal %s number '%i' (allowed [%i..%i])", what, number, min, max);
    }
    inline GB_ERROR check_valid_stream_index(GBL_command_arguments *args, int number) { return check_valid_index(number, "stream", 1, args->input.size()); }
    inline GB_ERROR check_valid_param_index (GBL_command_arguments *args, int number) { return check_valid_index(number, "param",  0, args->param_count()-1); }

    inline GB_ERROR check_item_referenced(GBL_command_arguments *args) {
        return args->get_item_ref()
            ? NULp
            : GBS_global_string("command %s cannot be called w/o item", args->get_cmdName());
    }
};

#define TRACE_ACI(text) if (GBL_IMPL::traceACI) GBL_IMPL::print_trace(text)

#define DO_AND_RETURN_ON_ERROR(cmd) do {                            \
        GB_ERROR perr = (cmd);                                      \
        if (perr) return perr;                                      \
    } while(0)

#define EXPECT_NO_PARAM(args)             DO_AND_RETURN_ON_ERROR(GBL_IMPL::check_no_parameter(args))
#define EXPECT_PARAMS_PASSED(args,syntax) DO_AND_RETURN_ON_ERROR(GBL_IMPL::check_has_parameters(args,syntax))
#define ACCEPT_ANY_PARAMS(args)           gb_assert((args)->set_params_checked())

#define EXPECT_PARAMS(args,cnt,help)                                                DO_AND_RETURN_ON_ERROR(GBL_IMPL::check_parameters(args, cnt, help))
#define EXPECT_OPTIONAL_PARAMS(args,fixCnt,fixhelp,optCnt,opthelp)                  DO_AND_RETURN_ON_ERROR(GBL_IMPL::check_optional_parameters(args, fixCnt, fixhelp, optCnt, opthelp, true, true))
#define EXPECT_OPTIONAL_PARAMS_CUSTOM(args,fixCnt,fixhelp,optCnt,opthelp,trail,all) DO_AND_RETURN_ON_ERROR(GBL_IMPL::check_optional_parameters(args, fixCnt, fixhelp, optCnt, opthelp, trail, all))

#define EXPECT_LEGAL_STREAM_INDEX(args,number) do {                             \
        GB_ERROR serr = GBL_IMPL::check_valid_stream_index(args, number);       \
        if (serr) return serr;                                                  \
    } while(0)

#define EXPECT_ITEM_REFERENCED(args) DO_AND_RETURN_ON_ERROR(GBL_IMPL::check_item_referenced(args))


#define COMMAND_DROPS_INPUT_STREAMS(args) do {                                                                          \
        if (GBL_IMPL::traceACI && args->input.size()>0) {                                                              \
            if (args->input.size()>1 || args->input.get(0)[0]) {                                                        \
                GBL_IMPL::print_trace(GBS_global_string("Warning: Dropped %i input streams\n", args->input.size())); \
            }                                                                                                           \
        }                                                                                                               \
    } while(0)


#else
#error gb_aci_impl.h included twice
#endif // GB_ACI_IMPL_H
