// -*- 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 COLVARDEPS_H
#define COLVARDEPS_H

#include "colvarmodule.h"
#include "colvarparse.h"

/// \brief Parent class for a member object of a bias, cv or cvc etc. containing features and
/// their dependencies, and handling dependency resolution
///
/// There are 3 kinds of features:
/// 1. Dynamic features are under the control of the dependency resolution
/// system. They may be enabled or disabled depending on dependencies.
/// 2. User features may be enabled based on user input (they may trigger a failure upon dependency resolution, though)
/// 3. Static features are static properties of the object, determined
///   programatically at initialization time.
///
class colvardeps {
public:

  colvardeps() {}
  virtual ~colvardeps();

  // Subclasses should initialize the following members:

  std::string description; // reference to object name (cv, cvc etc.)

  /// This contains the current state of each feature for each object
  struct feature_state {
    feature_state(bool a, bool e)
    : available(a), enabled(e) {}

    /// Feature may be enabled, subject to possible dependencies
    bool available;
    /// Currently enabled - this flag is subject to change dynamically
    /// TODO consider implications for dependency solving: anyone who disables
    /// it should trigger a refresh of parent objects
    bool enabled;     // see if this should be private depending on implementation
    // bool enabledOnce; // this should trigger an update when object is evaluated
  };


private:
  /// List of the states of all features
  std::vector<feature_state> feature_states;

  /// Enum of possible feature types
  enum feature_type {
    f_type_not_set,
    f_type_dynamic,
    f_type_user,
    f_type_static
  };

public:
  /// Pair a numerical feature ID with a description and type
  void init_feature(int feature_id, const char *description, feature_type type = f_type_not_set);

  /// Describes a feature and its dependecies
  /// used in a static array within each subclass
  class feature {

  public:
    feature() {}
    ~feature() {}

    std::string description; // Set by derived object initializer

    // features that this feature requires in the same object
    // NOTE: we have no safety mechanism against circular dependencies, however, they would have to be internal to an object (ie. requires_self or requires_alt)
    std::vector<int> requires_self;

    // Features that are incompatible, ie. required disabled
    // if enabled, they will cause a dependency failure (they will not be disabled)
    // To enforce these dependencies regardless of the order in which they
    // are enabled, they are always set in a symmetric way, so whichever is enabled
    // second will cause the dependency to fail
    std::vector<int> requires_exclude;

    // sets of features that are required in an alternate way
    // when parent feature is enabled, if none are enabled, the first one listed that is available will be enabled
    std::vector<std::vector<int> > requires_alt;

    // features that this feature requires in children
    std::vector<int> requires_children;

    inline bool is_dynamic() { return type == f_type_dynamic; }
    inline bool is_static() { return type == f_type_static; }
    inline bool is_user() { return type == f_type_user; }
    /// Type of this feature, from the enum feature_type
    feature_type type;
  };

  inline bool is_dynamic(int id) { return features()[id]->type == f_type_dynamic; }
  inline bool is_static(int id) { return features()[id]->type == f_type_static; }
  inline bool is_user(int id) { return features()[id]->type == f_type_user; }

  // Accessor to array of all features with deps, static in most derived classes
  // Subclasses with dynamic dependency trees may override this
  // with a non-static array
  // Intermediate classes (colvarbias and colvarcomp, which are also base classes)
  // implement this as virtual to allow overriding
  virtual std::vector<feature *>&features() = 0;

  void add_child(colvardeps *child);

  void remove_child(colvardeps *child);

  /// Used before deleting an object, if not handled by that object's destructor
  /// (useful for cvcs because their children are member objects)
  void remove_all_children();

private:

  // pointers to objects this object depends on
  // list should be maintained by any code that modifies the object
  // this could be secured by making lists of colvars / cvcs / atom groups private and modified through accessor functions
  std::vector<colvardeps *> children;

  // pointers to objects that depend on this object
  // the size of this array is in effect a reference counter
  std::vector<colvardeps *> parents;

public:
  // disabling a feature f:
  // if parents depend on f, tell them to refresh / check that they are ok?
  // if children provide features to satisfy f ONLY, disable that

  // When the state of this object has changed, recursively tell parents
  // to enforce their dependencies
//   void refresh_parents() {
//
//   }

  // std::vector<colvardeps *> parents; // Needed to trigger a refresh if capabilities of this object change

  // End of members to be initialized by subclasses

  // Checks whether given feature is enabled
  // Defaults to querying f_*_active
  inline bool is_enabled(int f = f_cv_active) const {
    return feature_states[f].enabled;
  }

  // Checks whether given feature is available
  // Defaults to querying f_*_active
  inline bool is_available(int f = f_cv_active) const {
    return feature_states[f].available;
  }

  /// Set the feature's available flag, without checking
  /// To be used for dynamic properties
  /// dependencies will be checked by enable()
  void provide(int feature_id, bool truefalse = true);

