/// based on https://mklimenko.github.io/english/2018/06/04/constexpr-random/
#pragma once

#include <atomic>
#include <cstdint>
#include <gsl/gsl>
#include <limits>
#include <random>

#include "HyperionUtils/Concepts.h"

namespace hyperion::math {
	using gsl::narrow_cast;
#ifndef _MSC_VER
	using std::size_t;
#endif //_MSC_VER

// clang-format off
#ifndef _MSC_VER
	// NOLINTNEXTLINE
	#define IGNORE_DATETIME_START \
		_Pragma("GCC diagnostic push") \
		_Pragma("GCC diagnostic ignored \"-Wdate-time\"")
#else
	// NOLINTNEXTLINE
	#define IGNORE_DATETIME_START
#endif
// clang-format on

// clang-format off
#ifndef _MSC_VER
	// NOLINTNEXTLINE
	#define IGNORE_DATETIME_STOP \
		_Pragma("GCC diagnostic pop")
#else
	// NOLINTNEXTLINE
	#define IGNORE_DATETIME_STOP
#endif
	// clang-format on

// clang-format off
#ifndef _MSC_VER
	// NOLINTNEXTLINE
	#define IGNORE_WEAK_VTABLES_START \
		_Pragma("GCC diagnostic push") \
		_Pragma("GCC diagnostic ignored \"-Wweak-vtables\"")
#else
	// NOLINTNEXTLINE
	#define IGNORE_WEAK_VTABLES_START
#endif
// clang-format on

// clang-format off
#ifndef _MSC_VER
	// NOLINTNEXTLINE
	#define IGNORE_WEAK_VTABLES_STOP \
		_Pragma("GCC diagnostic pop")
#else
	// NOLINTNEXTLINE
	#define IGNORE_WEAK_VTABLES_STOP
#endif
	// clang-format on

	IGNORE_WEAK_VTABLES_START
	class Engine {
	  public:
		constexpr Engine() noexcept = default;
		constexpr Engine(const Engine& engine) noexcept = default;
		constexpr Engine(Engine&& engine) noexcept = default;
		virtual constexpr ~Engine() noexcept = default;

		[[nodiscard]] virtual constexpr auto get_seed() const noexcept -> size_t = 0;
		virtual constexpr auto seed(size_t seed) noexcept -> void = 0;
		virtual constexpr auto generate() noexcept -> size_t = 0;

		template<size_t size>
		inline constexpr auto generate_array() noexcept -> std::array<size_t, size> {
			auto array = std::array<size_t, size>();
			for(auto& element : array) {
				element = generate();
			}
		}

		[[nodiscard]] virtual constexpr auto max_value() const noexcept -> size_t = 0;

		constexpr auto operator=(const Engine& engine) noexcept -> Engine& = default;
		constexpr auto operator=(Engine&& engine) noexcept -> Engine& = default;

		virtual constexpr auto operator()() noexcept -> size_t = 0;
	};
	IGNORE_WEAK_VTABLES_STOP

	template<size_t max = 714025>
	class LinearCongruentialEngine final : public Engine {
	  public:
		constexpr LinearCongruentialEngine() noexcept = default;
		constexpr LinearCongruentialEngine(
			const LinearCongruentialEngine& engine) noexcept = default;
		constexpr LinearCongruentialEngine(LinearCongruentialEngine&& engine) noexcept = default;
		constexpr ~LinearCongruentialEngine() noexcept final = default;

		IGNORE_DATETIME_START
		[[nodiscard]] inline constexpr auto get_seed() const noexcept -> size_t final {
			const auto* t = __TIME__;
			return time_from_string(t, 0) * 60 * 60 + time_from_string(t, 3) * 60
				   + time_from_string(t, 6);
		}
		IGNORE_DATETIME_STOP

		inline constexpr auto seed(size_t seed) noexcept -> void final {
			m_previous = seed;
		}

		[[nodiscard]] inline constexpr auto generate() noexcept -> size_t final {
			m_previous = (m_a * m_previous + m_c) % max;
			return m_previous;
		}

		[[nodiscard]] inline constexpr auto max_value() const noexcept -> size_t final {
			return max;
		}

		constexpr auto operator=(const LinearCongruentialEngine& engine) noexcept
			-> LinearCongruentialEngine& = default;
		constexpr auto operator=(LinearCongruentialEngine&& engine) noexcept
			-> LinearCongruentialEngine& = default;

		inline constexpr auto operator()() noexcept -> size_t final {
			return generate();
		}

	  private:
		size_t m_a = 4096;
		size_t m_c = 150889;
		size_t m_previous = get_seed();

		[[nodiscard]] static inline constexpr auto
		time_from_string(const char* string, int offset) noexcept -> size_t {
			return narrow_cast<size_t>(string[offset] - '0') * 10	// NOLINT
				   + narrow_cast<size_t>(string[offset + 1] - '0'); // NOLINT
		}
	};

	IGNORE_WEAK_VTABLES_START
	template<typename EngineType, utils::concepts::Numeric T = int>
	requires utils::concepts::Derived<EngineType, Engine>
	class Distribution {
	  public:
		constexpr Distribution() noexcept = default;
		constexpr Distribution(const Distribution& distribution) noexcept = default;
		constexpr Distribution(Distribution&& distribution) noexcept = default;
		virtual constexpr ~Distribution() noexcept = default;

