/**
 * @file funcs.hh
 * @author Christoph Langenbruch, Renata Kopecna
 * @date 2009-03-18
 *
 */

#ifndef FUNCS_H
#define FUNCS_H

#include <complex>

#include <TRandom3.h>
#include <Math/Boost.h>
#include <Math/Vector4D.h>
#include <Math/Vector3D.h>
#include <TMatrixDSym.h>
#include <TMatrixD.h>
#include <Math/SpecFuncMathCore.h>
#include <Math/SpecFuncMathMore.h>

namespace fcnc { //Forward declaration, fully defined in options.hh
    class options;
    class event;
}

namespace fcnc {

  typedef ROOT::Math::PxPyPzEVector LorentzVector;
  typedef ROOT::Math::Boost LorentzBoost;
  typedef ROOT::Math::DisplacementVector3D<ROOT::Math::Cartesian3D<double> > Vector3;

  TMatrixDSym get_bsz_invcov();
  TMatrixDSym get_bsz_cov();
  TMatrixD get_bsz_mu_invcov();

  double eff_paper(const double& q2, const double& ctl, const double& ctk, const double& phi);

  double get_sliding_error(double xmeas, double xth, double eup, double edown);

  double crystalball(double m, double mean, double sigma, double alpha, double n);
  double twotailedcrystalball(double m, double mean, double sigma, double alpha1, double alpha2, double n1, double n2);

  //blinding
  double evaluate_unblind_uniform(double valueSeenByAnalist, const char* blindingString, double scale);
  double evaluate_unblind_uniform_angle(double valueSeenByAnalist, const char* blindingString, double scale);

  double sqr(double x);
  //basic trigonometric functions
  double costheta2(double costheta);
  double cos2theta(double costheta);
  double sintheta2(double costheta);
  double sintheta_abs(double costheta);
  double sin2theta(double costheta);


  double chebyshev(double x, int n);
  void chebyshev(double x, int n, std::vector<double>& results);
  void chebyshev_to_poly(const std::vector<double>& chebychev, std::vector<double>& poly);
  void correct_poly(const std::vector<double>& poly, std::vector<double>& correct, double min, double max);

  double legendre(double x, int n);
  void legendre(double x, int n, std::vector<double>& results);
  void orthonormal_legendre(double x, int n, std::vector<double>& results);
  void legendre_to_poly(const std::vector<double>& legendre, std::vector<double>& poly);

  ///calculates the associated legendre polynomial for l,m order
  double assoc_legendre(unsigned int l, unsigned int m, double x);
  ///calculates the real part of the spherical harmonics for l, m 
  double spherical_harmonic_re(unsigned int l, unsigned int m, double theta, double phi);
  ///calculates the imaginary part of the spherical harmonics for l, m 
  double spherical_harmonic_im(unsigned int l, unsigned int m, double theta, double phi);
  ///function calculates the angles in the transversity base using the momenta of the final state particles 
  void calculate_transversity_angles(const LorentzVector& mu_minus, 
				     const LorentzVector& mu_plus, 
				     const LorentzVector& k_minus, 
				     const LorentzVector& k_plus, 
				     double & cos_theta, double & angle_phi, double & cos_psi);
  ///this function calculates the convolution sin(angle) from 0 to pi
  double convoluted_sin(double angle, double sigma);
  double convoluted_cos_sin(double angle, double sigma);
  double convoluted_cos(double angle, double sigma);//not used
  double convoluted_cos_2(double angle, double sigma);//not used
  double convoluted_sin_2(double angle, double sigma);//used by s wave
  double convoluted_2_sin(double angle, double sigma);//used by s wave
  double convoluted_2_cos(double angle, double sigma);//not used
  double convoluted_cos_2_sin(double angle, double sigma);
  double convoluted_2_sin_sin(double angle, double sigma);
  double convoluted_sin_3(double angle, double sigma);
  //this integrates the above expressions (vonvolution before has been from 0 to pi
  double int_convoluted_sin(double angle, double sigma);
  double int_convoluted_cos_sin(double angle, double sigma);
  double int_convoluted_cos_2_sin(double angle, double sigma);
  double int_convoluted_2_sin_sin(double angle, double sigma);
  double int_convoluted_sin_3(double angle, double sigma);
  double int_convoluted_sin_2(double angle, double sigma);//used by s wave
  double int_convoluted_2_sin(double angle, double sigma);//used by s wave
  //convolutions for phi. this is easier due to the wraparound at +-pi -> convolution from -int to inf
  double inf_convoluted_sin(double angle, double sigma);
  double inf_convoluted_cos(double angle, double sigma);
  double inf_convoluted_cos_2(double angle, double sigma);
  double inf_convoluted_sin_2(double angle, double sigma);
  double inf_convoluted_2_sin(double angle, double sigma);
  //integral of above
  double int_inf_convoluted_const(double angle, double sigma);
  double int_inf_convoluted_sin(double angle, double sigma);
  double int_inf_convoluted_cos(double angle, double sigma);
  double int_inf_convoluted_cos_2(double angle, double sigma);
  double int_inf_convoluted_sin_2(double angle, double sigma);
  double int_inf_convoluted_2_sin(double angle, double sigma);
  //integrals from -inf to inf over the convolution from 0 to pi with acceptance
  double int_convoluted_sin_cos_n(int n, double sigma);
  double int_convoluted_sin_3_cos_n(int n, double sigma);
  double int_convoluted_cos_2_sin_cos_n(int n, double sigma);
  double int_convoluted_2_sin_sin_cos_n(int n, double sigma);
  double int_convoluted_cos_sin_cos_n(int n, double sigma);
  double int_convoluted_sin_2_cos_n(int n, double sigma);
  //integrals from 0 to pi over the convolution from -inf to inf with acceptance
  double int_inf_convoluted_const_x_n(int n, double sigma);
  double int_inf_convoluted_cos_2_x_n(int n, double sigma);
  double int_inf_convoluted_sin_2_x_n(int n, double sigma);
  double int_inf_convoluted_sin_x_n(int n, double sigma);
  double int_inf_convoluted_2_sin_x_n(int n, double sigma);
  double int_inf_convoluted_cos_x_n(int n, double sigma);


