// -*- C++ -*-
// ---------------------------------------------------------------------------
//
// This file is a part of the CLHEP - a Class Library for High Energy Physics.
//
// History:
// 12.06.01 E.Chernyaev - CLHEP-1.7: initial  version
// 14.03.03 E.Chernyaev - CLHEP-1.9: template version
//

#ifndef BASIC_VECTOR3D_H
#define BASIC_VECTOR3D_H

#include <iosfwd>
#include "CLHEP/Vector/ThreeVector.h"

namespace HepGeom {
  /**
   * Base class for Point3D<T>, Vector3D<T> and Normal3D<T>.
   * It defines only common functionality for those classes and
   * should not be used as separate class.
   *
   * @author Evgeni Chernyaev <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  template<class T> class BasicVector3D {
  protected:
    T v_[3];

    /**
     * Default constructor.
     * It is protected - this class should not be instantiated directly.
     */
    BasicVector3D() { v_[0] = 0; v_[1] = 0; v_[2] = 0; }

  public:
    /**
     * Safe indexing of the coordinates when using with matrices, arrays, etc.
     */
    enum {
      X = 0,                 /**< index for x-component */
      Y = 1,                 /**< index for y-component */
      Z = 2,                 /**< index for z-component */
      NUM_COORDINATES = 3,   /**< number of components  */
      SIZE = NUM_COORDINATES /**< number of components  */
    };

    /**
     * Constructor from three numbers. */
    BasicVector3D(T x1, T y1, T z1) { v_[0] = x1; v_[1] = y1; v_[2] = z1; }

    /**
     * Copy constructor.
     * Note: BasicVector3D<double> has constructors
     * from BasicVector3D<double> (provided by compiler) and
     * from BasicVector3D<float> (defined in this file);
     * BasicVector3D<float> has only the last one.
     */
    BasicVector3D(const BasicVector3D<float> & v) {
      v_[0] = v.x(); v_[1] = v.y(); v_[2] = v.z();
    }

    /**
     * Destructor. */
    virtual ~BasicVector3D() {}

    // -------------------------
    // Interface to "good old C"
    // -------------------------

    /**
     * Conversion (cast) to ordinary array. */
    operator T * () { return v_; }

    /**
     * Conversion (cast) to ordinary const array. */
    operator const T * () const { return v_; }

    /**
     * Conversion (cast) to CLHEP::Hep3Vector.
     * This operator is needed only for backward compatibility and
     * in principle should not exit.
     */ 
    operator CLHEP::Hep3Vector () const { return CLHEP::Hep3Vector(x(),y(),z()); }

    // -----------------------------
    // General arithmetic operations
    // -----------------------------

    /**
     * Assignment. */
    BasicVector3D<T> & operator= (const BasicVector3D<T> & v) {
      v_[0] = v.v_[0]; v_[1] = v.v_[1]; v_[2] = v.v_[2]; return *this;
    }
    /**
     * Addition. */
    BasicVector3D<T> & operator+=(const BasicVector3D<T> & v) {
      v_[0] += v.v_[0]; v_[1] += v.v_[1]; v_[2] += v.v_[2]; return *this;
    }
    /**
     * Subtraction. */
    BasicVector3D<T> & operator-=(const BasicVector3D<T> & v) {
      v_[0] -= v.v_[0]; v_[1] -= v.v_[1]; v_[2] -= v.v_[2]; return *this;
    }
    /**
     * Multiplication by scalar. */
    BasicVector3D<T> & operator*=(double a) {
      v_[0] *= a; v_[1] *= a; v_[2] *= a; return *this;
    }
    /**
     * Division by scalar. */
    BasicVector3D<T> & operator/=(double a) {
      v_[0] /= a; v_[1] /= a; v_[2] /= a; return *this;
    }

    // ------------
    // Subscripting
    // ------------

    /**
     * Gets components by index. */
    T operator()(int i) const { return v_[i]; }
    /**
     * Gets components by index. */
    T operator[](int i) const { return v_[i]; }
    
    /**
     * Sets components by index. */
    T & operator()(int i) { return v_[i]; }
    /**
     * Sets components by index. */
    T & operator[](int i) { return v_[i]; }
    
    // ------------------------------------
    // Cartesian coordinate system: x, y, z
    // ------------------------------------