		virtual constexpr auto normalized_random_value() noexcept -> double = 0;
		virtual constexpr auto random_value() noexcept -> T = 0;
		template<size_t size>
		inline constexpr auto normalized_random_values() noexcept -> std::array<double, size> {
			auto array = std::array<double, size>();
			for(auto& element : array) {
				element = normalized_random_value();
			}

			return array;
		}
		template<size_t size>
		inline constexpr auto random_values() noexcept -> std::array<T, size> {
			auto array = std::array<T, size>();
			for(auto& element : array) {
				element = random_value();
			}

			return array;
		}
		virtual constexpr auto seed(size_t seed) noexcept -> void = 0;
		[[nodiscard]] virtual constexpr auto get_seed() noexcept -> size_t = 0;

		[[nodiscard]] virtual constexpr auto get_min() const noexcept -> T = 0;
		virtual constexpr auto set_min(T min) noexcept -> void = 0;
		[[nodiscard]] virtual constexpr auto get_max() const noexcept -> T = 0;
		virtual constexpr auto set_max(T max) noexcept -> void = 0;

		constexpr auto
		operator=(const Distribution& distribution) noexcept -> Distribution& = default;
		constexpr auto operator=(Distribution&& distribution) noexcept -> Distribution& = default;

		virtual constexpr auto operator()() noexcept -> T = 0;
	};
	IGNORE_WEAK_VTABLES_STOP

	template<typename EngineType, utils::concepts::Numeric T = int>
	requires utils::concepts::Derived<EngineType, Engine>
	class UniformDistribution final : public Distribution<EngineType, T> {
	  public:
		constexpr UniformDistribution() noexcept requires
			utils::concepts::DefaultConstructible<EngineType>
			: m_engine(std::make_unique<EngineType>()) {
			m_engine->seed(m_engine->get_seed());
		}
		constexpr UniformDistribution(T min, T max) noexcept requires
			utils::concepts::DefaultConstructible<EngineType>
			: m_min(min), m_max(max), m_engine(std::make_unique<EngineType>()) {
			m_engine->seed(m_engine->get_seed());
		}
		constexpr UniformDistribution(T min, T max, std::unique_ptr<EngineType>&& engine) noexcept
			: m_min(min), m_max(max), m_engine(engine) {
			m_engine->seed(m_engine->get_seed());
		}
		explicit constexpr UniformDistribution(std::unique_ptr<EngineType>&& engine) noexcept
			: m_engine(std::move(engine)) {
			m_engine->seed(m_engine->get_seed());
		}
		template<typename... Args>
		requires utils::concepts::Derived<EngineType, Engine> && utils::concepts::
			ConstructibleFrom<EngineType, Args...>
		explicit constexpr UniformDistribution(Args&&... args) noexcept
			: m_engine(std::make_unique<EngineType>(args...)) {
			m_engine->seed(m_engine->get_seed());
		}
		template<typename... Args>
		requires utils::concepts::Derived<EngineType, Engine> && utils::concepts::
			ConstructibleFrom<EngineType, Args...>
		explicit constexpr UniformDistribution(T min, T max, Args&&... args) noexcept
			: m_min(min), m_max(max), m_engine(std::make_unique<EngineType>(args...)) {
			m_engine->seed(m_engine->get_seed());
		}
		constexpr UniformDistribution(const UniformDistribution& distribution) noexcept = default;
		constexpr UniformDistribution(UniformDistribution&& distribution) noexcept = default;

		constexpr ~UniformDistribution() noexcept final = default;

		inline constexpr auto normalized_random_value() noexcept -> double final {
			return narrow_cast<double>(m_engine->generate())
				   / narrow_cast<double>(m_engine->max_value());
		}

		inline constexpr auto random_value() noexcept -> T final {
			return narrow_cast<T>(this->normalized_random_value()) * (m_max - m_min) + m_min;
		}

		inline constexpr auto seed(size_t seed) noexcept -> void final {
			m_engine->seed(seed);
		}

		[[nodiscard]] inline constexpr auto get_seed() noexcept -> size_t final {
			return m_engine->get_seed();
		}

		inline constexpr auto set_min(T min) noexcept -> void final {
			m_min = min;
		}
		[[nodiscard]] inline constexpr auto get_min() const noexcept -> T final {
			return m_min;
		}

		inline constexpr auto set_max(T max) noexcept -> void final {
			m_max = max;
		}
		[[nodiscard]] inline constexpr auto get_max() const noexcept -> T final {
			return m_max;
		}

		constexpr auto operator=(const UniformDistribution& distribution) noexcept
			-> UniformDistribution& = default;
		constexpr auto
		operator=(UniformDistribution&& distribution) noexcept -> UniformDistribution& = default;

		inline constexpr auto operator()() noexcept -> T final {
			return random_value();
		}

	  private:
		T m_min = narrow_cast<T>(0);
		T m_max = narrow_cast<T>(1);
		std::unique_ptr<EngineType> m_engine;
	};

	template<utils::concepts::Numeric T = float>
	[[clang::no_destroy]] static math::UniformDistribution<math::LinearCongruentialEngine<>, T>
		GLOBAL_UNIFORM_DISTRIBUTION;

	template<utils::concepts::Numeric T = float>
	static std::atomic_bool GLOBAL_UNIFORM_DISTRIBUTION_INITIALIZED;

	template<utils::concepts::Numeric T = float>
	inline static constexpr auto initialize_global_uniform_distribution() noexcept -> void {
		bool initialized = false;
		if(GLOBAL_UNIFORM_DISTRIBUTION_INITIALIZED<T>.compare_exchange_strong(
			   initialized,
			   true,
			   std::memory_order_seq_cst))
		{
			GLOBAL_UNIFORM_DISTRIBUTION<
				T> = math::UniformDistribution<math::LinearCongruentialEngine<>, T>();
		}
	}

