/**
 * @file fitter.hh
 * @author Christoph Langenbruch, Renata Kopecna
 * @date 2010-10-13
 *
 */

#ifndef FITTER_H
#define FITTER_H

#include <string>
#include <vector>
#include <mutex>

#include <TMinuit.h>

namespace fcnc {
    class event;
    class parameter;
    class parameters;
    class pdf;
    class plotter;
    class options;
class fitter;

///gives wether a i (absolutely) larger than b
template<class T> struct GreaterThan{
  bool operator()(const T a, const T b) const
  { return std::abs(a) > std::abs(b); }
};

///adds up all probability values in an efficient and numerically optimal manner
template<typename iterator> typename iterator::value_type add_results(iterator begin, iterator end){
  typedef typename iterator::value_type T;
  if (end == begin) return 0.;
  std::make_heap(begin, end, GreaterThan<T>());

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

///The fitter class implements an unbinned multithreaded maximum likelihood fit
class fitter {
public:
  ///global mutex for use of threads
  //static boost::mutex fitter_mutex;
  static std::mutex fitter_mutex;
  ///global fitter object for minuit
  static fitter* minuit_one_fitter;
  ///empirical constant which is subtracted to improve the numerical stability of the fit
  double empirical_constant;
  ///the minuit 1 minimizer object
  TMinuit* minuit_one;
  ///pointer to events to fit
  std::vector< std::vector<event>* > events;
  ///pointer to parameter sets to fit
  std::vector< parameters* > params;
  ///common parameters in different parameter sets
  std::vector< std::string > common_params;
  //plotters
  std::vector< plotter* > plots;
  ///pointer to the pdf(s)
  std::vector<pdf*> pdfs;
  ///pointer to the options object
  options* opts;
  ///if true, this fitter has already been initialized
  bool initialized;
  ///every thread adds the result of their likelihood calculation to this
  long double lh_tot;
  ///use square of weights for error correction in weighted fits
  bool square_weights;
public:
  ///minuit one FCN
  static void minuit_one_fcn(Int_t &npar, Double_t *grad, Double_t &lh, Double_t *params, Int_t iflag);
  ///constructor
  fitter(options* o);
  ///destructor
  ~fitter();
  /** fit the parameter set to the events using the pdf
     * @param probability pointer to pdf to use in the likelihood fit
     * @param parameters pointer to parameterset to use in the likelihood fit
     * @param events pointer to a vector of events for which to maximise the likelihood
     * @param index optional parameter to give the current run of a toystudy **/
  int fit(std::vector<pdf*> probs, std::vector<parameters*> parms, std::vector< std::vector<event> * > ev, std::string index="");
  /** fit the parameter set to the events using the pdf
     * @param vector of probability pointers to pdfs to use in the likelihood fit
     * @param vector of pointers to parametersets to use in the likelihood fit
     * @param vector of pointers to vector of events for which to maximise the likelihood
     * @param index optional parameter to give the current run of a toystudy **/
  int fit(pdf* probs, parameters* parms, std::vector<event>* ev, std::string index="");
  //set pdfs, parameters and events to extract likelihoods manually:
  void init(std::vector<pdf*> probs, std::vector<parameters*> parms, std::vector< std::vector<event> * > ev, std::string index="");
  void init(pdf* probs, parameters* parms, std::vector<event>* ev, std::string index="");
  ///add a parameter to minuit
  void define_parameter(int i, const parameter & p);
  ///reset all parameters to the start values
  void reset_param_values();
  ///calculates log (prod(p_i)) = sum lh(p_i), calls likelihood_thread for every thread
  double likelihood();
  ///calculates the log pdf for a single thread
  void likelihood_thread(int thread_id);
  //set plotter
  void set_plotters(plotter* plot);
  ///set plotters
  void set_plotters(std::vector<plotter*> plotters);
  ///define common parameters for all pdfs
  void set_common_parameters(std::vector<std::string> common_pars);
  ///is the parameter with name one of the common parameters
  bool is_common(std::string name);
  ///is this the first occurence of the parameter?
  bool is_first(std::string name, unsigned int idx_i, unsigned int idx_j);
  ///returns the nth minuit index for parameter with name
  int nth_minuit_index(std::string name, unsigned int n);
  ///returns the first minuit index for parameter with name
  int first_minuit_index(std::string name);

  ///returns wether this is an analysis with blinded parameters
  bool is_blind();
};


}//end namespace

//Print migrad status with explanation
void printMigradStatus(int status);
#endif