    /**
     * Gets x-component in cartesian coordinate system. */ 
    T x() const { return v_[0]; }
    /**
     * Gets y-component in cartesian coordinate system. */ 
    T y() const { return v_[1]; }
    /**
     * Gets z-component in cartesian coordinate system. */ 
    T z() const { return v_[2]; }

    /**
     * Sets x-component in cartesian coordinate system. */ 
    void setX(T a) { v_[0] = a; }
    /**
     * Sets y-component in cartesian coordinate system. */ 
    void setY(T a) { v_[1] = a; }
    /**
     * Sets z-component in cartesian coordinate system. */ 
    void setZ(T a) { v_[2] = a; }

    /**
     * Sets components in cartesian coordinate system.  */
    void set(T x1, T y1, T z1) { v_[0] = x1; v_[1] = y1; v_[2] = z1; }

    // ------------------------------------------
    // Cylindrical coordinate system: rho, phi, z
    // ------------------------------------------

    /**
     * Gets transverse component squared. */
    T perp2() const { return x()*x()+y()*y(); }
    /**
     * Gets transverse component. */
    T perp() const { return std::sqrt(perp2()); }
    /**
     * Gets rho-component in cylindrical coordinate system */
    T rho() const { return perp(); }

    /**
     * Sets transverse component keeping phi and z constant. */
    void setPerp(T rh) {
      T factor = perp();
      if (factor > 0) {
	factor = rh/factor; v_[0] *= factor; v_[1] *= factor;
      } 
    }

    // ------------------------------------------
    // Spherical coordinate system: r, phi, theta
    // ------------------------------------------

    /**
     * Gets magnitude squared of the vector. */
    T mag2() const { return x()*x()+y()*y()+z()*z(); }
    /**
     * Gets magnitude of the vector. */
    T mag() const { return std::sqrt(mag2()); }
    /**
     * Gets r-component in spherical coordinate system */
    T r() const { return mag(); }
    /**
     * Gets azimuth angle. */
    T phi() const {
      return x() == 0 && y() == 0 ? 0 : std::atan2(y(),x());
    }
    /**
     * Gets polar angle. */
    T theta() const {
      return x() == 0 && y() == 0 && z() == 0 ? 0 : std::atan2(perp(),z());
    }
    /**
     * Gets cosine of polar angle. */
    T cosTheta() const { T ma = mag(); return ma == 0 ? 1 : z()/ma; }

    /**
     * Gets r-component in spherical coordinate system */
    T getR() const { return r(); }
    /** 
     * Gets phi-component in spherical coordinate system */
    T getPhi() const { return phi(); }
    /** 
     * Gets theta-component in spherical coordinate system */
    T getTheta() const { return theta(); }

    /**
     * Sets magnitude. */
    void setMag(T ma) {
      T factor = mag();
      if (factor > 0) {
	factor = ma/factor; v_[0] *= factor; v_[1] *= factor; v_[2] *= factor;
      } 
    }
    /**
     * Sets r-component in spherical coordinate system. */
    void setR(T ma) { setMag(ma); }
    /**
     * Sets phi-component in spherical coordinate system. */
    void setPhi(T ph) { T xy = perp(); setX(xy*std::cos(ph)); setY(xy*std::sin(ph)); }
    /**
     * Sets theta-component in spherical coordinate system. */
    void setTheta(T th) {
      T ma = mag();
      T ph = phi();
      set(ma*std::sin(th)*std::cos(ph), ma*std::sin(th)*std::sin(ph), ma*std::cos(th));
    }

    // ---------------
    // Pseudo rapidity
    // ---------------

    /**
     * Gets pseudo-rapidity: -ln(tan(theta/2)) */
    T pseudoRapidity() const;
    /**
     * Gets pseudo-rapidity. */
    T eta() const { return pseudoRapidity(); }
    /**
     * Gets pseudo-rapidity. */
    T getEta() const { return pseudoRapidity(); }

    /**
     * Sets pseudo-rapidity, keeping magnitude and phi fixed. */
    void setEta(T a);

    // -------------------
    // Combine two vectors
    // -------------------

    /**
     * Scalar product. */
    T dot(const BasicVector3D<T> & v) const {
      return x()*v.x()+y()*v.y()+z()*v.z();
    }

    /**
     * Vector product. */
    BasicVector3D<T> cross(const BasicVector3D<T> & v) const {
      return BasicVector3D<T>(y()*v.z()-v.y()*z(),
			      z()*v.x()-v.z()*x(),
			      x()*v.y()-v.x()*y());
    }