	template<utils::concepts::FloatingPoint T = float>
	inline auto random_value() noexcept -> T {
		initialize_global_uniform_distribution<T>();
		GLOBAL_UNIFORM_DISTRIBUTION<T>.set_min(narrow_cast<T>(0));
		GLOBAL_UNIFORM_DISTRIBUTION<T>.set_max(narrow_cast<T>(1));

		return GLOBAL_UNIFORM_DISTRIBUTION<T>();
	}

	template<utils::concepts::Numeric T = float>
	inline auto random_value(T min, T max) noexcept -> T {
		initialize_global_uniform_distribution<T>();

		GLOBAL_UNIFORM_DISTRIBUTION<T>.set_min(min);
		GLOBAL_UNIFORM_DISTRIBUTION<T>.set_max(max);

		return GLOBAL_UNIFORM_DISTRIBUTION<T>();
	}
} // namespace hyperion::math


#ifndef IBS_HPP
#define IBS_HPP

#include <array>
#include <assert.h>
#include <memory>
#include <vector>

#include <gsl/gsl_integration.h>
#include <gsl/gsl_errno.h>

#include "jspec2/force.h"
#include "jspec2/rate.h"

class Lattice;
class IonBeam;

enum class IBSModel {MARTINI, BM, BMC, BMZ};

class IBSSolver {
protected:
    double log_c_ = 0.0;     //Coulomb logarithm.
    double k_ = 0.0;          //Coupling rate in transverse directions.
    bool cache_invalid = true;
    bool ibs_by_element = false; //Calculate and output the ibs rate contribution element by element.

    void ibs_coupling(double &rx, double &ry, double k, double emit_x, double emit_y);
    void ibs_by_element_sddshead(std::ofstream& outfile, int n_element);
public:
    double log_c() const { return log_c_; }
    double k() const { return k_; }
    void set_k(double x) { k_ = x; }
    void set_log_c(double x) { log_c_ = x; }
    void set_ibs_by_element(bool b) {ibs_by_element = b;}
    void invalidate_cache() { cache_invalid = true; }

    IBSSolver(double log_c, double k);

    virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) = 0;
};

class IBSSolver_Martini : public IBSSolver {
private:
    struct TrigonometryStorageUV {
        double sin_u2_cos_v2;
        double g1;
        double g2_1;
        double g2_2;
    };
    struct TrigonometryStorageV {
        double sin_v;
        double cos_v;
    };
    struct TrigonometryStorageU {
        double sin_u;
        double sin_u2;
        double cos_u2;
        double g3;
        std::vector<TrigonometryStorageUV> uv;
    };
    struct OpticalStorage {
        double a;
        double b2;
        double c2;
        double d2;
        double dtld;
        double k1;
        double k2;
        double k3;
    };

    int nu_ = 0;                //Grid number in u direction.
    int nv_ = 0;                //Grid number in v direction.
    int nz_ = 0;                //Grid number in z direction.

    // Scratch variables for IBS calculation (Martini model)
    std::vector<double> sigma_xbet, sigma_xbetp, sigma_y, sigma_yp;
    std::vector<TrigonometryStorageU> storage_u;
    std::vector<TrigonometryStorageV> storage_v;
    std::vector<OpticalStorage> storage_opt;
    std::vector<double> f1, f2, f3;

    void bunch_size(const Lattice &lattice, const IonBeam &beam);
    void abcdk(const Lattice &lattice, const IonBeam &beam);
    void coef_f();
    void f();
    double coef_a(const Lattice &lattice, const IonBeam &beam) const;
public:
    int nu() const { return nu_; }
    int nv() const { return nv_; }
    int nz() const { return nz_; }
    void set_nu(int nu) { assert(nu>0&&"Wrong value of nu in IBS parameters!"); nu_ = nu; invalidate_cache(); }
    void set_nv(int nv) { assert(nv>0&&"Wrong value of nv in IBS parameters!"); nv_ = nv; invalidate_cache(); }
    void set_nz(int nz) { assert(nz>0&&"Wrong value of nz in IBS parameters!"); nz_ = nz; invalidate_cache(); }
    IBSSolver_Martini(int nu, int nv, int nz, double log_c, double k);
    virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;
};

class IBSSolver_BM : public IBSSolver {
 private:
     struct OpticalStorage { //variables only depends on the TWISS parameters and the energy.
         double phi;
         double dx2; //D_x * D_x
         double dx_betax_phi_2; // D_x * D_x / (beta_x * beta_x) + phi * phi
         double sqrt_betay; // sqrt(beta_y)
         double gamma_phi_2; // gamma * gamma * phi * phi
     };
     struct Kernels {
         double  psi;
         double sx;
         double sp;
         double sxp;
         double inv_sigma;
     };

     // Scratch variables for IBS calculation (Bjorken-Mtingwa model using Sergei Nagitsev's formula)
     std::vector<OpticalStorage> optical_strage;
     std::vector<Kernels> kernels;
     void init_fixed_var(const Lattice &lattice, const IonBeam &beam);
     void calc_kernels(const Lattice &lattice, const IonBeam &beam);
     double coef_bm(const Lattice &lattice, const IonBeam &beam) const;
 public:
     IBSSolver_BM(double log_c, double k);
     virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;

};

