#ifndef __JDIRECTION3D__
#define __JDIRECTION3D__

#include <istream>
#include <ostream>

#include "JIO/JSerialisable.hh"
#include "JLang/JManip.hh"
#include "JGeometry3D/JVersor3D.hh"
#include "JGeometry3D/JAngle3D.hh"
#include "JGeometry3D/JVector3D.hh"
#include "JGeometry3D/JVersor3Z.hh"
#include "JGeometry3D/JRotation3D.hh"
#include "JGeometry3D/JQuaternion3D.hh"


/**
 * \author mdejong
 */

namespace JGEOMETRY3D {}
namespace JPP { using namespace JGEOMETRY3D; }

namespace JGEOMETRY3D {

  using JIO::JReader;
  using JIO::JWriter;
  

  /**
   * Data structure for direction in three dimensions.
   */
  class JDirection3D :
    public JVersor3D 
  {
  public:

    using JVersor3D::getDot;

    /**
     * Default constructor.
     */
    JDirection3D() :
      JVersor3D()
    {}


    /**
     * Constructor.
     *
     * \param  dir           direction
     */
    JDirection3D(const JVersor3D& dir) :
      JVersor3D(dir)
    {}


    /**
     * Constructor.
     *
     * \param  angle         angle
     */
    JDirection3D(const JAngle3D& angle) :
      JVersor3D(angle.getDX(), angle.getDY(), angle.getDZ())
    {}


    /**
     * Constructor.
     *
     * \param  pos           position
     */
    JDirection3D(const JVector3D& pos) :
      JVersor3D(pos.getX(), pos.getY(), pos.getZ())
    {}


    /**
     * Constructor.
     *
     * \param  dir           direction
     */
    JDirection3D(const JVersor3Z& dir) :
      JVersor3D(dir.getDX(), dir.getDY(), dir.getDZ())
    {}


    /**
     * Constructor.
     *
     * \param  dx            dx value
     * \param  dy            dy value
     * \param  dz            dz value
     */
    JDirection3D(const double dx,
		 const double dy,
		 const double dz) :
      JVersor3D(dx, dy, dz)
    {}
    

    /**
     * Get direction.
     *
     * \return               direction
     */
    const JDirection3D& getDirection() const
    {
      return static_cast<const JDirection3D&>(*this);
    }


    /**
     * Get direction.
     *
     * \return               direction
     */
    JDirection3D& getDirection()
    {
      return static_cast<JDirection3D&>(*this);
    }


    /**
     * Set direction.
     *
     * \param  dir           direction
     */
    void setDirection(const JDirection3D& dir)
    {
      static_cast<JDirection3D&>(*this) = dir;
    }


    /**
     * Type conversion operator.
     *
     * \return               angle
     */
    operator JAngle3D() const
    {
      return JAngle3D(getDX(), getDY(), getDZ());
    }


    /**
     * Type conversion operator.
     *
     * \return               position
     */
    operator JVector3D() const
    {
      return JVector3D(getDX(), getDY(), getDZ());
    }


    /**
     * Transform.
     *
     * \param  T             matrix
     * \return               this direction
     */
    JDirection3D& transform(const JMatrix3D& T)
    {
      T.transform(__dx, __dy, __dz);

      normalise();

      return *this;
    }

    
    /**
     * Rotate.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate(const JRotation3D& R)
    {
      R.rotate(__dx, __dy, __dz);

      normalise();

      return *this;
    }

    
    /**
     * Rotate back.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate_back(const JRotation3D& R)
    {
      R.rotate_back(__dx, __dy, __dz);

      normalise();

      return *this;
    }


    /**
     * Rotate around X-axis.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate(const JRotation3X& R)
    {
      R.rotate(__dy, __dz);

      normalise();

      return *this;
    }

    
    /**
     * Rotate back around X-axis.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate_back(const JRotation3X& R)
    {
      R.rotate_back(__dy, __dz);

      normalise();

      return *this;
    }

    
    /**
     * Rotate around Y-axis.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate(const JRotation3Y& R)
    {
      R.rotate(__dx, __dz);

      normalise();

      return *this;
    }

    
    /**
     * Rotate back around Y-axis.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate_back(const JRotation3Y& R)
    {
      R.rotate_back(__dx, __dz);

      normalise();

      return *this;
    }

    
    /**
     * Rotate around Z-axis.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate(const JRotation3Z& R)
    {
      R.rotate(__dx, __dy);

      normalise();

      return *this;
    }

    
    /**
     * Rotate back around Z-axis.
     *
     * \param  R             rotation matrix
     * \return               this direction
     */
    JDirection3D& rotate_back(const JRotation3Z& R)
    {
      R.rotate_back(__dx, __dy);

      normalise();

      return *this;
    }

    
    /**
     * Rotate.
     *
     * \param  Q             quaternion
     * \return               this position
     */
    JDirection3D& rotate(const JQuaternion3D& Q)
    {
      Q.rotate(__dx, __dy, __dz);

      return *this;
    }


    /**
     * Rotate back.
     *
     * \param  Q             quaternion
     * \return               this position
     */
    JDirection3D& rotate_back(const JQuaternion3D& Q)
    {
      Q.rotate_back(__dx, __dy, __dz);

      return *this;
    }


    /**
     * Get dot product.
     *
     * \param  angle         angle
     * \return               dot product
     */
    double getDot(const JAngle3D& angle) const
    {
      return
	getDX() * angle.getDX() +
	getDY() * angle.getDY() +
	getDZ() * angle.getDZ();
    }


    /**
     * Get dot product.
     *
     * \param  pos           position
     * \return               dot product
     */
    double getDot(const JVector3D& pos) const
    {
      return
	getDX() * pos.getX() +
	getDY() * pos.getY() +
	getDZ() * pos.getZ();
    }


    /**
     * Get dot product.
     *
     * \param  dir           direction
     * \return               dot product
     */
    double getDot(const JVersor3Z& dir) const
    {
      return
	getDX() * dir.getDX() +
	getDY() * dir.getDY() +
	getDZ() * dir.getDZ();
    }
    

    /**
     * Read direction from input.
     *
     * \param  in            input stream
     * \param  direction     direction
     * \return               input stream
     */
    friend inline std::istream& operator>>(std::istream& in, JDirection3D& direction)
    {
      in >> direction.__dx >> direction.__dy >> direction.__dz;

      direction.normalise();

      return in;
    }


    /**
     * Write direction to output.
     *
     * \param  out           output stream
     * \param  direction     direction
     * \return               output stream
     */
    friend inline std::ostream& operator<<(std::ostream& out, const JDirection3D& direction)
    {
      const JFormat format(out, getFormat<JDirection3D>(JFormat_t(9, 6, std::ios::fixed | std::ios::showpos)));
      
      out << format << direction.getDX() << ' '
	  << format << direction.getDY() << ' '
	  << format << direction.getDZ();

      return out;
    }


    /**
     * Read direction from input.
     *
     * \param  in            reader
     * \param  direction     direction
     * \return               reader
     */
    friend inline JReader& operator>>(JReader& in, JDirection3D& direction)
    {
      return in >> direction.__dx >> direction.__dy >> direction.__dz;
    }


    /**
     * Write direction to output.
     *
     * \param  out           writer
     * \param  direction     direction
     * \return               writer
     */
    friend inline JWriter& operator<<(JWriter& out, const JDirection3D& direction)
    {
      return out << direction.__dx << direction.__dy << direction.__dz;
    }
  };
}

#endif