// -*- c++ -*-

// This file is part of the Collective Variables module (Colvars).
// The original version of Colvars and its updates are located at:
// https://github.com/Colvars/colvars
// Please update all Colvars source files before making any changes.
// If you wish to distribute your changes, please submit them to the
// Colvars repository at GitHub.

#ifndef COLVARSCRIPT_H
#define COLVARSCRIPT_H

#include <string>
#include <vector>
#include <map>

#include "colvarmodule.h"
#include "colvarvalue.h"
#include "colvarproxy.h"


// Only these error values are part of the scripting interface
#define COLVARSCRIPT_ERROR -1
#define COLVARSCRIPT_OK 0

#define COLVARSCRIPT_MAGIC 0x5349474E

class colvardeps;

class colvarscript {

public:
  /// This magic number is used to validate colvarscript pointers
  uint32_t const magic = COLVARSCRIPT_MAGIC;

  /// Validate colvarscript pointer to catch invalid pointers passed by legacy code
  /// e.g. legacy NAMD Tcl interface
  static bool is_valid(const colvarscript* script_ptr) {
    if (!script_ptr) return false;
    // if address is inaccessible, this will sefgault
    return script_ptr->magic == COLVARSCRIPT_MAGIC;
  }

private:

  colvarproxy *proxy_;
  colvarmodule *cvmodule;

  inline colvarscript() {} // no-argument construction forbidden

public:

  friend class colvarproxy;

  colvarscript(colvarproxy *p, colvarmodule *m);

  ~colvarscript();

  /// String representation of the result of a script call
  std::string str_result_;

  /// Run a script command with space-separated positional arguments (objects)
  int run(int objc, unsigned char *const objv[]);

  /// Get the string result of the current scripting call
  inline std::string const &str_result() const
  {
    return str_result_;
  }

  /// Modify the string result of the current scripting call
  inline std::string &modify_str_result()
  {
    return str_result_;
  }

  /// Set the return value to the given string
  int set_result_str(std::string const &s);

  /// Clear the string result
  int clear_str_result();

  /// Add the given string to the error message of the script interface
  void add_error_msg(std::string const &s);

  /// Commands available
  enum command {
#define CVSCRIPT_ENUM_COMM(COMM) COMM,
#undef CVSCRIPT
#define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY)  \
  CVSCRIPT_ENUM_COMM(COMM)
#ifdef COLVARSCRIPT_COMMANDS_H
#undef COLVARSCRIPT_COMMANDS_H
#endif
#include "colvarscript_commands.h"
#undef COLVARSCRIPT_COMMANDS_H
#undef CVSCRIPT
#undef CVSCRIPT_ENUM_COMM
    cv_n_commands
  };

  /// Type of object handling a script command
  enum Object_type {
    use_module,
    use_colvar,
    use_bias
  };

  /// Return the prefix of the individual command for each object function
  std::string get_cmd_prefix(Object_type t);

  /// Get a pointer to the i-th argument of the command (NULL if not given)
  template<Object_type T>
  unsigned char *get_cmd_arg(int iarg, int objc, unsigned char *const objv[]);

  /// Instantiation of get_cmd_arg<> for module-level commands
  unsigned char *get_module_cmd_arg(int iarg, int objc,
                                    unsigned char *const objv[]);

  /// Instantiation of get_cmd_arg<> for colvar-level commands
  unsigned char *get_colvar_cmd_arg(int iarg, int objc,
                                    unsigned char *const objv[]);

  /// Instantiation of get_cmd_arg<> for bias-level commands
  unsigned char *get_bias_cmd_arg(int iarg, int objc,
                                  unsigned char *const objv[]);

  /// Check the argument count of the command
  template<Object_type T>
  int check_cmd_nargs(char const *cmd, int objc,
                      int n_args_min, int n_args_max);

  /// Instantiation of check_cmd_nargs<> for module-level commands
  int check_module_cmd_nargs(char const *cmd, int objc,
                             int n_args_min, int n_args_max);

  /// Instantiation of check_cmd_nargs<> for colvar-level commands
  int check_colvar_cmd_nargs(char const *cmd, int objc,
                             int n_args_min, int n_args_max);

  /// Instantiation of get_cmd_arg<> for bias-level commands
  int check_bias_cmd_nargs(char const *cmd, int objc,
                           int n_args_min, int n_args_max);

  /// Number of positional arguments to shift for each object type
  template<colvarscript::Object_type T>
  int cmd_arg_shift();

  /// Get names of all commands
  inline char const **get_command_names() const
  {
    return cmd_names;
  }

  /// Get one-line help summary for a command
  /// \param cmd Name of the command's function (e.g. "cv_units")
  char const *get_command_help(char const *cmd);