class IBSSolver_BMZ : public IBSSolver {
private:
    int nt_;     //Number of steps for integration.
    struct optcl{
        double phi_x2;
        double phi_y2;
        double hx;
        double hy;
        double dx_2_over_beta_x;
        double dy_2_over_beta_y;
        double beta_x_over_hx;
        double hy_beta_x_over_hx;
        double beta_phi_x2;
        double beta_phi_y2;
        double hy_over_beta_y;
    };
    std::vector<optcl> optical;
    double factor = 3;
    void init_optical(const Lattice &lattice);
    double calc_abc(const Lattice &lattice, const IonBeam& beam, int i, double& a, double& b, double& c,
                               double& ax, double& bx, double& ay, double& by,double& al, double& bl);
    double coef(const Lattice &lattice, const IonBeam &beam) const;
    void calc_integral(double a, double b, double c, double ax, double bx, double ay, double by, double al,
                                  double bl, double& ix, double& iy, double& is, int nt, double u);
public:
    IBSSolver_BMZ(int nt, double log_c, double k);
    void set_nt(int n){assert(n>0&&"Wrong value of nt in IBS parameters!"); nt_ = n; invalidate_cache();}
    virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;
    void set_factor(double x){factor = x;}
};

class IBSSolver_BM_Complete : public IBSSolver {
private:
    int nt_;
    double inv_ex;
    double inv_ey;
    double inv_dp2;
    double gamma;
    double gamma2;
    double factor = 3;
    struct Itgrl{
        double lambda;
        double lambda_sqrt;
        double ct;   // ct = 1/(1-t)^2
    };
    struct Optc {
        double phix;
        double phiy;
        double hx;
        double hy;
    };

    std::vector<Optc> optc;
    std::vector<Itgrl> itgrl;
    void init_optc(const Lattice &lattice);
    double det(std::array<std::array<double, 3>,3>& l);
    double inv(std::array<std::array<double, 3>,3>& l, std::array<std::array<double, 3>,3>& v);
    double trace(std::array<std::array<double, 3>,3>& l){return l[0][0]+l[1][1]+l[2][2];}
    void calc_l(const Lattice& lattice, int i, std::array<std::array<double, 3>,3>& lh,
                std::array<std::array<double, 3>,3>& lv, std::array<std::array<double, 3>,3>& ls);
    void calc_itgl(int i, std::array<std::array<double, 3>,3>& ii, std::array<std::array<double, 3>,3>& l,
                  std::array<std::array<double, 3>,3>& ll, std::array<std::array<double, 3>,3>& lh,
                  std::array<std::array<double, 3>,3>& lv, std::array<std::array<double, 3>,3>& ls);
    void calc_beam_const(const IonBeam& beam);
    double coef(const Lattice &lattice, const IonBeam &beam) const;
public:
     IBSSolver_BM_Complete(int nt, double log_c, double k);
     void set_factor(double x){factor = x;}
     virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;

};
//
//class IBSSolver_BMZ2 : public IBSSolver {
//private:
//    int nt_;     //Number of steps for integration.
//    struct itgrl{
//        double lambda;
////        double lambda_2;
////        double lambda_3;
//        double lambda_sqrt;
//        double ct;   // ct = 1/(1-t)^2
//    };
//    double gamma_2;
//    double gamma_2_inv;
//    double gamma_4;
//    double emit_x_inv;
//    double emit_y_inv;
//    double emit_x2_inv;
//    double emit_y2_inv;
//
//    struct optcl{
//        double phi_x;
//        double phi_y;
//        double dx_2_over_beta_x;
//        double dy_2_over_beta_y;
//    };
//    std::vector<itgrl> integral;
//    std::vector<optcl> optical;
//    struct debug {
//        double  a, b, c, ax, bx, ay, by, al, bl, ix, iy, is;
//    };
//    std::vector<debug> my_debug;
//
//    gsl_integration_workspace *gw;
//
//    size_t limit = 100;
//    double espabs = 1e-12;
//    double esprel = 1e-3;
//    struct P{
//        double a;
//        double b;
//        double c;
//        double ai;
//        double bi;
//    }p;
////    double core(double q, void* params);
//
////    void init_integral(int n);
//    void init_optical(const Lattice &lattice);
//    double calc_abc(const Lattice &lattice, const Beam& beam, int i, double& a, double& b, double& c,
//                               double& ax, double& bx, double& ay, double& by,double& al, double& bl, double& scale);
//    double coef(const Lattice &lattice, const Beam &beam) const;
//    void calc_integral(double a, double b, double c, double ax, double bx, double ay, double by, double al,
//                                  double bl, double& ix, double& iy, double& is, int nt, std::vector<itgrl>& g);
//public:
//    IBSSolver_BMZ(int nt, double log_c, double k);
//    set_nt(int n){assert(n>0&&"Wrong value of nt in IBS parameters!"); nt_ = n; invalidate_cache();}
//    virtual void rate(const Lattice &lattice, const Beam &beam, double &rx, double &ry, double &rs);
//
//    ~IBSSolver_BMZ(){gsl_integration_workspace_free(gw);};
//
//};
//
//
//
//};
#endif


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C header for structures representing a waveform as a list of modes in amplitude/phase form.
 *
 */

#ifndef _STRUCT_H
#define _STRUCT_H

#define _XOPEN_SOURCE 500

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_min.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_complex.h>

#include "constants.h"

#if defined(__cplusplus)
extern "C" {
#elif 0
} /* so that editors will match preceding brace */
#endif

/***************************************************/
/*************** Type definitions ******************/

/* Type for real functions */
typedef double (*RealFunctionPtr)(double);
typedef double (*RealObjectFunctionPtr)(const void *, double);

/* Type for real functions that reference an object */
typedef struct tagObjectFunction
{
  const void * object;
  RealObjectFunctionPtr function;
} ObjectFunction ;
double ObjectFunctionCall(const ObjectFunction*,double);