  ///function gives the result of exp(-ct/tau)*sin(delta_m*ct) and exp(-ct/tau)*cos(delta_m*ct) convoluted with a gaussian with width sigma 
  void convoluted_exp_sincos(double ct, double sigma, double tau, double deltam, double& one, double& two);
  ///function gives the norm of the expression exp(-ct/tau)*sin(delta_m*ct) and exp(-ct/tau)*cos(delta_m*ct) convoluted with a gaussian with width sigma 
  void norm_convoluted_exp_sincos(double tau, double deltam, double& one, double& two);
  ///function calculates exp(-ct/tau) convoluted with a gaussian with width sigma 
  double convoluted_exp(double ct, double sigma_ct, double S);
  ///function calculates exp(-ct/tau) depending on the parameters normalized or not 
  double expdecay(double t, double tau, bool normalized);
  ///function calculates 1/(sqrt(2*pi)*exp(-(x-mean)^2/(2*sigma^2)))
  double gauss(double x, double sigma, double mean);
  ///function calculates a normed gaussian between the values min and max used in the mass pdfs 
  double normed_gauss(double x, double sigma, double mean, double min, double max);
  ///integrate gauss from a to b
  double linear(double x, double min, double max, double slope);
  ///very important helper function, the error function with complex argument. This is probably one of the performance bottlenecks. Probably much faster with a lookup table and interpolation

  double threshold(double x, double thr, double n);
  double int_threshold(double a, double b, double thr, double n);
  
  
  //see arxiv 1207.4004
  /*
  std::complex<double> bw_kstar_amp(double mkpi_squared, double gammakstar, double mkstar);
  std::complex<double> bw_kstarzero_amp(double mkpisquared, double gk_re, double gk_im, double gammak, double mk, double gammakstarzero, double mkstarzero);  
  double bw_kstar_amp_squared(double mkpisquared, double gammakstar, double mkstar);
  std::complex<double>  bw_kstar_amp_kstarzero_amp_bar(double mkpisquared, double gammakstar, double mkstar, double gk_re, double gk_im, double gammak, double mk, double gammakstarzero, double mkstarzero);
  double bw_kstarzero_amp_squared(double mkpisquared, double gk_re, double gk_im, double gammak, double mk, double gammakstarzero, double mkstarzero);
  */
  double daughtermom(double m, double m1, double m2);
  std::complex<double> mkpi_simple_bw_kstar_amp(double mkpi, double qsquared, double gammakstar, double mkstar);
  double mkpi_simple_bw_kstar_amp_squared(double mkpi, double qsquared, double gammakstar, double mkstar);
  double gsl_mkpi_simple_bw_kstar_amp_squared(double x, void* par);
  double int_mkpi_simple_bw_kstar_amp_squared(const double& mkpia, const double& mkpib, const double& qsquared, const double& gammakstar, const double& mkstar);

  std::complex<double> mkpi_simple_kstarzero_amp(double mkpi, double qsquared, double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero);
  double mkpi_simple_kstarzero_amp_squared(double mkpi, double qsquared, double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero);
  double gsl_mkpi_simple_kstarzero_amp_squared(double x, void* par);
  double int_mkpi_simple_kstarzero_amp_squared(const double& mkpia, const double& mkpib, const double& qsquared, const double& f800mag, const double& f800phase, const double& gammaf800, const double& mf800, const double& gammakstarzero, const double& mkstarzero);

