#ifndef CUBAINT_HPP
#define CUBAINT_HPP

#include <vector>
#include <array>
#include <complex>

#include "cuba.h"

namespace cubaint 
{
  enum intmethod {SUAVE, DIVONNE, VEGAS, CUHRE};

  enum limtype {NUM, INF};

  struct limit {
    limit(const double& _number);
    limit(const limtype& _LimType);
    const limtype LimType;
    const double number;
  };

  typedef int (*integrand_t)(const int *ndim, const double x[],
			     const int *ncomp, double f[], void *userdata);

  typedef int (*integrand_tc)(const int *ndim, const double x[],
			      const int *ncomp, std::complex<double> f[], void *userdata);

  typedef std::vector<std::array<limit,2>> limits;

  struct options {
    int seed, flags, nvec, mineval, maxeval;
    double epsrel, epsabs;
    const char *statefile;
    int *spin;
    options() :
      seed(0), flags(0), nvec(1), mineval(0), maxeval(50000),
      epsrel(1e-3), epsabs(1e-12), statefile(NULL), spin(NULL)
    {};
    
    struct suave {
      int nnew;
      double flatness;
      int nmin;
      suave() :
	nnew(1000), flatness(25.), nmin(2)
      {};
    };

    struct divonne {
      int key1, key2, key3, maxpass, ngiven, ldxgiven, nextra;
      double border, maxchisq, mindeviation;
      double *xgiven;
      peakfinder_t peakfinder;
      divonne() :
	key1(47), key2(1), key3(1), maxpass(5), border(0), maxchisq(10),
	mindeviation(.25), ngiven(0), ldxgiven(0), xgiven(NULL),
	peakfinder(NULL), nextra(0)
      {};
    };

    struct vegas {
      int nstart, nincrease, nbatch, gridno;
      vegas() :
	nstart(1000), nincrease(500), nbatch(1000), gridno(0)
      {};
    };

    struct cuhre {
      int key;
      cuhre() :
	key(0)
      {};
    };
    
    suave Suave;
    divonne Divonne;
    vegas Vegas;
    cuhre Cuhre;
  };

  extern const options DefaultOptions;

  int integrate (integrand_t integrand, const intmethod& IntMethod, 
		 const limits& Limits, const int& ncomp,
		 int& nregions,
		 int& neval, int& fail, double integral[], 
		 double error[], double prob[], 
		 const unsigned int& verbosity=0, 
		 void *userdata=NULL, const options& Options=DefaultOptions);

  int integrate (integrand_tc integrand, const intmethod& IntMethod, 
		 const limits& Limits, const int& ncomp,
		 int& nregions,
		 int& neval, int& fail, std::complex<double> integral[], 
		 std::complex<double> error[], std::complex<double> prob[], 
		 const unsigned int& verbosity=0, 
		 void *userdata=NULL, const options& Options=DefaultOptions);
};

#endif