    /**
     * Returns transverse component w.r.t. given axis squared. */
    T perp2(const BasicVector3D<T> & v) const {
      T tot = v.mag2(), s = dot(v);
      return tot > 0 ? mag2()-s*s/tot : mag2();
    }

    /**
     * Returns transverse component w.r.t. given axis. */
    T perp(const BasicVector3D<T> & v) const {
      return std::sqrt(perp2(v));
    }

    /**
     * Returns angle w.r.t. another vector. */
    T angle(const BasicVector3D<T> & v) const;

    // ---------------
    // Related vectors
    // ---------------

    /**
     * Returns unit vector parallel to this. */ 
    BasicVector3D<T> unit() const {
      T len = mag();
      return (len > 0) ?
	BasicVector3D<T>(x()/len, y()/len, z()/len) : BasicVector3D<T>();
    }

    /**
     * Returns orthogonal vector. */ 
    BasicVector3D<T> orthogonal() const {
      T dx = x() < 0 ? -x() : x();
      T dy = y() < 0 ? -y() : y();
      T dz = z() < 0 ? -z() : z();
      if (dx < dy) {
	return dx < dz ?
	  BasicVector3D<T>(0,z(),-y()) : BasicVector3D<T>(y(),-x(),0);
      }else{
	return dy < dz ?
	  BasicVector3D<T>(-z(),0,x()) : BasicVector3D<T>(y(),-x(),0);
      }
    }

    // ---------
    // Rotations
    // ---------