/* Complex frequency series in amplitude and phase representation (for one mode) */
typedef struct tagCAmpPhaseFrequencySeries
{
  gsl_vector* freq;
  gsl_vector* amp_real; /* We authorize complex amplitudes - will be used for the LISA response */
  gsl_vector* amp_imag; /* We authorize complex amplitudes - will be used for the LISA response */
  gsl_vector* phase;
} CAmpPhaseFrequencySeries;
/* GSL splines for complex amplitude and phase representation (for one mode) */
typedef struct tagCAmpPhaseGSLSpline
{
  gsl_vector* freq;
  gsl_spline* spline_amp_real; /* We authorize complex amplitudes - will be used for the LISA response */
  gsl_spline* spline_amp_imag; /* We authorize complex amplitudes - will be used for the LISA response */
  gsl_spline* spline_phase;
  gsl_interp_accel* accel_amp_real;
  gsl_interp_accel* accel_amp_imag;
  gsl_interp_accel* accel_phase;
} CAmpPhaseGSLSpline;
/* Splines in matrix form for complex amplitude and phase representation (for one mode) */
typedef struct tagCAmpPhaseSpline
{
  gsl_matrix* spline_amp_real; /* We authorize complex amplitudes - will be used for the LISA response */
  gsl_matrix* spline_amp_imag; /* We authorize complex amplitudes - will be used for the LISA response */
  gsl_matrix* quadspline_phase;
} CAmpPhaseSpline;

/* Complex frequency series in real/imaginary part representation (for one mode, or their sum) */
typedef struct tagReImFrequencySeries
{
  gsl_vector* freq;
  gsl_vector* h_real;
  gsl_vector* h_imag;
} ReImFrequencySeries;

/* Complex frequency series in real/imaginary part representation (for one mode, or their sum) */
/* NOTE: for now, exact duplicata of ReImFrequencySeries - differentiated for readability of the code */
typedef struct tagReImTimeSeries
{
  gsl_vector* times;
  gsl_vector* h_real;
  gsl_vector* h_imag;
} ReImTimeSeries;

/* Complex frequency series in amplitude/phase representation (representing one mode) */
/* NOTE: for now, exact duplicata of ReImFrequencySeries - differentiated for readability of the code */
typedef struct tagAmpPhaseTimeSeries
{
  gsl_vector* times;
  gsl_vector* h_amp;
  gsl_vector* h_phase;
} AmpPhaseTimeSeries;

/* Real time series */
/* NOTE: could change the h to something more general, like values - also used for TD 22 amplitude, for instance  */
typedef struct tagRealTimeSeries
{
  gsl_vector* times;
  gsl_vector* h;
} RealTimeSeries;

/* List structure, for a list of modes, each in amplitude and phase form */
typedef struct tagListmodesCAmpPhaseFrequencySeries
{
  CAmpPhaseFrequencySeries*                      freqseries; /* The frequencies series with amplitude and phase */
  int                                            l; /* Node mode l  */
  int                                            m; /* Node submode m  */
  struct tagListmodesCAmpPhaseFrequencySeries*    next; /* Next pointer */
} ListmodesCAmpPhaseFrequencySeries;

/* List structure, for a list of modes, each with interpolated splines in amplitude and phase form */
typedef struct tagListmodesCAmpPhaseSpline
{
  CAmpPhaseSpline*                       splines; /* The frequencies series with amplitude and phase */
  int                                    l;       /* Node mode l  */
  int                                    m;       /* Node submode m  */
  struct tagListmodesCAmpPhaseSpline*    next;    /* Next pointer */
} ListmodesCAmpPhaseSpline;

/**************************************************************/
/* Functions computing the max and min between two int */
int max (int a, int b);
int min (int a, int b);

/**************************************************************/
/************** GSL error handling and I/O ********************/

/* GSL error handler */
void Err_Handler(const char *reason, const char *file, int line, int gsl_errno);

/* Functions to read/write data from files */
int Read_Vector(const char dir[], const char fname[], gsl_vector *v);
int Read_Matrix(const char dir[], const char fname[], gsl_matrix *m);
int Read_Text_Vector(const char dir[], const char fname[], gsl_vector *v);
int Read_Text_Matrix(const char dir[], const char fname[], gsl_matrix *m);
int Write_Vector(const char dir[], const char fname[], gsl_vector *v);
int Write_Matrix(const char dir[], const char fname[], gsl_matrix *m);
int Write_Text_Vector(const char dir[], const char fname[], gsl_vector *v);
int Write_Text_Matrix(const char dir[], const char fname[], gsl_matrix *m);

/**********************************************************/
/**************** Internal functions **********************/

/* Functions for list manipulations */
ListmodesCAmpPhaseFrequencySeries* ListmodesCAmpPhaseFrequencySeries_AddModeNoCopy(
	   ListmodesCAmpPhaseFrequencySeries* appended,  /* List structure to prepend to */
	   CAmpPhaseFrequencySeries* freqseries,  /* data to contain */
	   int l, /*< major mode number */
	   int m  /*< minor mode number */
);
ListmodesCAmpPhaseFrequencySeries* ListmodesCAmpPhaseFrequencySeries_GetMode(
	   ListmodesCAmpPhaseFrequencySeries* const list,  /* List structure to get a particular mode from */
	   int l, /*< major mode number */
	   int m  /*< minor mode number */
);
void ListmodesCAmpPhaseFrequencySeries_Destroy(
	   ListmodesCAmpPhaseFrequencySeries* list  /* List structure to destroy; notice that the data is destroyed too */
);
ListmodesCAmpPhaseSpline* ListmodesCAmpPhaseSpline_AddModeNoCopy(
	   ListmodesCAmpPhaseSpline* appended,  /* List structure to prepend to */
	   CAmpPhaseSpline* freqseries,  /* data to contain */
	   int l, /*< major mode number */
	   int m  /*< minor mode number */
);
ListmodesCAmpPhaseSpline* ListmodesCAmpPhaseSpline_GetMode(
	   ListmodesCAmpPhaseSpline* const list,  /* List structure to get a particular mode from */
	   int l, /*< major mode number */
	   int m  /*< minor mode number */
);
void ListmodesCAmpPhaseSpline_Destroy(
	   ListmodesCAmpPhaseSpline* list  /* List structure to destroy; notice that the data is destroyed too */
);

