/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2004-01-21
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef COMPLEX_H
#define COMPLEX_H

#include <math.h>
#include <QtCore>

#include "QGpCoreToolsDLLExport.h"

namespace QGpCoreTools {

class StringSection;

typedef double v2df __attribute__ ((vector_size (16)));

class QGPCORETOOLS_EXPORT Complex
{
public:
  inline Complex(const Complex& c);
  inline Complex(double re=0, double im=0);
  ~Complex() {}

  inline bool operator==(const Complex& c) const;
  inline bool operator!=(const Complex& c) const;

  inline Complex& operator=(const Complex& c);
  inline Complex& operator+=(const Complex& c);
  inline Complex& operator-=(const Complex& c);
  inline Complex& operator*=(const Complex& c);
  inline Complex& operator/=(const Complex& c);

  inline Complex& operator=(const double& d);
  inline Complex& operator+=(const double& d);
  inline Complex& operator-=(const double& d);
  inline Complex& operator*=(const double& d);
  inline Complex& operator/=(const double& d);

  inline Complex operator+(const Complex& c) const;
  inline Complex operator-(const Complex& c) const;
  inline Complex operator*(const Complex& c) const;
  inline Complex operator/(const Complex& c) const;

  inline Complex operator+(const double& d) const;
  inline Complex operator-(const double& d) const;
  inline Complex operator*(const double& d) const;
  inline Complex operator/(const double& d) const;

  bool isNull() const {return _data.values.re==0.0 && _data.values.im==0.0;}
  double re() const {return _data.values.re;}
  double im() const {return _data.values.im;}
  void set(double re, double im) {_data.values.re=re; _data.values.im=im;}
  void setRe(double re) {_data.values.re=re;}
  void setIm(double im) {_data.values.im=im;}
  void setExp(double fac, double arg)
  {
    _data.values.re=fac*::cos(arg);
    _data.values.im=fac*::sin(arg);
  }
  void setUnitExp(double arg)
  {
    _data.values.re=::cos(arg);
    _data.values.im=::sin(arg);
  }
  void conjugate() {_data.values.im=-_data.values.im;}
  double abs() const {return ::sqrt(abs2());}
  double abs2() const {return _data.values.re*_data.values.re+_data.values.im*_data.values.im;}
  double phase() const {return atan2(_data.values.im, _data.values.re);}
  void setAbs(double fac) {setExp(fac, phase());}
  void setPhase(double arg) {setExp(abs(), arg);}

  bool fromString(const StringSection& str);
  QString toString(char format='g', int precision=6) const;

