#ifndef QGLVIEWER_FRAME_H
#define QGLVIEWER_FRAME_H

#include <QObject>
#include <QString>

#include "constraint.h"
// #include "GL/gl.h" is now included in config.h for ease of configuration

namespace qglviewer {
/*! \brief The Frame class represents a coordinate system, defined by a position
  and an orientation. \class Frame frame.h QGLViewer/frame.h

  A Frame is a 3D coordinate system, represented by a position() and an
  orientation(). The order of these transformations is important: the Frame is
  first translated \e and \e then rotated around the new translated origin.

  A Frame is useful to define the position and orientation of a 3D rigid object,
  using its matrix() method, as shown below: \code
  // Builds a Frame at position (0.5,0,0) and oriented such that its Y axis is
  along the (1,1,1)
  // direction. One could also have used setPosition() and setOrientation().
  Frame fr(Vec(0.5,0,0), Quaternion(Vec(0,1,0), Vec(1,1,1)));
  glPushMatrix();
  glMultMatrixd(fr.matrix());
  // Draw your object here, in the local fr coordinate system.
  glPopMatrix();
  \endcode

  Many functions are provided to transform a 3D point from one coordinate system
  (Frame) to an other: see coordinatesOf(), inverseCoordinatesOf(),
  coordinatesOfIn(), coordinatesOfFrom()...

  You may also want to transform a 3D vector (such as a normal), which
  corresponds to applying only the rotational part of the frame transformation:
  see transformOf() and inverseTransformOf(). See the <a
  href="../examples/frameTransform.html">frameTransform example</a> for an
  illustration.

  The translation() and the rotation() that are encapsulated in a Frame can also
  be used to represent a \e rigid \e transformation of space. Such a
  transformation can also be interpreted as a change of coordinate system, and
  the coordinate system conversion functions actually allow you to use a Frame
  as a rigid transformation. Use inverseCoordinatesOf() (resp. coordinatesOf())
  to apply the transformation (resp. its inverse). Note the inversion.

  <h3>Hierarchy of Frames</h3>

  The position and the orientation of a Frame are actually defined with respect
  to a referenceFrame(). The default referenceFrame() is the world coordinate
  system (represented by a \c nullptr referenceFrame()). If you setReferenceFrame()
  to a different Frame, you must then differentiate:

  \arg the \e local translation() and rotation(), defined with respect to the
  referenceFrame(),

  \arg the \e global position() and orientation(), always defined with respect
  to the world coordinate system.

  A Frame is actually defined by its translation() with respect to its
  referenceFrame(), and then by a rotation() of the coordinate system around the
  new translated origin.

  This terminology for \e local (translation() and rotation()) and \e global
  (position() and orientation()) definitions is used in all the methods' names
  and should be sufficient to prevent ambiguities. These notions are obviously
  identical when the referenceFrame() is \c nullptr, i.e. when the Frame is defined
  in the world coordinate system (the one you are in at the beginning of the
  QGLViewer::draw() method, see the <a href="../introduction.html">introduction
  page</a>).

  Frames can hence easily be organized in a tree hierarchy, which root is the
  world coordinate system. A loop in the hierarchy would result in an
  inconsistent (multiple) Frame definition.
  settingAsReferenceFrameWillCreateALoop() checks this and prevents
  setReferenceFrame() from creating such a loop.

  This frame hierarchy is used in methods like coordinatesOfIn(),
  coordinatesOfFrom()... which allow coordinates (or vector) conversions from a
  Frame to any other one (including the world coordinate system).

  However, one must note that this hierarchical representation is internal to
  the Frame classes. When the Frames represent OpenGL coordinates system, one
  should map this hierarchical representation to the OpenGL GL_MODELVIEW matrix
  stack. See the matrix() documentation for details.

  <h3>Constraints</h3>

  An interesting feature of Frames is that their displacements can be
  constrained. When a Constraint is attached to a Frame, it filters the input of
  translate() and rotate(), and only the resulting filtered motion is applied to
  the Frame. The default constraint() is \c nullptr resulting in no filtering. Use
  setConstraint() to attach a Constraint to a frame.

  Constraints are especially usefull for the ManipulatedFrame instances, in
  order to forbid some mouse motions. See the <a
  href="../examples/constrainedFrame.html">constrainedFrame</a>, <a
  href="../examples/constrainedCamera.html">constrainedCamera</a> and <a
  href="../examples/luxo.html">luxo</a> examples for an illustration.

  Classical constraints are provided for convenience (see LocalConstraint,
  WorldConstraint and CameraConstraint) and new constraints can very easily be
  implemented.

  <h3>Derived classes</h3>

  The ManipulatedFrame class inherits Frame and implements a mouse motion
  convertion, so that a Frame (and hence an object) can be manipulated in the
  scene with the mouse.

  \nosubgrouping */
class QGLVIEWER_EXPORT Frame : public QObject {
  Q_OBJECT

public:
  Frame();