/* Functions to initialize and clean up data structure */
void CAmpPhaseFrequencySeries_Init(
	 CAmpPhaseFrequencySeries** freqseries, /* double pointer for initialization */
	 const int n );                         /* length of the frequency series */
void CAmpPhaseFrequencySeries_Cleanup(CAmpPhaseFrequencySeries* freqseries);
void CAmpPhaseSpline_Init(
	 CAmpPhaseSpline** splines,             /* double pointer for initialization */
	 const int n );                         /* length of the frequency series setting the splines */
void CAmpPhaseSpline_Cleanup(CAmpPhaseSpline* splines);
void CAmpPhaseGSLSpline_Init(
	 CAmpPhaseGSLSpline** splines,          /* double pointer for initialization */
	 const int n );                         /* length of the frequency series setting the splines */
void CAmpPhaseGSLSpline_Cleanup(CAmpPhaseGSLSpline* splines);
void ReImFrequencySeries_Init(
	 ReImFrequencySeries** freqseries,      /* double pointer for initialization */
	 const int n );                         /* length of the frequency series */
void ReImFrequencySeries_Cleanup(ReImFrequencySeries* freqseries);
void ReImTimeSeries_Init(
	 ReImTimeSeries** timeseries,           /* double pointer for initialization */
	 const int n );                         /* length of the time series */
void ReImTimeSeries_Cleanup(ReImTimeSeries* timeseries);
void AmpPhaseTimeSeries_Init(
	 AmpPhaseTimeSeries** timeseries,       /* double pointer for initialization */
	 const int n );                         /* length of the time series */
void AmpPhaseTimeSeries_Cleanup(AmpPhaseTimeSeries* timeseries);
void RealTimeSeries_Init(
	 RealTimeSeries** timeseries,      /* double pointer for initialization */
	 const int n );                /* length of the time series */
void RealTimeSeries_Cleanup(RealTimeSeries* timeseries);

/***********************************************************************/
/**************** I/O functions for internal structures ****************/

/* Note: at the moment, requires external input for the number of lines in the data */
int Read_RealTimeSeries(RealTimeSeries** timeseries, const char dir[], const char file[], const int nblines, const int binary);
int Read_AmpPhaseTimeSeries(AmpPhaseTimeSeries** timeseries, const char dir[], const char file[], const int nblines, const int binary);
int Read_ReImTimeSeries(ReImTimeSeries** timeseries, const char dir[], const char file[], const int nblines, const int binary);
int Write_ReImFrequencySeries(const char dir[], const char file[], ReImFrequencySeries* freqseries, const int binary);
int Write_RealTimeSeries(const char dir[], const char file[], RealTimeSeries* timeseries, int binary);
int Write_AmpPhaseTimeSeries(const char dir[], const char file[], AmpPhaseTimeSeries* timeseries, int binary);
int Write_ReImTimeSeries(const char dir[], const char file[], ReImTimeSeries* timeseries, int binary);

#if 0
{ /* so that editors will match succeeding brace */
#elif defined(__cplusplus)
}
#endif

#endif /* _STRUCT_H */


/** @file */

#ifndef __CCL_LSST_SPECS_INCLUDED__
#define __CCL_LSST_SPECS_INCLUDED__

#include <gsl/gsl_integration.h>
#include <gsl/gsl_spline.h>

/**
 * We assume no relevant source redshift distributions exceed z= 5
 */
#define Z_MIN_SOURCES 0.
#define Z_MAX_SOURCES 5.0

CCL_BEGIN_DECLS
/**
 * P(z) function.
 * This is a P(z) function (which can be user defined)
 * with a void* field to contain the parameters to that function.
 */
typedef struct {
        double (* your_pz_func)(double, double, void *, int*); /*< Function returns the likelihood of measuring a z_ph
        * (first double) given a z_spec (second double), with a pointer to additonal arguments and a status flag.*/
        void *  your_pz_params; /*< Additional parameters to be passed into your_pz_func */
} pz_info;

/**
 * dNdz function.
 * This is a dNdz function (which can be user defined)
 * with a void* field to contain the parameters to that function.
 */
typedef struct {
        double (* your_dN_func)(double, void *, int*); /*< Function returns the differential number density of galaxies wrt redshifts,
        * with a pointer to additonal arguments and a status flag.*/
        void *  your_dN_params; /*< Additional parameters to be passed into your_dN_func */
} dNdz_info;

/**
 * dNdz smail parmas structure.
 * This is a convenience parameters structure
 * to hold the three parameters of the Smail et al. analytic dNdz
 */
 typedef struct{
		double alpha;
		double beta;
		double z0;
	} smail_params;

/**
 * Return dNdz in a particular tomographic bin,
    convolved with a photo-z model (defined by the user), and normalized.
 * @param z redshift
 * @param dNdz_type the choice of dN/dz from Chang+
 * @param bin_zmin the minimum redshift of the tomorgraphic bin
 * @param bin_zmax the maximum redshift of the tomographic bin
 * @param photo_info the P(z) info struct
 * @param tomoout the output dN/dz
 * @param status Status flag. 0 if there are no errors, nonzero otherwise.
 * @return void
 */
void ccl_dNdz_tomog(double z, double bin_zmin, double bin_zmax, pz_info * photo_info,  dNdz_info * dN_info, double *tomoout, int *status);