  /// Set the feature's enabled flag, without dependency check or resolution
  /// To be used for static properties only
  /// Checking for availability is up to the caller
  void set_enabled(int feature_id, bool truefalse = true);

protected:



  /// Parse a keyword and enable a feature accordingly
  bool get_keyval_feature(colvarparse *cvp,
                          std::string const &conf, char const *key,
                          int feature_id, bool const &def_value,
                          colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal);

public:

  int enable(int f, bool dry_run = false, bool toplevel = true);  // enable a feature and recursively solve its dependencies
  // dry_run is set to true to recursively test if a feature is available, without enabling it
//     int disable(int f);


  /// This function is called whenever feature states are changed outside
  /// of the object's control, that is, by parents
  /// Eventually it may also be used when properties of children change
  virtual int refresh_deps() { return COLVARS_OK; }

  // NOTE that all feature enums should start with f_*_active
  enum features_biases {
    /// \brief Bias is active
    f_cvb_active,
    f_cvb_apply_force, // will apply forces
    f_cvb_get_total_force, // requires total forces
    f_cvb_history_dependent, // depends on simulation history
    f_cvb_scalar_variables, // requires scalar colvars
    f_cvb_calc_pmf, // whether this bias will compute a PMF
    f_cvb_ntot
  };

  enum features_colvar {
    /// \brief Calculate colvar
    f_cv_active,
    /// \brief Gradients are calculated and temporarily stored, so
    /// that external forces can be applied
    f_cv_gradient,
    /// \brief Collect atomic gradient data from all cvcs into vector
    /// atomic_gradient
    f_cv_collect_gradient,
    /// \brief Calculate the velocity with finite differences
    f_cv_fdiff_velocity,
    /// \brief The total force is calculated, projecting the atomic
    /// forces on the inverse gradient
    f_cv_total_force,
    /// \brief Calculate total force from atomic forces
    f_cv_total_force_calc,
    /// \brief Subtract the applied force from the total force
    f_cv_subtract_applied_force,
    /// \brief Estimate Jacobian derivative
    f_cv_Jacobian,
    /// \brief Do not report the Jacobian force as part of the total force
    /// instead, apply a correction internally to cancel it
    f_cv_hide_Jacobian,
    /// \brief The variable has a harmonic restraint around a moving
    /// center with fictitious mass; bias forces will be applied to
    /// the center
    f_cv_extended_Lagrangian,
    /// \brief The extended system coordinate undergoes Langevin dynamics
    f_cv_Langevin,
    /// \brief Output the potential and kinetic energies
    /// (for extended Lagrangian colvars only)
    f_cv_output_energy,
    /// \brief Output the value to the trajectory file (on by default)
    f_cv_output_value,
    /// \brief Output the velocity to the trajectory file
    f_cv_output_velocity,
    /// \brief Output the applied force to the trajectory file
    f_cv_output_applied_force,
    /// \brief Output the total force to the trajectory file
    f_cv_output_total_force,
    /// \brief A lower boundary is defined
    f_cv_lower_boundary,
    /// \brief An upper boundary is defined
    f_cv_upper_boundary,
    /// \brief Provide a discretization of the values of the colvar to
    /// be used by the biases or in analysis (needs lower and upper
    /// boundary)
    f_cv_grid,
    /// \brief Compute running average
    f_cv_runave,
    /// \brief Compute time correlation function
    f_cv_corrfunc,
    /// \brief Value and gradient computed by user script
    f_cv_scripted,
    /// \brief Colvar is periodic
    f_cv_periodic,
    /// \brief is scalar
    f_cv_scalar,
    f_cv_linear,
    f_cv_homogeneous,
    /// \brief Number of colvar features
    f_cv_ntot
  };

  enum features_cvc {
    f_cvc_active,
    f_cvc_scalar,
    f_cvc_gradient,
    f_cvc_inv_gradient,
    /// \brief If enabled, calc_gradients() will call debug_gradients() for every group needed
    f_cvc_debug_gradient,
    f_cvc_Jacobian,
    f_cvc_one_site_total_force,
    f_cvc_com_based,
    f_cvc_scalable,
    f_cvc_scalable_com,
    f_cvc_ntot
  };

  enum features_atomgroup {
    f_ag_active,
    f_ag_center,
    f_ag_rotate,
    f_ag_fitting_group,
    /// Perform a standard minimum msd fit for given atoms
    /// ie. not using refpositionsgroup
//     f_ag_min_msd_fit,
    f_ag_fit_gradient_group,// TODO check that these are sometimes needed separately
                            // maybe for minimum RMSD?
    f_ag_fit_gradient_ref,
    f_ag_atom_forces,
    f_ag_scalable,
    f_ag_scalable_com,
    f_ag_ntot
  };

  void init_cvb_requires();
  void init_cv_requires();
  void init_cvc_requires();
  void init_ag_requires();

  /// \brief print all enabled features and those of children, for debugging
  void print_state();
};

#endif