  /*! Virtual destructor. Empty. */
  virtual ~Frame() {}

  Frame(const Frame &frame);
  Frame &operator=(const Frame &frame);

Q_SIGNALS:
  /*! This signal is emitted whenever the position() or the orientation() of the
  Frame is modified.

  Connect this signal to any object that must be notified:
  \code
  QObject::connect(myFrame, SIGNAL(modified()), myObject, SLOT(update()));
  \endcode
  Use the QGLViewer::QGLViewerPool() to connect the signal to all the viewers.

  \note If your Frame is part of a Frame hierarchy (see referenceFrame()), a
  modification of one of the parents of this Frame will \e not emit this signal.
  Use code like this to change this behavior (you can do this recursively for
  all the referenceFrame() until the \c nullptr world root frame is encountered):
  \code
  // Emits the Frame modified() signal when its referenceFrame() is modified().
  connect(myFrame->referenceFrame(), SIGNAL(modified()), myFrame,
  SIGNAL(modified())); \endcode

  \attention Connecting this signal to a QOpenGLWidget::update() slot (or a
  method that calls it) will prevent you from modifying the Frame \e inside your
  QGLViewer::draw() method as it would result in an infinite loop. However,
  QGLViewer::draw() should not modify the scene.

  \note Note that this signal might be emitted even if the Frame is not actually
  modified, for instance after a translate(Vec(0,0,0)) or a
  setPosition(position()). */
  void modified();

  /*! This signal is emitted when the Frame is interpolated by a
  KeyFrameInterpolator.

  See the KeyFrameInterpolator documentation for details.

  If a KeyFrameInterpolator is used to successively interpolate several Frames
  in your scene, connect the KeyFrameInterpolator::interpolated() signal instead
  (identical, but independent of the interpolated Frame). */
  void interpolated();

public:
  /*! @name World coordinates position and orientation */
  //@{
  Frame(const Vec &position, const Quaternion &orientation);

  void setPosition(const Vec &position);
  void setPosition(qreal x, qreal y, qreal z);
  void setPositionWithConstraint(Vec &position);

  void setOrientation(const Quaternion &orientation);
  void setOrientation(qreal q0, qreal q1, qreal q2, qreal q3);
  void setOrientationWithConstraint(Quaternion &orientation);

  void setPositionAndOrientation(const Vec &position,
                                 const Quaternion &orientation);
  void setPositionAndOrientationWithConstraint(Vec &position,
                                               Quaternion &orientation);

  Vec position() const;
  Quaternion orientation() const;

  void getPosition(qreal &x, qreal &y, qreal &z) const;
  void getOrientation(qreal &q0, qreal &q1, qreal &q2, qreal &q3) const;
  //@}

public:
  /*! @name Local translation and rotation w/r reference Frame */
  //@{
  /*! Sets the translation() of the frame, locally defined with respect to the
  referenceFrame(). Emits the modified() signal.

  Use setPosition() to define the world coordinates position(). Use
  setTranslationWithConstraint() to take into account the potential constraint()
  of the Frame. */
  void setTranslation(const Vec &translation) {
    t_ = translation;
    Q_EMIT modified();
  }
  void setTranslation(qreal x, qreal y, qreal z);
  void setTranslationWithConstraint(Vec &translation);

  /*! Set the current rotation Quaternion. See rotation() and the different
  Quaternion constructors. Emits the modified() signal. See also
  setTranslation() and setRotationWithConstraint(). */

  /*! Sets the rotation() of the Frame, locally defined with respect to the
   referenceFrame(). Emits the modified() signal.

   Use setOrientation() to define the world coordinates orientation(). The
   potential constraint() of the Frame is not taken into account, use
   setRotationWithConstraint() instead. */
  void setRotation(const Quaternion &rotation) {
    q_ = rotation;
    Q_EMIT modified();
  }
  void setRotation(qreal q0, qreal q1, qreal q2, qreal q3);
  void setRotationWithConstraint(Quaternion &rotation);