  /// Get description of the return value of a command
  /// \param cmd Name of the command's function (e.g. "cv_units")
  char const *get_command_rethelp(char const *cmd);

  /// Get description of the argument of a command (excluding prefix)
  /// \param cmd Name of the command's function (e.g. "cv_units")
  /// \param i Index of the argument; 0 is the first argument after the
  /// prefix, e.g. "value" has an index of 0 in the array of arguments:
  /// { "cv", "colvar", "xi", "value" }
  char const *get_command_arghelp(char const *cmd, int i);

  /// Get number of required arguments (excluding prefix)
  /// \param cmd Name of the command's function (e.g. "cv_units")
  int get_command_n_args_min(char const *cmd);

  /// Get number of total arguments (excluding prefix)
  /// \param cmd Name of the command's function (e.g. "cv_units")
  int get_command_n_args_max(char const *cmd);

  /// Set the main command for the CLI, when it is not "cv" (e.g. LAMMPS)
  inline void set_cmdline_main_cmd(std::string const &cmd) {
    cmdline_main_cmd_ = cmd;
  }

  /// Get help string for a command (does not specify how it is launched)
  /// \param cmd Name of the command's function (e.g. "cv_units")
  char const *get_command_full_help(char const *cmd);

  /// Get summary of command line syntax for all commands of a given context
  /// \param t One of use_module, use_colvar or use_bias
  std::string get_cmdline_help_summary(Object_type t);

  /// Get a description of how the command should be used in a command line
  /// \param t One of use_module, use_colvar or use_bias
  /// \param c Value of the \link command \endlink enum
  std::string get_command_cmdline_syntax(Object_type t, command c);

  /// Get the command line syntax following by the help string
  /// \param t One of use_module, use_colvar or use_bias
  /// \param cmd Name of the subcommand (e.g. "units")
  std::string get_command_cmdline_help(Object_type t, std::string const &cmd);

  /// Set error code for unsupported script operation
  int unsupported_op();

  /// Pointer to the Colvars main object
  inline colvarmodule *module()
  {
    return this->cvmodule;
  }

  /// Pointer to the colvarproxy object (interface with host engine)
  inline colvarproxy *proxy()
  {
    return this->proxy_;
  }

  // Input functions - get the string reps of script argument objects

  /// Get the string representation of an object (by default, a simple cast)
  char *obj_to_str(unsigned char *obj);

  /// Get a list of strings from an object (does not work with a simple cast)
  std::vector<std::string> obj_to_str_vector(unsigned char *obj);