/**
 * This function creates a structure amalgamating the information on an analytic true dNdz, plus some parameters.
 * @param params parameters for the analytic dNdz form
 * @param dNdz_func dNdz function
 * @return a structure with the dNdz and parameters
 */

dNdz_info* ccl_create_dNdz_info(void * params, double(*dNdz_func)(double,void*,int*));

/**
 * This function creates a structure containing the true dNdz for the built-in Smail-type analytic form:
 * dNdz ~ z^alpha exp(- (z/z0)^beta)
 * @param alpha
 * @param z0
 * @param beta
 * @return a structure with the built-in Smail-type dNdz and parameters
 */
 dNdz_info* ccl_create_Smail_dNdz_info(double alpha, double beta, double z0);

/** Free memory holding the structure containing dNdz information.
 * @param dN_info that holds user-defined dNdz and parameters
 * @return void
 */
void ccl_free_dNdz_info(dNdz_info * dN_info);

/**
 * This function creates a structure amalgamating the information on the photo-z model, P(z) plus some parameters.
 * @param params parameters for the P(z) function
 * @param pz_func P(z) function
 * @return a structure with the P(z) and parameters
 */

pz_info* ccl_create_photoz_info(void * params, double(*pz_func)(double, double,void*,int*));

/**
 * This function creates a structure containing the photo-z model for the built-in Gaussian photo-z pdf.
 * @param sigma_z0 The photo-z uncertainty at z=0. The photo-z uncertainty is assumed to scale like (1 + z).
 * @return a structure with the built-in Gaussian P(z) and parameters
 */
pz_info* ccl_create_gaussian_photoz_info(double sigma_z0);

/** Free memory holding the structure containing user-input photoz information.
 * @param my_photoz_info that holds user-defined P(z) and parameters
 * @return void
 */
void ccl_free_photoz_info(pz_info *my_photoz_info);

CCL_END_DECLS

#endif


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C header for the instrumental noise for LISA-type detectors.
 *
 * Formulas taken from Królak&al gr-qc/0401108 (c.f. section III).
 *
 */

#ifndef _LISANOISE_H
#define _LISANOISE_H

#define _XOPEN_SOURCE 500

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_min.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_complex.h>

#include "constants.h"
#include "LISAgeometry.h"

#if defined(__cplusplus)
extern "C" {
#elif 0
} /* so that editors will match preceding brace */
#endif

/************************************************************************/
/****** Global variables storing min and max f for the noise PSD  *******/

/* Defines bounds in frequency beyond which we don't trust the instrument model anymore - all waveforms will be cut to this range */
/* Here extended range - allows for instance to taper the FD signal for f>1Hz */
#define __LISASimFD_Noise_fLow 1.e-6
#define __LISASimFD_Noise_fHigh 5.
/* Original, more conservative bounds */
//#define __LISASimFD_Noise_fLow 1.e-5
//#define __LISASimFD_Noise_fHigh 1.

/**************************************************************/
/****** Prototypes: functions evaluating the noise PSD  *******/

/* Function returning the relevant noise function, given a set of TDI observables and a channel */
ObjectFunction NoiseFunction(const LISAconstellation *variant, const TDItag tditag, const int nchan);

/* Noise Sn for TDI observables - factors have been scaled out both in the response and the noise */
double SnXYZ(const LISAconstellation *variant, double f);
double Snalphabetagamma(const LISAconstellation *variant, double f);
double SnAXYZ(const LISAconstellation *variant, double f);
double SnEXYZ(const LISAconstellation *variant, double f);
double SnTXYZ(const LISAconstellation *variant, double f);
double SnAalphabetagamma(const LISAconstellation *variant, double f);
double SnEalphabetagamma(const LISAconstellation *variant, double f);
double SnTalphabetagamma(const LISAconstellation *variant, double f);

/* Noise functions for AET(XYZ) without rescaling */
double SnAXYZNoRescaling(const LISAconstellation *variant, double f);
double SnEXYZNoRescaling(const LISAconstellation *variant, double f);
double SnTXYZNoRescaling(const LISAconstellation *variant, double f);

/* Function returning the relevant noise function, given a set of TDI observables and a channel */
/* double (*NoiseFunction(const TDItag tditag, const int chan))(double); */

/* The noise functions themselves */
/* double NoiseSnA(const double f); */
/* double NoiseSnE(const double f); */
/* double NoiseSnT(const double f); */

#if 0
{ /* so that editors will match succeeding brace */
#elif defined(__cplusplus)
}
#endif

#endif /* _LISANOISE_H */


/* rstat/rquantile.c
 *
 * Copyright (C) 2015 Patrick Alken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <stdlib.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_statistics.h>
#include <gsl/gsl_rstat.h>

/*
 * Running quantile calculation based on the paper
 *
 * [1] R. Jain and I. Chlamtac, "The P^2 algorithm for dynamic
 *     calculation of quantiles and histograms without storing
 *     observations", Communications of the ACM, October 1985
 */

static double calc_psq(const double qp1, const double q, const double qm1,
                       const double d, const double np1, const double n, const double nm1);

gsl_rstat_quantile_workspace *
gsl_rstat_quantile_alloc(const double p)
{
  gsl_rstat_quantile_workspace *w;

  w = calloc(1, sizeof(gsl_rstat_quantile_workspace));
  if (w == 0)
    {
      GSL_ERROR_NULL ("failed to allocate space for workspace", GSL_ENOMEM);
    }

  w->p = p;

  gsl_rstat_quantile_reset(w);

  return w;
} /* gsl_rstat_quantile_alloc() */

void
gsl_rstat_quantile_free(gsl_rstat_quantile_workspace *w)
{
  free(w);
} /* gsl_rstat_quantile_free() */