  void setTranslationAndRotation(const Vec &translation,
                                 const Quaternion &rotation);
  void setTranslationAndRotationWithConstraint(Vec &translation,
                                               Quaternion &rotation);

  /*! Returns the Frame translation, defined with respect to the
  referenceFrame().

  Use position() to get the result in the world coordinates. These two values
  are identical when the referenceFrame() is \c nullptr (default).

  See also setTranslation() and setTranslationWithConstraint(). */
  Vec translation() const { return t_; }
  /*! Returns the Frame rotation, defined with respect to the referenceFrame().

  Use orientation() to get the result in the world coordinates. These two values
  are identical when the referenceFrame() is \c nullptr (default).

  See also setRotation() and setRotationWithConstraint(). */

  /*! Returns the current Quaternion orientation. See setRotation(). */
  Quaternion rotation() const { return q_; }

  void getTranslation(qreal &x, qreal &y, qreal &z) const;
  void getRotation(qreal &q0, qreal &q1, qreal &q2, qreal &q3) const;
  //@}

public:
  /*! @name Frame hierarchy */
  //@{
  /*! Returns the reference Frame, in which coordinates system the Frame is
  defined.

  The translation() and rotation() of the Frame are defined with respect to the
  referenceFrame() coordinate system. A \c nullptr referenceFrame() (default value)
  means that the Frame is defined in the world coordinate system.

  Use position() and orientation() to recursively convert values along the
  referenceFrame() chain and to get values expressed in the world coordinate
  system. The values match when the referenceFrame() is \c nullptr.

  Use setReferenceFrame() to set this value and create a Frame hierarchy.
  Convenient functions allow you to convert 3D coordinates from one Frame to an
  other: see coordinatesOf(), localCoordinatesOf(), coordinatesOfIn() and their
  inverse functions.

  Vectors can also be converted using transformOf(), transformOfIn,
  localTransformOf() and their inverse functions. */
  const Frame *referenceFrame() const { return referenceFrame_; }
  void setReferenceFrame(const Frame *const refFrame);
  bool settingAsReferenceFrameWillCreateALoop(const Frame *const frame);
  //@}

  /*! @name Frame modification */
  //@{
  void translate(Vec &t);
  void translate(const Vec &t);
  // Some compilers complain about "overloading cannot distinguish from previous
  // declaration" Simply comment out the following method and its associated
  // implementation
  void translate(qreal x, qreal y, qreal z);
  void translate(qreal &x, qreal &y, qreal &z);

  void rotate(Quaternion &q);
  void rotate(const Quaternion &q);
  // Some compilers complain about "overloading cannot distinguish from previous
  // declaration" Simply comment out the following method and its associated
  // implementation
  void rotate(qreal q0, qreal q1, qreal q2, qreal q3);
  void rotate(qreal &q0, qreal &q1, qreal &q2, qreal &q3);

  void rotateAroundPoint(Quaternion &rotation, const Vec &point);
  void rotateAroundPoint(const Quaternion &rotation, const Vec &point);

  void alignWithFrame(const Frame *const frame, bool move = false,
                      qreal threshold = 0.0);
  void projectOnLine(const Vec &origin, const Vec &direction);
  //@}

  /*! @name Coordinate system transformation of 3D coordinates */
  //@{
  Vec coordinatesOf(const Vec &src) const;
  Vec inverseCoordinatesOf(const Vec &src) const;
  Vec localCoordinatesOf(const Vec &src) const;
  Vec localInverseCoordinatesOf(const Vec &src) const;
  Vec coordinatesOfIn(const Vec &src, const Frame *const in) const;
  Vec coordinatesOfFrom(const Vec &src, const Frame *const from) const;

  void getCoordinatesOf(const qreal src[3], qreal res[3]) const;
  void getInverseCoordinatesOf(const qreal src[3], qreal res[3]) const;
  void getLocalCoordinatesOf(const qreal src[3], qreal res[3]) const;
  void getLocalInverseCoordinatesOf(const qreal src[3], qreal res[3]) const;
  void getCoordinatesOfIn(const qreal src[3], qreal res[3],
                          const Frame *const in) const;
  void getCoordinatesOfFrom(const qreal src[3], qreal res[3],
                            const Frame *const from) const;
  //@}