  static const Complex null;
private:
  union {
    v2df vector;
    struct {
      double re, im;
    } values;
  } _data;
};

inline Complex operator+(const double& d, const Complex& c);
inline Complex operator-(const double& d, const Complex& c);
inline Complex operator-(const Complex& c);
inline Complex operator*(const double& d, const Complex& c);
inline Complex operator/(const double& d, const Complex& c);

inline Complex sqrt(const Complex& c);
inline Complex cos(const Complex& c);
inline Complex sin(const Complex& c);
inline Complex tan(const Complex& c);
inline Complex asin(const Complex& c);
inline Complex log(const Complex& c);
inline Complex exp(const Complex& c);
inline Complex conjugate(const Complex& c);
inline Complex inverse(const Complex& c);
inline double abs(const Complex& c);
inline double abs2(const Complex& c);

QGPCORETOOLS_EXPORT QTextStream& operator<< (QTextStream& s, const Complex& c);
QGPCORETOOLS_EXPORT QTextStream& operator>> (QTextStream& s, Complex& c);
QGPCORETOOLS_EXPORT QDataStream& operator<< (QDataStream& s, const Complex& c);
QGPCORETOOLS_EXPORT QDataStream& operator>> (QDataStream& s, Complex& c);

inline Complex::Complex(const Complex& c)
{
#if defined(__SSE__)
  _data.vector=c._data.vector;
#else
  _data.values.re=c._data.values.re;
  _data.values.im=c._data.values.im;
#endif
}

inline Complex::Complex(double re, double im)
{
  _data.values.re=re;
  _data.values.im=im;
}

inline bool Complex::operator==(const Complex& c) const
{
  return _data.values.re==c._data.values.re && _data.values.im==c._data.values.im;
}

inline bool Complex::operator!=(const Complex& c) const
{
  return _data.values.re!=c._data.values.re || _data.values.im!=c._data.values.im;
}

inline Complex& Complex::operator=(const double& d)
{
  _data.values.re=d;
  _data.values.im=0.0;
  return *this;
}

inline Complex& Complex::operator=(const Complex& c)
{
#ifdef __SSE__
  _data.vector=c._data.vector;
#else
  _data.values.re=c._data.values.re;
  _data.values.im=c._data.values.im;
#endif
  return *this;
}

inline Complex& Complex::operator+=(const double& d)
{
  _data.values.re+=d;
  return *this;
}

inline Complex& Complex::operator+=(const Complex& c)
{
#ifdef __SSE__
  _data.vector+=c._data.vector;
#else
  _data.values.re+=c._data.values.re;
  _data.values.im+=c._data.values.im;
#endif
  return *this;
}

inline Complex& Complex::operator-=(const double& d)
{
  _data.values.re-=d;
  return *this;
}

inline Complex& Complex::operator-=(const Complex& c)
{
#ifdef __SSE__
  _data.vector-=c._data.vector;
#else
  _data.values.re-=c._data.values.re;
  _data.values.im-=c._data.values.im;
#endif
  return *this;
}

inline Complex& Complex::operator*=(const double& d)
{
  _data.values.re*=d;
  _data.values.im*=d;
  return *this;
}

inline Complex& Complex::operator*=(const Complex& c)
{
  // __builtin_ia32_shufpd not available for clang++ under Mac OS X
  // TODO switch to _builtin_shuffle() https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html#Vector-Extensions
#if defined(__SSE3__) && !defined(Q_OS_MAC)
  v2df thisIm=__builtin_ia32_shufpd(_data.vector, _data.vector, 3);       // Im part of this in both
  v2df thisRe=__builtin_ia32_shufpd(_data.vector, _data.vector, 0);       // Re part of this in both
  v2df c_flipped=__builtin_ia32_shufpd(c._data.vector, c._data.vector, 1);  // Swap re and im parts of c
  _data.vector=__builtin_ia32_addsubpd(__builtin_ia32_mulpd(thisRe, c._data.vector),
                                       __builtin_ia32_mulpd(thisIm, c_flipped));
#elif defined(__SSE2__) && !defined(Q_OS_MAC)
  v2df thisIm=__builtin_ia32_shufpd(_data.vector, _data.vector,3);       // Im part of this in both
  v2df thisRe=__builtin_ia32_shufpd(_data.vector, _data.vector,0);       // Re part of this in both
  v2df c_flipped=__builtin_ia32_shufpd(c._data.vector,c._data.vector,1); // Swap re and im parts of c
  static const union {                                                   // (signbit,0)
    unsigned int i[4]; v2df v;
  } signbitlow={{0,0x80000000,0,0}};
  thisIm=__builtin_ia32_xorpd(thisIm, signbitlow.v);                     // Change sign of low
  // Multiply and add:
  _data.vector=__builtin_ia32_mulpd(thisRe, c._data.vector)+
               __builtin_ia32_mulpd(thisIm, c_flipped);
#else
  double tmpre=_data.values.re*c._data.values.re-_data.values.im*c._data.values.im;
  _data.values.im=_data.values.re*c._data.values.im+_data.values.im*c._data.values.re;
  _data.values.re=tmpre;
#endif
  return *this;
}

inline Complex& Complex::operator/=(const double& d)
{
  return operator*=(1.0/d);
}

inline Complex& Complex::operator/=(const Complex& c)
{
  operator*=(QGpCoreTools::conjugate(c));
  operator/=(c.abs2());
  return *this;
}

inline Complex Complex::operator+(const double& d) const
{
  return Complex(_data.values.re+d, _data.values.im);
}

inline Complex operator+(const double& d, const Complex& c)
{
  return c+d;
}

inline Complex Complex::operator+(const Complex& c) const
{
  Complex r;
#ifdef __SSE__
  r._data.vector=_data.vector+c._data.vector;
#else
  r._data.values.re=_data.values.re+c._data.values.re;
  r._data.values.im=_data.values.im+c._data.values.im;
#endif
  return r;
}

inline Complex Complex::operator-(const double& d) const
{
  return Complex(_data.values.re-d, _data.values.im);
}

inline Complex operator-(const double& d, const Complex& c)
{
  return Complex(d-c.re(), -c.im());
}

inline Complex operator-(const Complex& c)
{
  return Complex(-c.re(), -c.im());
}

inline Complex Complex::operator-(const Complex& c) const
{
  Complex r;
#ifdef __SSE__
  r._data.vector=_data.vector-c._data.vector;
#else
  r._data.values.re=_data.values.re-c._data.values.re;
  r._data.values.im=_data.values.im-c._data.values.im;
#endif
  return r;
}

inline Complex Complex::operator*(const double& d) const
{
  return Complex(_data.values.re*d, _data.values.im*d);
}

inline Complex operator*(const double& d, const Complex& c)
{
  return Complex(d*c.re(), d*c.im());
}

inline Complex Complex::operator*(const Complex& c) const
{
  Complex r(*this);
  r*=c;
  return r;
}

inline Complex Complex::operator/(const double& d) const
{
  return Complex(_data.values.re/d, _data.values.im/d);
}

inline Complex operator/(const double& d, const Complex& c)
{
  double m2=c.abs2();
  Complex r(d);
  r*=conjugate(c);
  r/=m2;
  return r;
}

inline Complex Complex::operator/(const Complex& c) const
{
  double m2=c.abs2();
  Complex r(*this);
  r*=QGpCoreTools::conjugate(c);
  r/=m2;
  return r;
}

inline Complex sqrt(const Complex& c)
{
  double r2=::sqrt(c.abs());
  double phi2=c.phase()/2;
  return Complex(r2*::cos(phi2), r2*::sin(phi2));
}

inline Complex inverse(const Complex& c)
{
  double m2=1.0/c.abs2();
  return Complex(c.re()*m2, -m2*c.im());
}

inline Complex exp(const Complex& c)
{
  double r=::exp(c.re());
  return Complex(r*::cos(c.im()), r*::sin(c.im()));
}

inline Complex log(const Complex& c)
{
  return Complex(::log(c.abs()), c.phase());
}

/*!
  asin(C)=-i log (sqrt(1-C^2)+i*C), where C is this
*/
inline Complex asin(const Complex& c)
{
  return Complex(0.0,-1.0)*log(sqrt(Complex(1.0)-c*c)+Complex(0.0,1.0)*c);
}

inline Complex cos(const Complex& c)
{
  return Complex(::cos(c.re())*::cosh(c.im()), -::sin(c.re())*::sinh(c.im()));
}

inline Complex sin(const Complex& c)
{
  return Complex(::sin(c.re())*::cosh(c.im()), ::cos(c.re())*::sinh(c.im()));
}

inline Complex tan(const Complex& c)
{
  return sin(c)/cos(c);
}

inline Complex conjugate(const Complex& c)
{
  return Complex(c.re(), -c.im());
}

double abs(const Complex& c)
{
  return c.abs();
}

double abs2(const Complex& c)
{
  return c.abs2();
}

} // namespace QGpCoreTools

#endif // COMPLEX_H