  std::complex<double> mkpi_simple_bw_kstar_amp_kstarzero_amp_bar(double mkpi, double qsquared,
								  double gammakstar, double mkstar, double asphase, 
								  double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero);
  std::complex<double> mkpi_simple_bw_kstar_amp_bar_kstarzero_amp(double mkpi, double qsquared,
								  double gammakstar, double mkstar, double asphase, 
								  double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero);

  std::complex<double> int_mkpi_simple_bw_kstar_amp_kstarzero_amp_bar(const double& mkpia, const double& mkpib, const double& qsquared,
								      const double& gammakstar, const double& mkstar, const double& asphase, 
								      const double& f800mag, const double& f800phase, const double& gammaf800, const double& mf800, const double& gammakstarzero, const double& mkstarzero);
  std::complex<double> int_mkpi_simple_bw_kstar_amp_bar_kstarzero_amp(const double& mkpia, const double& mkpib, const double& qsquared,
								      const double& gammakstar, const double& mkstar, const double& asphase, 
								      const double& f800mag, const double& f800phase, const double& gammaf800, const double& mf800, const double& gammakstarzero, const double& mkstarzero);

  double gsl_mkpi_simple_re_bw_kstar_amp_kstarzero_amp_bar(double x, void* par);
  double gsl_mkpi_simple_im_bw_kstar_amp_kstarzero_amp_bar(double x, void* par);
  double gsl_mkpi_simple_re_bw_kstar_amp_bar_kstarzero_amp(double x, void* par);
  double gsl_mkpi_simple_im_bw_kstar_amp_bar_kstarzero_amp(double x, void* par);