  /*! @name Coordinate system transformation of vectors */
  // A frame is as a new coordinate system, defined with respect to a reference
  // frame (the world coordinate system by default, see the "Composition of
  // frame" section).

  // The transformOf() (resp. inverseTransformOf()) functions transform a 3D
  // vector from (resp. to) the world coordinates system. This section defines
  // the 3D vector transformation functions. See the Coordinate system
  // transformation of 3D points above for the transformation of 3D points. The
  // difference between the two sets of functions is simple: for vectors, only
  // the rotational part of the transformations is taken into account, while
  // translation is also considered for 3D points.

  // The length of the resulting transformed vector is identical to the one of
  // the source vector for all the described functions.

  // When local is prepended to the names of the functions, the functions simply
  // transform from (and to) the reference frame.

  // When In (resp. From) is appended to the names, the functions transform from
  // (resp. To) the frame that is given as an argument. The frame does not need
  // to be in the same branch or the hierarchical tree, and can be \c nullptr (the
  // world coordinates system).

  // Combining any of these functions with its inverse (in any order) leads to
  // the identity.
  //@{
  Vec transformOf(const Vec &src) const;
  Vec inverseTransformOf(const Vec &src) const;
  Vec localTransformOf(const Vec &src) const;
  Vec localInverseTransformOf(const Vec &src) const;
  Vec transformOfIn(const Vec &src, const Frame *const in) const;
  Vec transformOfFrom(const Vec &src, const Frame *const from) const;

  void getTransformOf(const qreal src[3], qreal res[3]) const;
  void getInverseTransformOf(const qreal src[3], qreal res[3]) const;
  void getLocalTransformOf(const qreal src[3], qreal res[3]) const;
  void getLocalInverseTransformOf(const qreal src[3], qreal res[3]) const;
  void getTransformOfIn(const qreal src[3], qreal res[3],
                        const Frame *const in) const;
  void getTransformOfFrom(const qreal src[3], qreal res[3],
                          const Frame *const from) const;
  //@}

  /*! @name Constraint on the displacement */
  //@{
  /*! Returns the current constraint applied to the Frame.

  A \c nullptr value (default) means that no Constraint is used to filter Frame
  translation and rotation. See the Constraint class documentation for details.

  You may have to use a \c dynamic_cast to convert the result to a Constraint
  derived class. */
  Constraint *constraint() const { return constraint_; }
  /*! Sets the constraint() attached to the Frame.

  A \c nullptr value means no constraint. The previous constraint() should be
  deleted by the calling method if needed. */
  void setConstraint(Constraint *const constraint) { constraint_ = constraint; }
  //@}

  /*! @name Associated matrices */
  //@{
public:
  const GLdouble *matrix() const;
  void getMatrix(GLdouble m[4][4]) const;
  void getMatrix(GLdouble m[16]) const;

  const GLdouble *worldMatrix() const;
  void getWorldMatrix(GLdouble m[4][4]) const;
  void getWorldMatrix(GLdouble m[16]) const;

  void setFromMatrix(const GLdouble m[4][4]);
  void setFromMatrix(const GLdouble m[16]);
  //@}

  /*! @name Inversion of the transformation */
  //@{
  Frame inverse() const;
  /*! Returns the inverse() of the Frame world transformation.

  The orientation() of the new Frame is the Quaternion::inverse() of the
  original orientation. Its position() is the negated and inverse rotated image
  of the original position.

  The result Frame has a \c nullptr referenceFrame() and a \c nullptr constraint().

  Use inverse() for a local (i.e. with respect to referenceFrame())
  transformation inverse. */
  Frame worldInverse() const {
    return Frame(-(orientation().inverseRotate(position())),
                 orientation().inverse());
  }
  //@}

  /*! @name XML representation */
  //@{
public:
  virtual QDomElement domElement(const QString &name,
                                 QDomDocument &document) const;
public Q_SLOTS:
  virtual void initFromDOMElement(const QDomElement &element);
  //@}

private:
  // P o s i t i o n   a n d   o r i e n t a t i o n
  Vec t_;
  Quaternion q_;

  // C o n s t r a i n t s
  Constraint *constraint_;

  // F r a m e   c o m p o s i t i o n
  const Frame *referenceFrame_;
};

} // namespace qglviewer

#endif // QGLVIEWER_FRAME_H
