//
//  Copyright (C) 2004-2006 Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include <RDGeneral/export.h>
#ifndef __RD_ANGLEBEND_H__
#define __RD_ANGLEBEND_H__

#include <ForceField/Contrib.h>
#include <Geometry/point.h>

namespace ForceFields {
namespace UFF {
class AtomicParams;

//! The angle-bend term for the Universal Force Field
class RDKIT_FORCEFIELD_EXPORT AngleBendContrib : public ForceFieldContrib {
 public:
  AngleBendContrib() {}
  //! Constructor
  /*!
    The angle is between atom1 - atom2 - atom3

    \param owner       pointer to the owning ForceField
    \param idx1        index of atom1 in the ForceField's positions
    \param idx2        index of atom2 in the ForceField's positions
    \param idx3        index of atom3 in the ForceField's positions
    \param bondOrder12 order of the bond between atoms 1 and 2 (as a double)
    \param bondOrder23 order of the bond between atoms 2 and 3 (as a double)
    \param at1Params   pointer to the parameters for atom 1
    \param at2Params   pointer to the parameters for atom 2
    \param at3Params   pointer to the parameters for atom 3
    \param order (optional) the order of the angle term, this is for
       special cases and should adopt values:
         - 0: not a special case, use the \c theta0 value from \c at2Params
         - 1: linear coordination
         - 3: trigonal planar coordination
         - 4: square planar or octahedral coordination

  */
  AngleBendContrib(ForceField *owner, unsigned int idx1, unsigned int idx2,
                   unsigned int idx3, double bondOrder12, double bondOrder23,
                   const AtomicParams *at1Params, const AtomicParams *at2Params,
                   const AtomicParams *at3Params, unsigned int order = 0);
  double getEnergy(double *pos) const override;
  void getGrad(double *pos, double *grad) const override;

  AngleBendContrib *copy() const override {
    return new AngleBendContrib(*this);
  }

 private:
  int d_at1Idx{-1};
  int d_at2Idx{-1};
  int d_at3Idx{-1};
  unsigned int d_order{0};
  double d_forceConstant, d_C0, d_C1, d_C2, d_theta0;
  
  double getEnergyTerm(double cosTheta, double sinThetaSq) const;
  double getThetaDeriv(double cosTheta, double sinTheta) const;
};

namespace Utils {
//! Calculate the force constant for an angle bend
/*!
  The angle is between atom1 - atom2 - atom3

  \param theta0      the preferred value of the angle (in radians)
  \param bondOrder12 order of the bond between atoms 1 and 2 (as a double)
  \param bondOrder23 order of the bond between atoms 2 and 3 (as a double)
  \param at1Params   pointer to the parameters for atom 1
  \param at2Params   pointer to the parameters for atom 2
  \param at3Params   pointer to the parameters for atom 3

*/
RDKIT_FORCEFIELD_EXPORT double calcAngleForceConstant(
    double theta0, double bondOrder12, double bondOrder23,
    const AtomicParams *at1Params, const AtomicParams *at2Params,
    const AtomicParams *at3Params);
RDKIT_FORCEFIELD_EXPORT void calcAngleBendGrad(RDGeom::Point3D *r, double *dist,
                                               double **g, double &dE_dTheta,
                                               double &cosTheta,
                                               double &sinTheta);
}  // namespace Utils
}  // namespace UFF
}  // namespace ForceFields
#endif