  std::complex<double> mkpi_bw_kstar_amp(double mkpi, double qsquared, double gammakstar, double mkstar, double R);
  std::complex<double> mkpi_kstarzero_lass_amp(double mkpi, double qsquared, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> mkpi_bw_kstar_amp_kstarzero_lass_amp_bar(double mkpi, double qsquared, double gammakstar, double mkstar, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> mkpi_bw_kstar_amp_bar_kstarzero_lass_amp(double mkpi, double qsquared, double gammakstar, double mkstar, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);
  double mkpi_bw_kstar_amp_squared(double mkpi, double qsquared, double gammakstar, double mkstar, double R);
  double mkpi_kstarzero_lass_amp_squared(double mkpi, double qsquared, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);

  std::complex<double> mkpi_kstarzero_isobar_amp(double mkpi, double qsquared, double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  double mkpi_kstarzero_isobar_amp_squared(double mkpi, double qsquared, double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> mkpi_bw_kstar_amp_kstarzero_isobar_amp_bar(double mkpi, double qsquared,
								  double gammakstar, double mkstar, double asphase, 
								  double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> mkpi_bw_kstar_amp_bar_kstarzero_isobar_amp(double mkpi, double qsquared,
								  double gammakstar, double mkstar, double asphase, 
								  double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  double gsl_mkpi_kstarzero_isobar_amp_squared(double x, void* par);
  double gsl_mkpi_re_bw_kstar_amp_kstarzero_isobar_amp_bar(double x, void* par);
  double gsl_mkpi_im_bw_kstar_amp_kstarzero_isobar_amp_bar(double x, void* par);
  double gsl_mkpi_re_bw_kstar_amp_bar_kstarzero_isobar_amp(double x, void* par);
  double gsl_mkpi_im_bw_kstar_amp_bar_kstarzero_isobar_amp(double x, void* par);
  
  double gsl_mkpi_bw_kstar_amp_squared(double x, void* par);
  double gsl_mkpi_kstarzero_lass_amp_squared(double x, void* par);
  double gsl_mkpi_re_bw_kstar_amp_kstarzero_lass_amp_bar(double x, void* par);
  double gsl_mkpi_im_bw_kstar_amp_kstarzero_lass_amp_bar(double x, void* par);
  double gsl_mkpi_re_bw_kstar_amp_bar_kstarzero_lass_amp(double x, void* par);
  double gsl_mkpi_im_bw_kstar_amp_bar_kstarzero_lass_amp(double x, void* par);

  double int_mkpi_bw_kstar_amp_squared(double mkpia, double mkpib, double qsquared, double gammakstar, double mkstar, double R);
  double int_mkpi_bw_kstar_amp_squared(double qsquared, double gammakstar, double mkstar, double R);  
  double int_mkpi_kstarzero_lass_amp_squared(double mkpia, double mkpib, double qsquared, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);
  double int_mkpi_kstarzero_lass_amp_squared(double qsquared, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);

  std::complex<double> int_mkpi_bw_kstar_amp_kstarzero_lass_amp_bar(double mkpia, double mkpib, double qsquared, double gammakstar, double mkstar, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> int_mkpi_bw_kstar_amp_bar_kstarzero_lass_amp(double mkpia, double mkpib, double qsquared, double gammakstar, double mkstar, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> int_mkpi_bw_kstar_amp_kstarzero_lass_amp_bar(double qsquared, double gammakstar, double mkstar, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> int_mkpi_bw_kstar_amp_bar_kstarzero_lass_amp(double qsquared, double gammakstar, double mkstar, double asphase, double a, double r, double gammakstarzero, double mkstarzero, double R);

  double int_mkpi_kstarzero_isobar_amp_squared(double mkpia, double mkpib, double qsquared, double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  double int_mkpi_kstarzero_isobar_amp_squared(double qsquared, double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  
  std::complex<double> int_mkpi_bw_kstar_amp_kstarzero_isobar_amp_bar(double mkpia, double mkpib, double qsquared,
								      double gammakstar, double mkstar, double asphase, 
								      double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> int_mkpi_bw_kstar_amp_bar_kstarzero_isobar_amp(double mkpia, double mkpib, double qsquared,
								      double gammakstar, double mkstar, double asphase, 
								      double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> int_mkpi_bw_kstar_amp_kstarzero_isobar_amp_bar(double qsquared,
								      double gammakstar, double mkstar, double asphase, 
								      double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);
  std::complex<double> int_mkpi_bw_kstar_amp_bar_kstarzero_isobar_amp(double qsquared,
								      double gammakstar, double mkstar, double asphase, 
								      double f800mag, double f800phase, double gammaf800, double mf800, double gammakstarzero, double mkstarzero, double R);

  double rndgauss(TRandom3* rnd, double mean, double width, double min, double max);
  double rndgauss(TRandom3* rnd, double mean, double widthlo, double widthhi, double min, double max);

  void moments_to_observables(TRandom3* rnd,
			      std::vector<double> mom,
			      std::vector<double> momcov,
			      double fsext,
			      double dfsext,
			      std::vector<double>& obs,
			      std::vector<double>& obscov,
			      bool usefsext = true
			      );

  template<class T> struct NumberGreaterThan
  {
    bool operator()(const T a, const T b) const
    { return std::abs(a) > std::abs(b); }
  };

  template<typename iterator> typename iterator::value_type add_numbers(iterator begin, iterator end){
      typedef typename iterator::value_type T;
      if (end == begin) return 0.;
      std::make_heap(begin, end, NumberGreaterThan<T>());

      for (; begin + 1 < end; ) {
          T x = *begin;
          std::pop_heap(begin, end--, NumberGreaterThan<T>());
          do {
              x += *begin;
              std::pop_heap(begin, end--, NumberGreaterThan<T>());
          } while (std::abs(*begin) >= std::abs(x) && begin < end);
          *end++ = x;
          std::push_heap(begin, end, NumberGreaterThan<T>());
      }
      return *begin;
  };

  std::vector<UInt_t>GetNOutOfM(UInt_t N, UInt_t M, Int_t Seed, bool DoubleCounts, bool PoissonFluctuate);

  template<typename iterator> typename iterator::value_type add_numbers(iterator begin, iterator end);

  int throw_multivariate_normal(const unsigned int ndim, const unsigned int ntoys, const std::vector<double>& xi, const std::vector<double>& cov, std::vector<std::vector<double> >& results);
  int get_mean_cov(const std::vector<std::vector<double> >& meas, std::vector<double>& mean, std::vector<double>& cov);
  
  void sis_to_pis(TRandom3* rnd,
		  std::vector<double> sis,
		  std::vector<double> sicorrs,
		  std::vector<double>& pis,
		  std::vector<double>& picorrs);
  
  
  std::complex<double> cErrF(const std::complex<double>& x);
  ///implementation 2 of the complex error function
  std::complex<double> cErrF_2(const std::complex<double>& x);
  std::complex<double> ErrF_2(const std::complex<double>& x);

  ///implementation 3 of the complex error function
  std::complex<double> cErrF_3(const std::complex<double>& x);
  ///the faddeeva function function
  std::complex<double> Faddeeva_2 (const std::complex<double>& z);
  ///used by cErrF, does the actual calculations
  std::complex<double> wErrF(const std::complex<double>& arg);
  ///This is another version of the complex error function, used in the newer cdf code
  std::complex<double> nwwerf(const std::complex<double> z);

}

#endif
