// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_vec4
#define tools_vec4

#include <cstddef> //size_t

namespace tools {

template <class T>
class vec4 {
protected:
  static T zero() {return T();}
  static T minus_one() {return T(-1);}
public:
  typedef T elem_t;
  unsigned int dimension() const {return 4;}
public:
  vec4(){
    m_data[0] = T();
    m_data[1] = T();
    m_data[2] = T();
    m_data[3] = T();
  }
  vec4(const T a_vec[4]) {
    m_data[0] = a_vec[0];
    m_data[1] = a_vec[1];
    m_data[2] = a_vec[2];
    m_data[3] = a_vec[3];
  }
  vec4(const T& a0,const T& a1,const T& a2,const T& a3) {
    m_data[0] = a0;
    m_data[1] = a1;
    m_data[2] = a2;
    m_data[3] = a3;
  }
  virtual ~vec4() {
  }
public:
  vec4(const vec4& a_from){
    m_data[0] = a_from.m_data[0];
    m_data[1] = a_from.m_data[1];
    m_data[2] = a_from.m_data[2];
    m_data[3] = a_from.m_data[3];
  }
  vec4& operator=(const vec4& a_from) {
    m_data[0] = a_from.m_data[0];
    m_data[1] = a_from.m_data[1];
    m_data[2] = a_from.m_data[2];
    m_data[3] = a_from.m_data[3];
    return *this;
  }
public:
  const T& v0() const { return m_data[0];}
  const T& v1() const { return m_data[1];}
  const T& v2() const { return m_data[2];}
  const T& v3() const { return m_data[3];}

  void v0(const T& a_value) { m_data[0] = a_value;}
  void v1(const T& a_value) { m_data[1] = a_value;}
  void v2(const T& a_value) { m_data[2] = a_value;}
  void v3(const T& a_value) { m_data[3] = a_value;}

  const T& x() const { return m_data[0];}
  const T& y() const { return m_data[1];}
  const T& z() const { return m_data[2];}
  const T& t() const { return m_data[3];}

  void set_value(const T& a0,const T& a1,const T& a2,const T& a3) {
    m_data[0] = a0;
    m_data[1] = a1;
    m_data[2] = a2;
    m_data[3] = a3;
  }
  void set_value(const T aV[4]) {
    m_data[0] = aV[0];
    m_data[1] = aV[1];
    m_data[2] = aV[2];
    m_data[3] = aV[3];
  }
  void value(T& a0,T& a1,T& a2,T& a3) const {
    a0 = m_data[0];
    a1 = m_data[1];
    a2 = m_data[2];
    a3 = m_data[3];
  }

  bool set_value(unsigned int a_index,const T& a_value) {
    if(a_index>=4) return false;
    m_data[a_index] = a_value;
    return true;
  }

  T length(T(*a_sqrt)(T)) const {
    return a_sqrt(m_data[0]*m_data[0]+m_data[1]*m_data[1]+m_data[2]*m_data[2]+m_data[3]*m_data[3]);
  }

  T normalize(T(*a_sqrt)(T)) {
    T norme = length(a_sqrt);
    if(norme==T()) return T();
    divide(norme);
    return norme;
  }

  bool equal(const vec4& a_vec) const {
    if(m_data[0]!=a_vec.m_data[0]) return false;
    if(m_data[1]!=a_vec.m_data[1]) return false;
    if(m_data[2]!=a_vec.m_data[2]) return false;
    if(m_data[3]!=a_vec.m_data[3]) return false;
    return true;
  }
  bool equal(const vec4& a_vec,const T& a_epsil) const {
    T* tp = (T*)m_data;
    T* ap = (T*)a_vec.m_data;
    for(unsigned int i=0;i<4;i++,tp++,ap++) {
      T diff = (*tp) - (*ap);
      if(diff<zero()) diff *= minus_one();
      if(diff>=a_epsil) return false;
    }
    return true;
  }