int
gsl_rstat_quantile_reset(gsl_rstat_quantile_workspace *w)
{
  const double p = w->p;
  size_t i;

  /* initialize positions n */
  for (i = 0; i < 5; ++i)
    w->npos[i] = i + 1;

  /* initialize n' */
  w->np[0] = 1.0;
  w->np[1] = 1.0 + 2.0 * p;
  w->np[2] = 1.0 + 4.0 * p;
  w->np[3] = 3.0 + 2.0 * p;
  w->np[4] = 5.0;

  /* initialize dn' */
  w->dnp[0] = 0.0;
  w->dnp[1] = 0.5 * p;
  w->dnp[2] = p;
  w->dnp[3] = 0.5 * (1.0 + p);
  w->dnp[4] = 1.0;

  w->n = 0;

  return GSL_SUCCESS;
}

int
gsl_rstat_quantile_add(const double x, gsl_rstat_quantile_workspace *w)
{
  if (w->n < 5)
    {
      w->q[w->n] = x;
    }
  else
    {
      int i;
      int k = -1;

      if (w->n == 5)
        {
          /* initialization: sort the first five heights */
          gsl_sort(w->q, 1, w->n);
        }

      /* step B1: find k such that q_k <= x < q_{k+1} */
      if (x < w->q[0])
        {
          w->q[0] = x;
          k = 0;
        }
      else if (x >= w->q[4])
        {
          w->q[4] = x;
          k = 3;
        }
      else
        {
          for (i = 0; i <= 3; ++i)
            {
              if (w->q[i] <= x && x < w->q[i + 1])
                {
                  k = i;
                  break;
                }
            }
        }

      if (k < 0)
        {
          /* we could get here if x is nan */
          GSL_ERROR ("invalid input argument x", GSL_EINVAL);
        }

      /* step B2(a): update n_i */
      for (i = k + 1; i <= 4; ++i)
        ++(w->npos[i]);

      /* step B2(b): update n_i' */
      for (i = 0; i < 5; ++i)
        w->np[i] += w->dnp[i];

      /* step B3: update heights */
      for (i = 1; i <= 3; ++i)
        {
          double ni = (double) w->npos[i];
          double d = w->np[i] - ni;

          if ((d >= 1.0 && (w->npos[i + 1] - w->npos[i] > 1)) ||
              (d <= -1.0 && (w->npos[i - 1] - w->npos[i] < -1)))
            {
              int dsign = (d > 0.0) ? 1 : -1;
              double qp1 = w->q[i + 1];
              double qi = w->q[i];
              double qm1 = w->q[i - 1];
              double np1 = (double) w->npos[i + 1];
              double nm1 = (double) w->npos[i - 1];
              double qp = calc_psq(qp1, qi, qm1, (double) dsign,
                                   np1, ni, nm1);

              if (qm1 < qp && qp < qp1)
                w->q[i] = qp;
              else
                {
                  /* use linear formula */
                  w->q[i] += dsign * (w->q[i + dsign] - qi) / ((double) w->npos[i + dsign] - ni);
                }

              w->npos[i] += dsign;
            }
        }
    }

  ++(w->n);

  return GSL_SUCCESS;
} /* gsl_rstat_quantile_add() */

double
gsl_rstat_quantile_get(gsl_rstat_quantile_workspace *w)
{
  if (w->n >= 5)
    {
      return w->q[2];
    }
  else
    {
      /* not yet initialized */
      gsl_sort(w->q, 1, w->n);
      return gsl_stats_quantile_from_sorted_data(w->q, 1, w->n, w->p);
    }
} /* gsl_rstat_quantile_get() */

static double
calc_psq(const double qp1, const double q, const double qm1,
         const double d, const double np1, const double n, const double nm1)
{
  double outer = d / (np1 - nm1);
  double inner_left = (n - nm1 + d) * (qp1 - q) / (np1 - n);
  double inner_right = (np1 - n - d) * (q - qm1) / (n - nm1);

  return q + outer * (inner_left + inner_right);
} /* calc_psq() */


#ifndef _SPC_FITTING_H
#define _SPC_FITTING_H

#include <math.h>
#include <gsl/gsl_fit.h>
#include <gsl/gsl_multifit.h>
#include "aXe_errors.h"

extern void
comp_vector_average (const double *xs, double *ys,
		       double *ws, double *yi, const int n, const int final);
extern void
comp_vector_median (const double *xs, double *ys,
		      double *ws, double *yi, const int n, const int final);

extern void
comp_vector_linear (const double *xs, double *ys,
		      double *ws, double *yi, const int n, const int final);

extern void
comp_vector_polyN (const int m, const double *xs, double *ys,
		    double *ws, double *yi, const int n, const int final);

extern void
fill_const_value(double *ys, double *ws, double *yi, const int n,
		 double cval, double stdev, const int final);

extern void
det_vector_average (const double *xs, double *ys,
		    double *ws, const int n, double *avg, double *std);
extern void
det_vector_median (const double *xs, double *ys,
		    double *ws, const int n, double *med, double *std);

extern gsl_vector *
det_vector_linear(const double *xs, double *ys, double *ws,
		  const int n, const int weight);

extern gsl_vector *
det_vector_poly_N (int m, const double *const xs, double *const ys,
		   double *const ws, const int n, gsl_vector *c,
		   gsl_matrix *cov);

extern void
fill_linear_interp(const double *const xs, double *const ys,
		   double *const ws, double *yi, const int n,
		   gsl_vector *interp, const int final);

extern void
fill_polyN_interp(const double *const xs, double *const ys,
		  double *const ws, double *yi, const int n,
		  gsl_vector *coeffs, gsl_matrix *cov, gsl_vector *interp,
		  const int final);
#endif