  // Output functions - convert internal objects to representations suitable
  // for use in the scripting language.  At the moment only conversion to C
  // strings is supported, and obj is assumed to be a char * pointer.

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_int(int const &x, unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_int_vec(std::vector<int> const &x, unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_long_int(long int const &x, unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_long_int_vec(std::vector<long int> const &x,
                              unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_real(cvm::real const &x, unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  template <typename T>
  int set_result_real_vec(T const &x,
                          unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_rvector(cvm::rvector const &x, unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  template <typename T>
  int set_result_rvector_vec(T const &x,
                             unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_colvarvalue(colvarvalue const &x, unsigned char *obj = NULL);

  /// Copy x into obj if not NULL, or into the script object's result otherwise
  int set_result_colvarvalue_vec(std::vector<colvarvalue> const &x,
                                 unsigned char *obj = NULL);

private:

  /// Set up all script API functions
  int init_commands();

  /// Set up a single script API function
  int init_command(colvarscript::command const &comm,
                   char const *name, char const *help,
                   int n_args_min, int n_args_max, char const *arghelp,
                   int (*fn)(void *, int, unsigned char * const *));

public: // TODO this function will be removed soon

  /// Run subcommands on base colvardeps object (colvar, bias, ...)
  int proc_features(colvardeps *obj,
                    int argc, unsigned char *const argv[]);

private: // TODO

  /// Internal identifiers of command strings
  std::map<std::string, command> cmd_str_map;

  /// Main command used in command line ("cv" by default)
  std::string cmdline_main_cmd_;

  /// Inverse of cmd_str_map (to be exported outside this class)
  char const **cmd_names;

  /// Help strings for each command
  std::vector<std::string> cmd_help;

  /// Description of the return values of each command (may be empty)
  std::vector<std::string> cmd_rethelp;

  /// Minimum number of arguments for each command
  std::vector<size_t> cmd_n_args_min;

  /// Maximum number of arguments for each command
  std::vector<size_t> cmd_n_args_max;

  /// Help strings for each command argument
  std::vector< std::vector<std::string> > cmd_arghelp;

  /// Full help strings for each command
  std::vector<std::string> cmd_full_help;

  /// Implementations of each command
  std::vector<int (*)(void *, int, unsigned char * const *)> cmd_fns;

  /// Get a pointer to the implementation of the given command
  inline int (*get_cmd_fn(std::string const &cmd_key))(void *,
                                                       int,
                                                       unsigned char * const *)
  {
    if (cmd_str_map.count(cmd_key) > 0) {
      return cmd_fns[cmd_str_map[cmd_key]];
    }
    return NULL;
  }

  /// Set obj equal to x, using its string representation
  template <typename T>
  int set_result_text(T const &x, unsigned char *obj);

  /// Code reused by instances of set_result_text()
  template <typename T>
  int pack_vector_elements_text(T const &x, std::string &x_str);

  /// Code reused by all instances of set_result_text()
  int set_result_text_from_str(std::string const &x_str, unsigned char *obj);


};


/// Get a pointer to the colvarscript object pointed to by pobj
inline static colvarscript *colvarscript_obj(void *pobj)
{
  return reinterpret_cast<colvarscript *>(pobj);
}

/// Get a pointer to the main colvarscript object
inline static colvarscript *colvarscript_obj()
{
  return cvm::main()->proxy->script;
}

/// Get a pointer to the colvar object pointed to by pobj
inline static colvar *colvar_obj(void *pobj)
{
  return reinterpret_cast<colvar *>(pobj);
}


/// Get a pointer to the colvarbias object pointed to by pobj
inline static colvarbias *colvarbias_obj(void *pobj)
{
  return reinterpret_cast<colvarbias *>(pobj);
}



template<colvarscript::Object_type T>
unsigned char *colvarscript::get_cmd_arg(int iarg,
                                         int objc,
                                         unsigned char *const objv[])
{
  int const shift = cmd_arg_shift<T>();
  return (shift+iarg < objc) ? objv[shift+iarg] : NULL;
}


inline unsigned char *colvarscript::get_module_cmd_arg(int iarg, int objc,
                                                       unsigned char *const objv[])
{
  return get_cmd_arg<use_module>(iarg, objc, objv);
}


inline unsigned char *colvarscript::get_colvar_cmd_arg(int iarg, int objc,
                                                       unsigned char *const objv[])
{
  return get_cmd_arg<use_colvar>(iarg, objc, objv);
}


inline unsigned char *colvarscript::get_bias_cmd_arg(int iarg, int objc,
                                                     unsigned char *const objv[])
{
  return get_cmd_arg<use_bias>(iarg, objc, objv);
}


template<colvarscript::Object_type T>
int colvarscript::check_cmd_nargs(char const *cmd,
                                  int objc,
                                  int n_args_min,
                                  int n_args_max)
{
  int const shift = cmd_arg_shift<T>();
  if (objc < shift+n_args_min) {
    add_error_msg("Insufficient number of arguments ("+cvm::to_str(objc)+
                  ") for script function \""+std::string(cmd)+
                  "\":\n"+get_command_full_help(cmd));
    return COLVARSCRIPT_ERROR;
  }
  if (objc > shift+n_args_max) {
    add_error_msg("Too many arguments ("+cvm::to_str(objc)+
                  ") for script function \""+std::string(cmd)+
                  "\":\n"+get_command_full_help(cmd));
    return COLVARSCRIPT_ERROR;
  }
  return COLVARSCRIPT_OK;
}


inline int colvarscript::check_module_cmd_nargs(char const *cmd,
                                                int objc,
                                                int n_args_min,
                                                int n_args_max)
{
  return check_cmd_nargs<use_module>(cmd, objc, n_args_min, n_args_max);
}


inline int colvarscript::check_colvar_cmd_nargs(char const *cmd,
                                                int objc,
                                                int n_args_min,
                                                int n_args_max)
{
  return check_cmd_nargs<use_colvar>(cmd, objc, n_args_min, n_args_max);
}


inline int colvarscript::check_bias_cmd_nargs(char const *cmd,
                                              int objc,
                                              int n_args_min,
                                              int n_args_max)
{
  return check_cmd_nargs<use_bias>(cmd, objc, n_args_min, n_args_max);
}


template<colvarscript::Object_type T>
int colvarscript::cmd_arg_shift()
{
  int shift = 0;
  if (T == use_module) {
    // "cv" and "COMMAND" are 1st and 2nd argument, and shift is equal to 2
    shift = 2;
  } else if (T == use_colvar) {
    // Same as above with additional arguments "colvar" and "NAME"
    shift = 4;
  } else if (T == use_bias) {
    shift = 4;
  }
  return shift;
}


extern "C" {

  /// Generic wrapper for string-based scripting
  // New: requires pointer to colvarscript object - TODO - pass it in ctypes Python example
  int run_colvarscript_command(colvarscript *script, int objc, unsigned char *const objv[]);

  /// Get the string result of a script call
  const char * get_colvarscript_result();

}

#endif // #ifndef COLVARSCRIPT_H