  bool is_proportional(const vec4& a_vec,T& a_factor) const {
    // If true, then : a_vec = a_factor * this.
    a_factor = zero();
    bool first = true;
    T* tp = (T*)m_data;
    T* ap = (T*)a_vec.m_data;
    for(unsigned int i=0;i<4;i++,tp++,ap++) {
             if( ((*tp)==zero()) && ((*ap)==zero())) {
        continue;
      } else if( ((*tp)!=zero()) && ((*ap)==zero())) {
        return false;
      } else if( ((*tp)==zero()) && ((*ap)!=zero())) {
        return false;
      } else {
        if(first) {
          a_factor = (*ap)/(*tp);
          first = false;
        } else {
          if((*ap)!=(*tp)*a_factor) return false;
        }
      }
    }
    return true;
  }

  void multiply(const T& a_T) {
    m_data[0] *= a_T;
    m_data[1] *= a_T;
    m_data[2] *= a_T;
    m_data[3] *= a_T;
  }

  bool divide(const T& a_T) {
    if(a_T==T()) return false;
    m_data[0] /= a_T;
    m_data[1] /= a_T;
    m_data[2] /= a_T;
    m_data[3] /= a_T;
    return true;
  }

  void add(const vec4& a_v) {
    m_data[0] += a_v.m_data[0];
    m_data[1] += a_v.m_data[1];
    m_data[2] += a_v.m_data[2];
    m_data[3] += a_v.m_data[3];
  }

  void add(const T& a0,const T& a1,const T& a2,const T& a3) {
    m_data[0] += a0;
    m_data[1] += a1;
    m_data[2] += a2;
    m_data[3] += a3;
  }

  void subtract(const vec4& a_v) {
    m_data[0] -= a_v.m_data[0];
    m_data[1] -= a_v.m_data[1];
    m_data[2] -= a_v.m_data[2];
    m_data[3] -= a_v.m_data[3];
  }

  void subtract(const T& a0,const T& a1,const T& a2,const T& a3) {
    m_data[0] -= a0;
    m_data[1] -= a1;
    m_data[2] -= a2;
    m_data[3] -= a3;
  }

public: //operators
  T& operator[](size_t a_index) {
    //WARNING : no check on a_index.
    return m_data[a_index];
  }
  const T& operator[](size_t a_index) const {
    //WARNING : no check on a_index.
    return m_data[a_index];
  }

  vec4 operator+(const vec4& a_v) const {
    return vec4(m_data[0]+a_v.m_data[0],
                m_data[1]+a_v.m_data[1],
                m_data[2]+a_v.m_data[2],
                m_data[3]+a_v.m_data[3]);
  }

  vec4 operator-(const vec4& a_v) const {
    return vec4(m_data[0]-a_v.m_data[0],
                m_data[1]-a_v.m_data[1],
                m_data[2]-a_v.m_data[2],
                m_data[3]-a_v.m_data[3]);
  }

  vec4 operator*(const T& a_v) const {
    return vec4(m_data[0]*a_v,
                m_data[1]*a_v,
                m_data[2]*a_v,
                m_data[3]*a_v);
  }

  bool operator==(const vec4& a_v) const {return equal(a_v);}
  bool operator!=(const vec4& a_v) const {return !operator==(a_v);}

public: //for tools/sg/sf_vec
  typedef unsigned int size_type;
  size_type size() const {return 4;}
  const T* data() const {return m_data;}
protected:
  T m_data[4];
private:static void check_instantiation() {vec4<float> v;}
};

//for sf, mf :
template <class T>
inline const T* get_data(const vec4<T>& a_v) {return a_v.data();}

}

#include <ostream>

namespace tools {

// for sf_vec::dump().
template <class T>
inline std::ostream& operator<<(std::ostream& a_out,const vec4<T>& a_this){
  a_out << "x = " << a_this.v0()
        << ",y = " << a_this.v1()
        << ",z = " << a_this.v2()
        << ",t = " << a_this.v3();
  return a_out;
}

}

#endif