    /**
     * Rotates around x-axis. */
    BasicVector3D<T> & rotateX(T a);
    /**
     * Rotates around y-axis. */
    BasicVector3D<T> & rotateY(T a);
    /**
     * Rotates around z-axis. */
    BasicVector3D<T> & rotateZ(T a);
    /**
     * Rotates around the axis specified by another vector. */
    BasicVector3D<T> & rotate(T a, const BasicVector3D<T> & v);
  };

  /*************************************************************************
   *                                                                       *  
   * Non-member functions for BasicVector3D<float>                         *
   *                                                                       *  
   *************************************************************************/

  /**
   * Output to stream.
   * @relates BasicVector3D
   */
  std::ostream &
  operator<<(std::ostream &, const BasicVector3D<float> &);

  /**
   * Input from stream.
   * @relates BasicVector3D
   */
  std::istream &
  operator>>(std::istream &, BasicVector3D<float> &);

  /**
   * Unary plus.
   * @relates BasicVector3D
   */
  inline BasicVector3D<float>
  operator+(const BasicVector3D<float> & v) { return v; }

  /**
   * Addition of two vectors.
   * @relates BasicVector3D
   */
  inline BasicVector3D<float>
  operator+(const BasicVector3D<float> & a, const BasicVector3D<float> & b) {
    return BasicVector3D<float>(a.x()+b.x(), a.y()+b.y(), a.z()+b.z());
  }

  /**
   * Unary minus.
   * @relates BasicVector3D
   */
  inline BasicVector3D<float>
  operator-(const BasicVector3D<float> & v) {
    return BasicVector3D<float>(-v.x(), -v.y(), -v.z());
  }

  /**
   * Subtraction of two vectors.
   * @relates BasicVector3D
   */
  inline BasicVector3D<float>
  operator-(const BasicVector3D<float> & a, const BasicVector3D<float> & b) {
    return BasicVector3D<float>(a.x()-b.x(), a.y()-b.y(), a.z()-b.z());
  }

  /**
   * Multiplication vector by scalar.
   * @relates BasicVector3D
   */
  inline BasicVector3D<float>
  operator*(const BasicVector3D<float> & v, double a) {
    return BasicVector3D<float>(v.x()*static_cast<float>(a), v.y()*static_cast<float>(a), v.z()*static_cast<float>(a));
  }

  /**
   * Scalar product of two vectors.
   * @relates BasicVector3D
   */
  inline float
  operator*(const BasicVector3D<float> & a, const BasicVector3D<float> & b) {
    return a.dot(b);
  }

  /**
   * Multiplication scalar by vector.
   * @relates BasicVector3D
   */
  inline BasicVector3D<float>
  operator*(double a, const BasicVector3D<float> & v) {
    return BasicVector3D<float>(static_cast<float>(a)*v.x(), static_cast<float>(a)*v.y(), static_cast<float>(a)*v.z());
  }

  /**
   * Division vector by scalar.
   * @relates BasicVector3D
   */
  inline BasicVector3D<float>
  operator/(const BasicVector3D<float> & v, double a) {
    return BasicVector3D<float>(v.x()/static_cast<float>(a), v.y()/static_cast<float>(a), v.z()/static_cast<float>(a));
  }
  
  /**
   * Comparison of two vectors for equality. 
   * @relates BasicVector3D
   */
  inline bool
  operator==(const BasicVector3D<float> & a, const BasicVector3D<float> & b) {
    return (a.x()==b.x() && a.y()==b.y() && a.z()==b.z());
  }

  /**
   * Comparison of two vectors for inequality. 
   * @relates BasicVector3D
   */
  inline bool
  operator!=(const BasicVector3D<float> & a, const BasicVector3D<float> & b) {
    return (a.x()!=b.x() || a.y()!=b.y() || a.z()!=b.z());
  }

  /*************************************************************************
   *                                                                       *  
   * Non-member functions for BasicVector3D<double>                        *
   *                                                                       *  
   *************************************************************************/

  /**
   * Output to stream.
   * @relates BasicVector3D
   */
  std::ostream &
  operator<<(std::ostream &, const BasicVector3D<double> &);

  /**
   * Input from stream.
   * @relates BasicVector3D
   */
  std::istream &
  operator>>(std::istream &, BasicVector3D<double> &);

  /**
   * Unary plus.
   * @relates BasicVector3D
   */
  inline BasicVector3D<double>
  operator+(const BasicVector3D<double> & v) { return v; }

  /**
   * Addition of two vectors.
   * @relates BasicVector3D
   */
  inline BasicVector3D<double>
  operator+(const BasicVector3D<double> & a,const BasicVector3D<double> & b) {
    return BasicVector3D<double>(a.x()+b.x(), a.y()+b.y(), a.z()+b.z());
  }

  /**
   * Unary minus.
   * @relates BasicVector3D
   */
  inline BasicVector3D<double>
  operator-(const BasicVector3D<double> & v) {
    return BasicVector3D<double>(-v.x(), -v.y(), -v.z());
  }

  /**
   * Subtraction of two vectors.
   * @relates BasicVector3D
   */
  inline BasicVector3D<double>
  operator-(const BasicVector3D<double> & a,const BasicVector3D<double> & b) {
    return BasicVector3D<double>(a.x()-b.x(), a.y()-b.y(), a.z()-b.z());
  }

  /**
   * Multiplication vector by scalar.
   * @relates BasicVector3D
   */
  inline BasicVector3D<double>
  operator*(const BasicVector3D<double> & v, double a) {
    return BasicVector3D<double>(v.x()*a, v.y()*a, v.z()*a);
  }

  /**
   * Scalar product of two vectors.
   * @relates BasicVector3D
   */
  inline double
  operator*(const BasicVector3D<double> & a,const BasicVector3D<double> & b) {
    return a.dot(b);
  }

  /**
   * Multiplication scalar by vector.
   * @relates BasicVector3D
   */
  inline BasicVector3D<double>
  operator*(double a, const BasicVector3D<double> & v) {
    return BasicVector3D<double>(a*v.x(), a*v.y(), a*v.z());
  }

  /**
   * Division vector by scalar.
   * @relates BasicVector3D
   */
  inline BasicVector3D<double>
  operator/(const BasicVector3D<double> & v, double a) {
    return BasicVector3D<double>(v.x()/a, v.y()/a, v.z()/a);
  }
  
  /**
   * Comparison of two vectors for equality. 
   * @relates BasicVector3D
   */
  inline bool
  operator==(const BasicVector3D<double> & a, const BasicVector3D<double> & b)
  {
    return (a.x()==b.x() && a.y()==b.y() && a.z()==b.z());
  }

  /**
   * Comparison of two vectors for inequality. 
   * @relates BasicVector3D
   */
  inline bool
  operator!=(const BasicVector3D<double> & a, const BasicVector3D<double> & b)
  {
    return (a.x()!=b.x() || a.y()!=b.y() || a.z()!=b.z());
  }
} /* namespace HepGeom */

#endif /* BASIC_VECTOR3D_H */
