#ifndef FSTMODELMULTINOM_H
#define FSTMODELMULTINOM_H

/*!======================================================================
   Feature Selection Toolbox 3 source code
   ---------------------------------------

   \file    model_multinom.hpp
   \brief   Implements multinomial model
   \author  Petr Somol (somol@utia.cas.cz) with collaborators, see Contacts at http://fst.utia.cz
   \date    March 2011
   \version 3.1.0.beta
   \note    FST3 was developed using gcc 4.3 and requires
   \note    \li Boost library (http://www.boost.org/, tested with versions 1.33.1 and 1.44),
   \note    \li (\e optionally) LibSVM (http://www.csie.ntu.edu.tw/~cjlin/libsvm/,
                tested with version 3.00)
   \note    Note that LibSVM is required for SVM related tools only,
            as demonstrated in demo12t.cpp, demo23.cpp, demo25t.cpp, demo32t.cpp, etc.

*/ /*
=========================================================================
Copyright:
  * FST3 software (with exception of any externally linked libraries)
    is copyrighted by Institute of Information Theory and Automation (UTIA),
    Academy of Sciences of the Czech Republic.
  * FST3 source codes as presented here do not contain code of third parties.
    FST3 may need linkage to external libraries to exploit its functionality
    in full. For details on obtaining and possible usage restrictions
    of external libraries follow their original sources (referenced from
    FST3 documentation wherever applicable).
  * FST3 software is available free of charge for non-commercial use.
    Please address all inquires concerning possible commercial use
    of FST3, or if in doubt, to FST3 maintainer (see http://fst.utia.cz).
  * Derivative works based on FST3 are permitted as long as they remain
    non-commercial only.
  * Re-distribution of FST3 software is not allowed without explicit
    consent of the copyright holder.
Disclaimer of Warranty:
  * FST3 software is presented "as is", without warranty of any kind,
    either expressed or implied, including, but not limited to, the implied
    warranties of merchantability and fitness for a particular purpose.
    The entire risk as to the quality and performance of the program
    is with you. Should the program prove defective, you assume the cost
    of all necessary servicing, repair or correction.
Limitation of Liability:
  * The copyright holder will in no event be liable to you for damages,
    including any general, special, incidental or consequential damages
    arising out of the use or inability to use the code (including but not
    limited to loss of data or data being rendered inaccurate or losses
    sustained by you or third parties or a failure of the program to operate
    with any other programs).
========================================================================== */

#include <boost/smart_ptr.hpp>
#include <iostream>
#include <sstream>
#include <limits>
#include <cmath>
#include <cstring> // memcpy
#include "error.hpp"
#include "global.hpp"
#include "model.hpp"
#include "indexed_vector.hpp"
#include "indexed_matrix.hpp"

/*============== Template parameter type naming conventions ==============
--------- Numeric types: -------------------------------------------------
DATATYPE - data sample values - usually real numbers (but may be integers
          in text processing etc.)
REALTYPE - must be real numbers - for representing intermediate results of
          calculations like mean, covariance etc.
IDXTYPE - index values for enumeration of data samples - (nonnegative) integers,
          extent depends on numbers of samples in data
DIMTYPE - index values for enumeration of features (dimensions), or classes (not
          class sizes) - (nonnegative) integers, usually lower extent than IDXTYPE,
          but be aware of expressions like _classes*_features*_features !
          in linearized representations of feature matrices for all classes
BINTYPE - feature selection marker type - represents ca. <10 different feature
          states (selected, deselected, sel./desel. temporarily 1st nested loop, 2nd...)
RETURNTYPE - criterion value: real value, but may be extended in future to support
          multiple values
--------- Class types: ---------------------------------------------------
SUBSET       - class of class type Subset
CLASSIFIER   - class implementing interface defined in abstract class Classifier
EVALUATOR    - class implementing interface defined in abstract class Sequential_Step
DISTANCE     - class implementing interface defined in abstract class Distance
DATAACCESSOR - class implementing interface defined in abstract class Data_Accessor
INTERVALCONTAINER - class of class type TIntervaller
CONTAINER    - STL container of class type TInterval
========================================================================== */

namespace FST {

//! Implements multinomial model
//
// \note only non-negative integers assumed as data
template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
class Model_Multinomial : public Model<SUBSET,DATAACCESSOR> {
public:
	typedef boost::shared_ptr<DATAACCESSOR> PDataAccessor;
	typedef boost::shared_ptr<SUBSET> PSubset;
	Model_Multinomial() : _classes(0), _n_max(0), _n(0), _d(0), _allpatterns(0), _doc_avg_length(0) {notify("Model_Multinomial constructor");}
	Model_Multinomial(const Model_Multinomial& mm); // copy-constructor
	virtual ~Model_Multinomial() {notify("Model_Multinomial destructor");}

	virtual void learn(PDataAccessor da); // learn model of full dimensionality
	virtual void learn(PDataAccessor da, const PSubset sub); // learn model of lower dimensionality

	void narrow_to(const PSubset sub); // fill _index[] -> access to data is then mapped to sub-matrix
	void denarrow(); // reset _index[] -> access is reset to the full matrix

	void compute_theta();
	void compute_MI(); // Mutual Information of individual features
	void compute_IB(); // Individual Bhattacharyya of individual features

	DIMTYPE get_n_max() const {return _n_max;}
	DIMTYPE get_n() const {return _n;}
	DIMTYPE get_d() const {return _d;}
	DIMTYPE get_classes() const {return _classes;}

	REALTYPE get_doc_avg_length() const {return _doc_avg_length;}
	// NOTE: caller must ensure any access takes place only during _theta, _IB, _MI and _Pc existence
	REALTYPE get_Pc(const DIMTYPE c) const {assert(c>=0 && c<_classes); return _Pc[c];}
protected:
	template<class, class, class, class, class, class, class> friend class Criterion_Multinomial_Bhattacharyya;
	template<class, class, class, class, class, class, class> friend class Classifier_Multinomial_NaiveBayes;
	const boost::scoped_array<REALTYPE>& get_theta() const {return _theta;}
	const boost::scoped_array<REALTYPE>& get_IB() const {return _IB;}
	const boost::scoped_array<REALTYPE>& get_MI() const {return _MI;}
protected:
	DIMTYPE _classes; // initialized in learn(), WARNING: int sufficient ? note expressions like _classes*_n*_n
	// NOTE: matrix size is defined by:
	//       _n_max - hard constant representing maximum allocated space of size _n_max*_n_max
	//       _n     - "full feature set" dimensionality, _n<=_n_max. If Indexed_Matrix servers as output buffer
	//                to store matrices of various (reduced) dimensionalites, this is needed for correct functionality
	//                of at(), at_raw() etc. (both "raw" and "non-raw" matrix item access methods)
	//       _d     - "virtual subset" dimensionality, _d<=_n. Assuming the matrix holds _n*_n values (_n consecutively stored
	//                rows of _n values each), a virtual sub-matrix can be defined and accessed using at() ("non-raw" methods)
	DIMTYPE _n_max;
	DIMTYPE _n; // initialized in learn(), WARNING: int sufficient ? note expressions like _classes*_n*_n
	void compute_Nsuminclass(PDataAccessor da);
	boost::scoped_array<DATATYPE> _Nsuminclass;
	boost::scoped_array<REALTYPE> _theta;// actually P(term|class)
	boost::scoped_array<REALTYPE> _Pc;
	boost::scoped_array<REALTYPE> _Pc_d; // for compute_MI
	boost::scoped_array<REALTYPE> _Pv;
	boost::scoped_array<REALTYPE> _MI;
	boost::scoped_array<REALTYPE> _IB;

	DIMTYPE _d;
	// NOTE: _index defines a sub-space of subs-space defined in _learn_index
	boost::scoped_array<DIMTYPE> _index; //!< maps feature subset indexes to raw (full set) feature indexes, used for narrow()ing

	IDXTYPE _allpatterns; // initialized in learn()
	REALTYPE _doc_avg_length; // initialized in compute__theta(), needed in compute_IB()

	// NOTE: _learn_index here is not used for narrow()ing, but for learn(da,sub)
	boost::scoped_array<DIMTYPE> _learn_index; // maps virtual sub-space _n dimensions to original _n_max dimensions
};

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::Model_Multinomial(const Model_Multinomial& mm) : // copy-constructor
	Model<SUBSET,DATAACCESSOR>(mm),
	_classes(mm._classes),
	_n_max(mm._n_max),
	_n(mm._n),
	_d(mm._d),
	_allpatterns(mm._allpatterns),
	_doc_avg_length(mm._doc_avg_length)
{
	notify("Model_Multinomial copy-constructor.");
	if(_classes>0) {
		_Pc.reset(new REALTYPE[_classes]); memcpy((void *)_Pc.get(),(void *)(mm._Pc).get(),sizeof(REALTYPE)*_classes);
		_Pc_d.reset(new REALTYPE[_classes]); memcpy((void *)_Pc_d.get(),(void *)(mm._Pc_d).get(),sizeof(REALTYPE)*_classes);
		if(_n_max>0) {
			_Nsuminclass.reset(new DATATYPE[_n_max*_classes]); memcpy((void *)_Nsuminclass.get(),(void *)(mm._Nsuminclass).get(),sizeof(DATATYPE)*_n_max*_classes);
			_theta.reset(new REALTYPE[_n_max*_classes]); memcpy((void *)_theta.get(),(void *)(mm._theta).get(),sizeof(REALTYPE)*_n_max*_classes);
		}
	}
	if(_n_max>0)
	{
		_Pv.reset(new REALTYPE[_n_max]); memcpy((void *)_Pv.get(),(void *)(mm._Pv).get(),sizeof(REALTYPE)*_n_max);
		_MI.reset(new REALTYPE[_n_max]); memcpy((void *)_MI.get(),(void *)(mm._MI).get(),sizeof(REALTYPE)*_n_max);
		_IB.reset(new REALTYPE[_n_max]); memcpy((void *)_IB.get(),(void *)(mm._IB).get(),sizeof(REALTYPE)*_n_max);
		// to store subsubspace info of the (possibly subspace-learned) learned model
		_index.reset(new DIMTYPE[_n_max]); memcpy((void *)_index.get(),(void *)(mm._index).get(),sizeof(DIMTYPE)*_n_max);
		// to store subspace info for the to-be-learned model
		_learn_index.reset(new DIMTYPE[_n_max]); memcpy((void *)_learn_index.get(),(void *)(mm._learn_index).get(),sizeof(DIMTYPE)*_n_max);
	}
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::learn(PDataAccessor da)
{
	assert(da);
	assert(da->getNoOfClasses()>0);
	assert(da->getNoOfFeatures()>0);
	PSubset fullset(new SUBSET(da->getNoOfFeatures()));
	fullset->select_all();
#ifdef DEBUG
	{std::ostringstream sos; sos << "fullset = "<< *fullset << std::endl; syncout::print(std::cout,sos);}
#endif
	learn(da,fullset);
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::learn(PDataAccessor da, const PSubset sub)
{
#ifdef DEBUG
	{std::ostringstream sos; sos <<"Model_Multinomial::learn():"<<std::endl; syncout::print(std::cout,sos);}
#endif
	assert(sub);
	assert(da->getNoOfClasses()>0);
	assert(da->getNoOfFeatures()>0);
	assert(sub->get_n_raw()==da->getNoOfFeatures());

	if(_classes!=da->getNoOfClasses() || sub->get_n_raw()>_n_max) // insufficient buffers - reset constants and reallocate
	{
		_classes=da->getNoOfClasses();
		_n_max=sub->get_n_raw();

		_learn_index.reset(new DIMTYPE[_n_max]); // to store subspace info about the to-be-learned model
		_index.reset(new DIMTYPE[_n_max]); for(DIMTYPE f=0;f<_n_max;f++) _index[f]=f; // to store subsubspace info of the (possibly subspace-learned) learned model
		_Nsuminclass.reset(new DATATYPE[_n_max*_classes]);
		_theta.reset(new REALTYPE[_n_max*_classes]);
		_Pv.reset(new REALTYPE[_n_max]);
		_MI.reset(new REALTYPE[_n_max]);
		_IB.reset(new REALTYPE[_n_max]);
		_Pc.reset(new REALTYPE[_classes]);
		_Pc_d.reset(new REALTYPE[_classes]);
		// NOTE: class priors are estimated from complete data available through da.
		//       Note that when only parts of data are processed (i.e., using data splits), the
		//       estimates below would possibly not precisely depict the relative frequency of documents
		//       in stratified class parts, esp. if class sizes are small
		IDXTYPE _classsizesum=da->getClassSizeSum();
		assert(_classsizesum>0);
		if(_classsizesum==0) throw FST::fst_error("Model_Multinomial::learn: zero _classsizesum");
		for(DIMTYPE c=0;c<_classes;c++) _Pc[c]=(REALTYPE)da->getClassSize(c)/(REALTYPE)_classsizesum;
	}
	_n=sub->get_d_raw(); // actual to-be-learned model dimensionality is to be the one defined by the subset defined in sub
	_d=_n; // access to learned model is initially not narrow()ed
	DIMTYPE f, fi=0;
	for(bool b=sub->getFirstFeature(f);b==true;b=sub->getNextFeature(f)) {_learn_index[fi++]=f;}
	assert(fi==_n);

#ifdef DEBUG
	{
		std::ostringstream sos; sos << "_Pc:"<<std::endl;
		for(DIMTYPE c=0;c<_classes;c++) sos << _Pc[c] << " "; sos << std::endl;
		syncout::print(std::cout,sos);
	}
#endif
	compute_Nsuminclass(da); // sub info passed in form of _learn_index

	DATATYPE total_sum_length=0;
	DIMTYPE wCV=0;
	for(DIMTYPE c=0;c<_classes;c++) {
		for(DIMTYPE f=0;f<_n;f++) total_sum_length+=_Nsuminclass[wCV+f];
		wCV+=_n;
	}
	if(total_sum_length==0) {
		for(DIMTYPE c=0;c<_classes;c++) _Pc_d[c]=1.0/(REALTYPE)_classes; // workaround to prevent division by zero below
	} else {
		wCV=0;
		for(DIMTYPE c=0;c<_classes;c++) {
			DATATYPE class_sum_length=0;
			for(DIMTYPE f=0;f<_n;f++) class_sum_length+=_Nsuminclass[wCV+f]; // in subset case this is done only for selected features
			_Pc_d[c]=(REALTYPE)class_sum_length/(REALTYPE)total_sum_length;
			wCV+=_n;
		}
	}
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::compute_Nsuminclass(PDataAccessor da)
{
	assert(_learn_index);
	assert(_Nsuminclass);
	for(DIMTYPE i=0;i<_classes*_n;i++) _Nsuminclass[i]=0;
	typename DATAACCESSOR::PPattern p;
	IDXTYPE cnt;
	assert(da->getSplitIndex()>0); //
	DIMTYPE wCV=0;
	DIMTYPE _features=da->getNoOfFeatures();
	_allpatterns=0;
	for(DIMTYPE c=0;c<_classes;c++) {
		da->setClass(c);
		for(bool b=da->getFirstBlock(TRAIN,p,cnt);b!=false;b=da->getNextBlock(TRAIN,p,cnt)) {
			for(IDXTYPE i=0;i<cnt;i++) {
				for(DIMTYPE f=0;f<_n;f++) {_Nsuminclass[wCV+f]+=p[i*_features+_learn_index[f]];}
			}
			_allpatterns+=cnt;
		}
		wCV+=_n;
	}
#ifdef DEBUG
	{
		std::ostringstream sos; sos <<"_Nsuminclass:"<<std::endl;
		for(DIMTYPE c=0;c<_classes;c++) {for(DIMTYPE f=0;f<_n;f++) sos << _Nsuminclass[c*_n+f] << " "; sos << std::endl;}
		syncout::print(std::cout,sos);
	}
#endif
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::compute_theta()
{
	assert(_classes>0);
	assert(_n>0);
	assert(_d>0 || _d<=_n);
	assert(_index);
	assert(_allpatterns>0);
	assert(_Nsuminclass);
	assert(_theta);
	DATATYPE total_sum_length=0;
	DIMTYPE wCV=0, wCd=0;
	for(DIMTYPE c=0;c<_classes;c++) {
		DATATYPE class_sum_length=0;
		for(DIMTYPE f=0;f<_d;f++) class_sum_length+=_Nsuminclass[wCV+_index[f]]; // in subset case this is done only for selected features
		total_sum_length+=class_sum_length;
		for(DIMTYPE f=0;f<_d;f++) { // in subset case this is done only for selected features
			_theta[wCd++]=(1.0+(REALTYPE)_Nsuminclass[wCV+_index[f]])/((REALTYPE)_d+(REALTYPE)class_sum_length); // in subset case substitute d (subset size) for _n
		}
		wCV+=_n;
	}
	if(_allpatterns==0) throw FST::fst_error("Model_Multinomial::compute_theta: division by zero _allpatterns");
	_doc_avg_length=(REALTYPE)total_sum_length/(REALTYPE)_allpatterns;
#ifdef DEBUG
	{
		std::ostringstream sos; sos <<"_theta:"<<std::endl;
		for(DIMTYPE c=0;c<_classes;c++) {
			REALTYPE tmp=0.0;
			for(DIMTYPE f=0;f<_d;f++) {tmp+=_theta[c*_d+f]; sos << _theta[c*_d+f] << " "; sos << std::endl;}
			sos << "theta sum over class<"<<c<<">: "<<tmp<<std::endl;
		}
		sos << "total_sum_length="<<total_sum_length<<", doc_avg_length="<<_doc_avg_length<<std::endl;
		syncout::print(std::cout,sos);
	}
#endif
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::narrow_to(const PSubset sub)
{
	assert(_index);
	assert(sub);
	assert(sub->get_n_raw()==_n); // NOTE: sub should represent subspace of the learned subspace ! not of the full space
	assert(sub->get_d_raw()>0);
	DIMTYPE f;
	_d=0;
	for(bool b=sub->getFirstFeature(f);b!=false;b=sub->getNextFeature(f)) _index[_d++]=f;
	assert(_d>0 && _d<=_n);
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::denarrow()
{
	assert(_index);
	for(DIMTYPE i=0;i<_n;i++) _index[i]=i;
	_d=_n;
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::compute_MI()
{
	// NOTE: needs _theta computed for full feature set
	assert(_classes>0);
	assert(_n>0);
	assert(_Nsuminclass);
	assert(_theta);
	assert(_Pc);
	assert(_Pv);
	assert(_MI);
	for(DIMTYPE f=0;f<_n;f++) _MI[f]=0.0;
	DIMTYPE wCV=0;
	DATATYPE sum=0;
	for(DIMTYPE c=0;c<_classes;c++) {
		for(DIMTYPE f=0;f<_n;f++) {
			_MI[f]+=_Nsuminclass[wCV+f];
			sum+=_Nsuminclass[wCV+f];
		}
		wCV+=_n;
	}
	for(DIMTYPE f=0;f<_n;f++) {if(sum>0.0) _Pv[f]=(_MI[f])/((REALTYPE)sum); else _Pv[f]=0.0;}

	for(DIMTYPE f=0;f<_n;f++) {
		_MI[f]=0.0;
		if(_Pv[f]>0.0) {
			wCV=0;
			for(DIMTYPE c=0;c<_classes;c++) {
				//_MI[f]+=_theta[wCV+f]*_Pc_d[c]*fabs( (REALTYPE)log(_theta[wCV+f]/_Pv[f]) ); // IG abs
				//_MI[f]+=_theta[wCV+f]*_Pc_d[c]*(REALTYPE)log(_theta[wCV+f]/_Pv[f]); // IG
				//_MI[f]+=_Pc_d[c]*fabs( (REALTYPE)log(_theta[wCV+f]/_Pv[f]) ); // MI abs
				_MI[f]+=_Pc_d[c]*(REALTYPE)log(_theta[wCV+f]/_Pv[f]); // MI
				wCV+=_n;
			}
		}
	}
#ifdef DEBUG
	{
		std::ostringstream sos; sos <<"_MI:"<<std::endl;
		for(DIMTYPE f=0;f<_n;f++) sos << _MI[f] << " "; sos << std::endl;
		syncout::print(std::cout,sos);
	}
#endif
}

template<typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
void Model_Multinomial<DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::compute_IB()
{
	// NOTE: needs _theta computed for full feature set
	assert(_classes>0);
	assert(_n>0);
	assert(_doc_avg_length>0);
	assert(_theta);
	assert(_Pc);
	assert(_IB);
	for(DIMTYPE f=0;f<_n;f++) _IB[f]=0.0;
	DIMTYPE wCV1,wCV2;
	DIMTYPE combs;
	REALTYPE _thetasum, value;
	for(DIMTYPE f=0;f<_n;f++) {
		value=0.0; combs=0;
		wCV1=0;
		for(DIMTYPE c1=0;c1<_classes;c1++) {
			wCV2=wCV1+_n;
			for(DIMTYPE c2=c1+1;c2<_classes;c2++) {
				_thetasum=sqrt(_theta[wCV1+f]*_theta[wCV2+f])+sqrt((1.0-_theta[wCV1+f])*(1.0-_theta[wCV2+f]));
#ifdef DEBUG
				{std::ostringstream sos; sos << _thetasum<< std::endl; syncout::print(std::cout,sos);}
#endif
				value+=((-_doc_avg_length)*(REALTYPE)log(_thetasum))*_Pc_d[c1]*_Pc_d[c2];
				combs++;
				wCV2+=_n;
			}
			wCV1+=_n;
		}
		assert(combs>0);
		if(combs==0) throw FST::fst_error("Model_Multinomial::compute_IB: division by zero combs");
		_IB[f]=value/(REALTYPE)combs;
	}
#ifdef DEBUG
	{
		std::ostringstream sos; sos <<"_IB:"<<std::endl;
		for(DIMTYPE f=0;f<_n;f++) sos << _IB[f] << " "; sos << std::endl;
		syncout::print(std::cout,sos);
	}
#endif
}

} // namespace
#endif // FSTMODELMULTINOM_H ///:~


#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/matrix.hpp>

#include <cmath>
#include <memory>
#include <vector>
#include <limits>
#include <algorithm>
#include <utility>
#include <iterator>

#include "statespace.hpp"

using namespace boost::numeric::ublas;

CompactSupportSSM::CompactSupportSSM(\
       const vector<int> & start_idxs, const vector <int > & end_idxs,
       const vector<int> & identities, const matrix<double> & basis_prototypes,
       const vector<double> & coef_means,  const vector<double> & coef_vars,
       double obs_noise, double bias) : start_idxs(start_idxs), end_idxs(end_idxs),
					identities(identities), basis_prototypes(basis_prototypes),
					coef_means(coef_means), coef_vars(coef_vars),
					obs_noise(obs_noise), bias(bias) {

  this->is_cssm = true;
  this->n_basis = start_idxs.size();
  this->n_steps = *(std::max_element(end_idxs.begin(), end_idxs.end()));

  if (coef_means.size() != this->n_basis) {
    printf("ERROR: coef prior mean length must match size of basis\n");
    exit(-1);
  }
  if (coef_vars.size() != this->n_basis) {
    printf("ERROR: coef prior vars length must match size of basis\n");
    exit(-1);
  }

  std::vector< std::vector<int> > tmp_active_basis(this->n_steps);

  this->active_indices.set_empty_key(std::make_pair(-1,-1) );

  unsigned int i;
  vector<int>::const_iterator st;
  vector<int>::const_iterator et;
  for( i = 0, st = this->start_idxs.begin(), et = this->end_idxs.begin() ;
       i < this->n_basis && st < this->start_idxs.end() && et < this->end_idxs.end();
       ++i, ++st, ++et ) {

    for (unsigned j= std::max(*st, 0); j < std::min((unsigned int)*et, this->n_steps); ++j) {
      tmp_active_basis[j].push_back(i);
      (this->active_indices)[std::make_pair(i,j)] = int(tmp_active_basis[j].size()-1);
    }
  }

  int max_dimension = 0;
  for (unsigned i=0; i < this->n_steps; ++i) {
    max_dimension = std::max(max_dimension, int(tmp_active_basis[i].size()));
  }
  this->max_dimension = max_dimension;

  this->active_basis = matrix<int>(this->n_steps, max_dimension);
  for (unsigned i=0; i < this->n_steps; ++i) {
    int j = 0;
    for(j=0; j < int(tmp_active_basis[i].size()); ++j) {
      this->active_basis(i,j) = tmp_active_basis[i][j];
    }
    if (j < max_dimension) {
      this->active_basis(i,j) = -1;
    }
  }

  // printf("identities len %d\n", this->identities.size());

}

CompactSupportSSM::~CompactSupportSSM() {
  return;
};

int CompactSupportSSM::apply_transition_matrix( const double * x, int k, double * result) {
  /* compute F_k*x for the given x, where
     F_k is the transition matrix from time
     k-1 to k. The result is stored in
     the provided vector x_new. If x_new
     has extra dimensions, they are not
     touched (so may still contain garbage).*/

  if (k <= 0) {
    printf("error: applying CSSM transition at invalid index %d.\n", k);
    exit(-1);
  }

  for (int i=0; i < this->max_dimension; ++i) {
    result[i] = 0;
  }

  if (k >= this->n_steps) {
    return 0;
  }

  const matrix_row< matrix<int> > active = row(this->active_basis, k);
  matrix_row< matrix<int> >::const_iterator idx;
  int i = 0;
  for (i=0, idx = active.begin();
       idx < active.end() && *idx >= 0;
       ++i, ++idx) {
    // if this basis is new at this timestep, initialize to zero.
    // otherwise, use its previous value.
    std::pair<int,int> key = std::make_pair(int(*idx), k-1);
    dense_hash_map< std::pair<int, int>, int, boost::hash< std::pair< int,int> >  >::iterator contains = this->active_indices.find(key);
    if (contains == this->active_indices.end()) {
      result[i] = 0;
      D(printf("transition k %d basis %d idx %d prev_idx %d x_new[i] %.4f\n", k, *idx, i, -1, 0.0);)
    } else {
      int prev_idx = this->active_indices[key];
      result[i] =x[prev_idx];
      D(printf("transition k %d basis %d idx %d prev_idx %d x_new[i] %.4f\n", k, *idx, i, prev_idx, x[prev_idx]);)
    }
  }
  return i;
}

/* Each column of the input X gives a column of the result matrix */
int CompactSupportSSM::apply_transition_matrix( const matrix<double> &X,
						unsigned int x_row_offset,
						int k,
						matrix<double> &result,
						unsigned int r_row_offset,
						unsigned int n) {
  if (k <= 0) {
    printf("error: applying CSSM transition at invalid index %d.\n", k);
    exit(-1);
  }

  for (int i=r_row_offset; i < r_row_offset+this->max_dimension; ++i) {
    for (int j =0; j < n; ++j) {
      result(i,j) = 0;
    }
  }

  if (k >= this->n_steps) {
    return 0;
  }

  const matrix_row< matrix<int> > active = row(this->active_basis, k);
  matrix_row< matrix<int> >::const_iterator idx;
  unsigned i = r_row_offset;

  for (i=r_row_offset, idx = active.begin();
       idx < active.end() && *idx >= 0;
       ++i, ++idx) {

    // if this basis is new at this timestep, initialize to zero.
    // otherwise, use its previous value.
    std::pair<int,int> key = std::make_pair(int(*idx), k-1);
    dense_hash_map< std::pair<int, int>, int, boost::hash< std::pair< int,int> >  >::iterator contains = this->active_indices.find(key);

    if (contains == this->active_indices.end()) {
      for (unsigned j=0; j < n; ++j) result(i, j) = 0;
      // printf("MATR0 transition k %d i %d idx %d prev_idx %d x_new[i,i] %.4f\n", k, i, *idx, -1, 0.0);
    } else {
      int prev_idx = this->active_indices[key] + x_row_offset;
      for (unsigned j=0; j < n; ++j) result(i, j) = X(prev_idx, j);
      //printf("MATR1 transition k %d i %d idx %d prev_idx %d x_new[i, i] %.4f\n", k, i, *idx, prev_idx, X(prev_idx, prev_idx));
    }
  }
  return i-r_row_offset;
}

void CompactSupportSSM::transition_bias(int k, double * result) {
  if (k >= this->n_steps) {
    return;
  }

  const matrix_row< matrix<int> > active = row(this->active_basis, k);
  matrix_row< matrix<int> >::const_iterator idx;
  int i = 0;
  for (i=0, idx = active.begin();
       idx < active.end() && *idx >= 0;
       ++i, ++idx) {

    // if this basis is new at this timestep, initialize to zero.
    // otherwise, use its previous value.
    std::pair<int,int> key = std::make_pair(*idx, k-1);
    dense_hash_map< std::pair<int, int>, int, boost::hash< std::pair< int,int> >  >::iterator contains = this->active_indices.find(key);
    if (contains == this->active_indices.end()) {
      result[i] += this->coef_means(*idx);
    }
  }
}

void CompactSupportSSM::transition_noise_diag(int k, double * result) {
  if (k >= this->n_steps) {
    return;
  }

  unsigned int i = 0;
  for (i; i < this->max_dimension; ++i) {
    result[i] = 0.0;
  }

  const matrix_row< matrix<int> > active = row(this->active_basis, k);
  matrix_row< matrix<int> >::const_iterator idx;
  for (i=0, idx = active.begin();
       idx < active.end() && *idx >= 0;
       ++i, ++idx) {
    std::pair<int,int> key = std::make_pair(*idx, k-1);
    dense_hash_map< std::pair<int, int>, int, boost::hash< std::pair< int,int> >  >::iterator contains = this->active_indices.find(key);
    if (contains == this->active_indices.end()) {
      result[i] = this->coef_vars(*idx);
      //D(printf("time %d instantiating coef %d into state %d with noise variance %f\n", k, *idx, i, result[i]);)
    } else {
      result[i] = 0.0;
    }
  }
}

double CompactSupportSSM::apply_observation_matrix(const double * x, int k) {
  // const matrix_row< matrix<int> > active = row(this->active_basis, k);

  double result = 0;

  if (k >= this->n_steps) {
    return 0;
  }

  matrix_row< matrix<int> >::const_iterator idx;
  unsigned int i = 0;
  for (i=0; i < this->active_basis.size2() && this->active_basis(k, i) >= 0; ++i) {
    int basis = this->active_basis(k, i);

    int prototype = (this->identities)(basis);
    int st = (this->start_idxs)(basis);

    result += x[i] * (this->basis_prototypes)(prototype, k-st);
  }

  return result;
}

void CompactSupportSSM::apply_observation_matrix(const matrix<double> &X,
						 unsigned int row_offset, int k,
						 double *result, double *result_tmp, unsigned int n) {
  //const matrix_row< matrix<int> > active = row(this->active_basis, k);
  //matrix_row< matrix<int> >::const_iterator idx;

  for (unsigned j=0; j < n; ++j) {
    result[j] = 0;
  }

  if (k >= this->n_steps) {
    return;
  }

  unsigned int i = 0;
  for (i=0; i < this->active_basis.size2() && this->active_basis(k, i) >= 0; ++i) {
    int basis = this->active_basis(k, i);

    int prototype = this->identities(basis);
    int st = this->start_idxs(basis);

    for (unsigned j=0; j < n; ++j) {
      result[j] += X(row_offset+i,j) * this->basis_prototypes(prototype, k-st);
    }
  }
}

double CompactSupportSSM::observation_bias(int k) {
  return this->bias;
}

double CompactSupportSSM::observation_noise(int k) {
  return this->obs_noise;
}

bool CompactSupportSSM::stationary(int k) {
  return k >= this->n_steps;
}

int CompactSupportSSM::prior_mean(double *result) {
  const matrix_row< matrix<int> > active = row(this->active_basis, 0);
  matrix_row< matrix<int> >::const_iterator idx;
  int i = 0;

  for (i=0, idx = active.begin();
       idx < active.end() && *idx >= 0;
       ++i, ++idx) {
    result[i] = this->coef_means(*idx);
  }
  for (unsigned ii=i; ii < this->max_dimension; ++ii) {
    result[ii] = 0.0;
  }
  return i;
}

int CompactSupportSSM::prior_vars(double *result) {
  const matrix_row< matrix<int> > active = row(this->active_basis, 0);
  matrix_row< matrix<int> >::const_iterator idx;
  int i = 0;
  for (i=0, idx = active.begin();
       idx < active.end() && *idx >= 0;
       ++i, ++idx) {
    result[i] = this->coef_vars(*idx);
  }
  for (unsigned ii=i; ii < this->max_dimension; ++ii) {
    result[ii] = 0.0;
  }

  return i;
}

void CompactSupportSSM::extract_coefs(const vector<double> &x,
				      const matrix<double> &P,
				      unsigned int state_offset,
				      int k,
				      vector<double> & coef_means,
				      vector<double> & coef_vars) {

  if (k >= this->n_steps) {
    return;
  }

  /* given a state estimate at some time k, extract marginals for
   * whatever coefficients we can reasonably do so for at the
   * current time. Earlier estimates will always be overwritten
   * by later ones. */
  const matrix_row< matrix<int> > active = row(this->active_basis, k);
  matrix_row< matrix<int> >::const_iterator idx;
  unsigned int i;

  for (i=0, idx = active.begin();
       idx < active.end() && *idx >= 0;
       ++i, ++idx) {
    int basis_idx = *idx;
    std::pair<int,int> key = std::make_pair(basis_idx, k);

    dense_hash_map< std::pair<int, int>, int, boost::hash< std::pair< int,int> >  >::iterator contains = this->active_indices.find(key);
    if (contains == this->active_indices.end()) {
      printf("\nWAAT, no key found for basis %d\n", basis_idx);
      exit(-1);
    } else {
      int state_idx = this->active_indices[key] + state_offset;
      coef_means(basis_idx) = x(state_idx);
      coef_vars(basis_idx) = P(state_idx,state_idx);

      //printf("extracted coef %d var %f from state %d at cssm-local time %d\n", basis_idx, coef_vars(basis_idx), state_idx, k);
      /*if (coef_vars(basis_idx) == 0) {
	printf("WARNING: posterior coef var is zero for basis %d at state %d at time %d, this doesn't seem right....\n", basis_idx, state_idx, k);
	//exit(-1);
	}*/
    }
  }
}


///////////////////////////////////////////////////////////////
//  Copyright 2012-21 John Maddock.
//  Copyright 2021 Iskandarov Lev. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_MP_INTEGER_HPP
#define BOOST_MP_INTEGER_HPP

#include <type_traits>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/detail/bitscan.hpp>
#include <boost/multiprecision/detail/no_exceptions_support.hpp>
#include <boost/multiprecision/detail/standalone_config.hpp>

namespace boost {
namespace multiprecision {

template <class Integer, class I2>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value && boost::multiprecision::detail::is_integral<I2>::value, Integer&>::type
multiply(Integer& result, const I2& a, const I2& b)
{
   return result = static_cast<Integer>(a) * static_cast<Integer>(b);
}
template <class Integer, class I2>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value && boost::multiprecision::detail::is_integral<I2>::value, Integer&>::type
add(Integer& result, const I2& a, const I2& b)
{
   return result = static_cast<Integer>(a) + static_cast<Integer>(b);
}
template <class Integer, class I2>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value && boost::multiprecision::detail::is_integral<I2>::value, Integer&>::type
subtract(Integer& result, const I2& a, const I2& b)
{
   return result = static_cast<Integer>(a) - static_cast<Integer>(b);
}

template <class Integer>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value>::type divide_qr(const Integer& x, const Integer& y, Integer& q, Integer& r)
{
   q = x / y;
   r = x % y;
}

template <class I1, class I2>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<I1>::value && boost::multiprecision::detail::is_integral<I2>::value, I2>::type integer_modulus(const I1& x, I2 val)
{
   return static_cast<I2>(x % val);
}

namespace detail {
//
// Figure out the kind of integer that has twice as many bits as some builtin
// integer type I.  Use a native type if we can (including types which may not
// be recognised by boost::int_t because they're larger than long long),
// otherwise synthesize a cpp_int to do the job.
//
template <class I>
struct double_integer
{
   static constexpr const unsigned int_t_digits =
       2 * sizeof(I) <= sizeof(long long) ? std::numeric_limits<I>::digits * 2 : 1;

   using type = typename std::conditional<
       2 * sizeof(I) <= sizeof(long long),
       typename std::conditional<
           boost::multiprecision::detail::is_signed<I>::value && boost::multiprecision::detail::is_integral<I>::value,
           typename boost::multiprecision::detail::int_t<int_t_digits>::least,
           typename boost::multiprecision::detail::uint_t<int_t_digits>::least>::type,
       typename std::conditional<
           2 * sizeof(I) <= sizeof(double_limb_type),
           typename std::conditional<
               boost::multiprecision::detail::is_signed<I>::value && boost::multiprecision::detail::is_integral<I>::value,
               signed_double_limb_type,
               double_limb_type>::type,
           number<cpp_int_backend<sizeof(I) * CHAR_BIT * 2, sizeof(I) * CHAR_BIT * 2, (boost::multiprecision::detail::is_signed<I>::value ? signed_magnitude : unsigned_magnitude), unchecked, void> > >::type>::type;
};

} // namespace detail

template <class I1, class I2, class I3>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<I1>::value && boost::multiprecision::detail::is_unsigned<I2>::value && boost::multiprecision::detail::is_integral<I3>::value, I1>::type
powm(const I1& a, I2 b, I3 c)
{
   using double_type = typename detail::double_integer<I1>::type;

   I1          x(1), y(a);
   double_type result(0);

   while (b > 0)
   {
      if (b & 1)
      {
         multiply(result, x, y);
         x = integer_modulus(result, c);
      }
      multiply(result, y, y);
      y = integer_modulus(result, c);
      b >>= 1;
   }
   return x % c;
}

template <class I1, class I2, class I3>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<I1>::value && boost::multiprecision::detail::is_signed<I2>::value && boost::multiprecision::detail::is_integral<I2>::value && boost::multiprecision::detail::is_integral<I3>::value, I1>::type
powm(const I1& a, I2 b, I3 c)
{
   if (b < 0)
   {
      BOOST_MP_THROW_EXCEPTION(std::runtime_error("powm requires a positive exponent."));
   }
   return powm(a, static_cast<typename boost::multiprecision::detail::make_unsigned<I2>::type>(b), c);
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, std::size_t>::type lsb(const Integer& val)
{
   if (val <= 0)
   {
      if (val == 0)
      {
         BOOST_MP_THROW_EXCEPTION(std::domain_error("No bits were set in the operand."));
      }
      else
      {
         BOOST_MP_THROW_EXCEPTION(std::domain_error("Testing individual bits in negative values is not supported - results are undefined."));
      }
   }
   return detail::find_lsb(val);
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, std::size_t>::type msb(Integer val)
{
   if (val <= 0)
   {
      if (val == 0)
      {
         BOOST_MP_THROW_EXCEPTION(std::domain_error("No bits were set in the operand."));
      }
      else
      {
         BOOST_MP_THROW_EXCEPTION(std::domain_error("Testing individual bits in negative values is not supported - results are undefined."));
      }
   }
   return detail::find_msb(val);
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, bool>::type bit_test(const Integer& val, std::size_t index)
{
   Integer mask = 1;
   if (index >= sizeof(Integer) * CHAR_BIT)
      return 0;
   if (index)
      mask <<= index;
   return val & mask ? true : false;
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, Integer&>::type bit_set(Integer& val, std::size_t index)
{
   Integer mask = 1;
   if (index >= sizeof(Integer) * CHAR_BIT)
      return val;
   if (index)
      mask <<= index;
   val |= mask;
   return val;
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, Integer&>::type bit_unset(Integer& val, std::size_t index)
{
   Integer mask = 1;
   if (index >= sizeof(Integer) * CHAR_BIT)
      return val;
   if (index)
      mask <<= index;
   val &= ~mask;
   return val;
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, Integer&>::type bit_flip(Integer& val, std::size_t index)
{
   Integer mask = 1;
   if (index >= sizeof(Integer) * CHAR_BIT)
      return val;
   if (index)
      mask <<= index;
   val ^= mask;
   return val;
}

namespace detail {

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR Integer karatsuba_sqrt(const Integer& x, Integer& r, size_t bits)
{
   //
   // Define the floating point type used for std::sqrt, in our tests, sqrt(double) and sqrt(long double) take
   // about the same amount of time as long as long double is not an emulated 128-bit type (ie the same type
   // as __float128 from libquadmath).  So only use long double if it's an 80-bit type:
   //
#ifndef __clang__
   typedef typename std::conditional<(std::numeric_limits<long double>::digits == 64), long double, double>::type real_cast_type;
#else
   // clang has buggy __int128 -> long double conversion:
   typedef double real_cast_type;
#endif
   //
   // As per the Karatsuba sqrt algorithm, the low order bits/4 bits pay no part in the result, only in the remainder,
   // so define the number of bits our argument must have before passing to std::sqrt is safe, even if doing so
   // looses a few bits:
   //
   constexpr std::size_t cutoff = (std::numeric_limits<real_cast_type>::digits * 4) / 3;
   //
   // Type which can hold at least "cutoff" bits:
   //
#ifdef BOOST_HAS_INT128
   using cutoff_t = typename std::conditional<(cutoff > 64), uint128_type, std::uint64_t>::type;
#else
   using cutoff_t = std::uint64_t;
#endif
   //
   // See if we can take the fast path:
   //
   if (bits <= cutoff)
   {
      constexpr cutoff_t half_bits = (cutoff_t(1u) << ((sizeof(cutoff_t) * CHAR_BIT) / 2)) - 1;
      cutoff_t       val = static_cast<cutoff_t>(x);
      real_cast_type real_val = static_cast<real_cast_type>(val);
      cutoff_t       s64 = static_cast<cutoff_t>(std::sqrt(real_val));
      // converting to long double can loose some precision, and `sqrt` can give eps error, so we'll fix this
      // this is needed
      while ((s64 > half_bits) || (s64 * s64 > val))
         s64--;
      // in my tests this never fired, but theoretically this might be needed
      while ((s64 < half_bits) && ((s64 + 1) * (s64 + 1) <= val))
         s64++;
      r = static_cast<Integer>(val - s64 * s64);
      return static_cast<Integer>(s64);
   }
   // https://hal.inria.fr/file/index/docid/72854/filename/RR-3805.pdf
   std::size_t b = bits / 4;
   Integer q = x;
   q >>= b * 2;
   Integer s = karatsuba_sqrt(q, r, bits - b * 2);
   Integer t = 0u;
   bit_set(t, static_cast<unsigned>(b * 2));
   r <<= b;
   t--;
   t &= x;
   t >>= b;
   t += r;
   s <<= 1;
   divide_qr(t, s, q, r);
   r <<= b;
   t = 0u;
   bit_set(t, static_cast<unsigned>(b));
   t--;
   t &= x;
   r += t;
   s <<= (b - 1); // we already <<1 it before
   s += q;
   q *= q;
   // we substract after, so it works for unsigned integers too
   if (r < q)
   {
      t = s;
      t <<= 1;
      t--;
      r += t;
      s--;
   }
   r -= q;
   return s;
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR Integer bitwise_sqrt(const Integer& x, Integer& r)
{
   //
   // This is slow bit-by-bit integer square root, see for example
   // http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29
   // There are better methods such as http://hal.inria.fr/docs/00/07/28/54/PDF/RR-3805.pdf
   // and http://hal.inria.fr/docs/00/07/21/13/PDF/RR-4475.pdf which should be implemented
   // at some point.
   //
   Integer s = 0;
   if (x == 0)
   {
      r = 0;
      return s;
   }
   std::ptrdiff_t g = msb(x);
   if (g == 0)
   {
      r = 1;
      return s;
   }

   Integer t = 0;
   r = x;
   g /= 2;
   bit_set(s, g);
   bit_set(t, 2 * g);
   r = x - t;
   --g;
   do
   {
      t = s;
      t <<= g + 1;
      bit_set(t, 2 * g);
      if (t <= r)
      {
         bit_set(s, g);
         r -= t;
      }
      --g;
   } while (g >= 0);
   return s;
}

} // namespace detail

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, Integer>::type sqrt(const Integer& x, Integer& r)
{
#ifndef BOOST_MP_NO_CONSTEXPR_DETECTION
   // recursive Karatsuba sqrt can cause issues in constexpr context:
   if (BOOST_MP_IS_CONST_EVALUATED(x))
      return detail::bitwise_sqrt(x, r);
#endif
   if (x == 0u) {
      r = 0u;
      return 0u;
   }

   return detail::karatsuba_sqrt(x, r, msb(x) + 1);
}

template <class Integer>
BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_integral<Integer>::value, Integer>::type sqrt(const Integer& x)
{
   Integer r(0);
   return sqrt(x, r);
}

}} // namespace boost::multiprecision

#endif


/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "itkNumericTraits.h"

#include "otbSailModel.h"
#include "otb_boost_expint_header.h"
#include <boost/shared_ptr.hpp>
#include "otbMath.h"

//TODO check EPSILON matlab
#define EPSILON 0.0000000000000000000000001

namespace otb
{

/** Constructor */
SailModel
::SailModel() : m_LAI(2), m_Angl(50), m_PSoil(1), m_Skyl(70), m_HSpot(0.2),
                m_TTS(30), m_TTO(0), m_PSI(0), m_FCoverView(0.0), m_UseSoilFile(false), m_SoilIndex(0)
{
  this->ProcessObject::SetNumberOfRequiredInputs(2);
  this->ProcessObject::SetNumberOfRequiredOutputs(4);

  SpectralResponseType::Pointer vRefl = static_cast<SpectralResponseType *>(this->MakeOutput(0).GetPointer());
  this->itk::ProcessObject::SetNthOutput(0, vRefl.GetPointer());

  SpectralResponseType::Pointer hRefl = static_cast<SpectralResponseType *>(this->MakeOutput(1).GetPointer());
  this->itk::ProcessObject::SetNthOutput(1, hRefl.GetPointer());

  SpectralResponseType::Pointer vAbs = static_cast<SpectralResponseType *>(this->MakeOutput(2).GetPointer());
  this->itk::ProcessObject::SetNthOutput(2, vAbs.GetPointer());

  SpectralResponseType::Pointer hAbs = static_cast<SpectralResponseType *>(this->MakeOutput(3).GetPointer());
  this->itk::ProcessObject::SetNthOutput(3, hAbs.GetPointer());
}

/** Destructor */
SailModel
::~SailModel()
{}

/** Set/Get input reflectance */
void
SailModel
::SetReflectance(const SpectralResponseType * object)
{
  this->itk::ProcessObject::SetNthInput(0, const_cast<SpectralResponseType *>(object));
}

SailModel::SpectralResponseType *
SailModel
::GetReflectance()
                  {
                    if(this->GetNumberOfInputs() != 2)
                      {
                      //exit
                      return nullptr;
                      }
                    return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetInput(0));
                  }

/** Set/Get input transmittance */
                  void
                  SailModel
                  ::SetTransmittance(const SpectralResponseType * object)
                  {
                    this->itk::ProcessObject::SetNthInput(1, const_cast<SpectralResponseType *>(object));
                  }

                  SailModel::SpectralResponseType *
                  SailModel
                  ::GetTransmittance()
                  {
                    if(this->GetNumberOfInputs() != 2)
                      {
                      //exit
                      return nullptr;
                      }
                    return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetInput(1));
                  }

/** Make output */
                  SailModel::DataObjectPointer
                  SailModel
                  ::MakeOutput(DataObjectPointerArraySizeType)
                  {
                    return static_cast<itk::DataObject*>(SpectralResponseType::New().GetPointer());
                  }

/** Get output viewing reflectance */
                  SailModel::SpectralResponseType *
                  SailModel
                  ::GetViewingReflectance()
                  {
                    if(this->GetNumberOfOutputs() < 4)
                      {
                      //exit
                      return nullptr;
                      }
                    return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetOutput(0));
                  }

/** Get output hemispherical reflectance */
                  SailModel::SpectralResponseType *
                  SailModel
                  ::GetHemisphericalReflectance()
                  {
                    if(this->GetNumberOfOutputs() < 4)
                      {
                      //exit
                      return nullptr;
                      }
                    return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetOutput(1));
                  }

/** Get output viewing absorptance */
                  SailModel::SpectralResponseType *
                  SailModel
                  ::GetViewingAbsorptance()
                  {
                    if(this->GetNumberOfOutputs() < 4)
                      {
                      //exit
                      return nullptr;
                      }
                    return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetOutput(2));
                  }

/** Get output hemispherical absorptance */
                  SailModel::SpectralResponseType *
                  SailModel
                  ::GetHemisphericalAbsorptance()
                  {
                    if(this->GetNumberOfOutputs() < 4)
                      {
                      //exit
                      return nullptr;
                      }
                    return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetOutput(3));
                  }

/** Set Parameters */
                  void
                  SailModel
                  ::SetInput(const ParametersType & params)
                  {

                    if(params.Size()!=8) itkExceptionMacro( << "Must have 8 parameters in that order : LAI, Angl, PSoil, Skyl, HSpot, TTS, TTO, PSI" );
                    this->SetParameters(params);
                    m_LAI=params[0];
                    m_Angl=params[1];
   m_PSoil=params[2];
   m_Skyl=params[3];
   m_HSpot=params[4];
   m_TTS=params[5];
   m_TTO=params[6];
   m_PSI=params[7];
}

/** Get Parameters */
const SailModel::ParametersType
SailModel
::GetInput()
{
  ParametersType parameters=this->GetParameters();
   if(parameters.Size()!=8)
   {
      parameters[0]=m_LAI;
      parameters[1]=m_Angl;
      parameters[2]=m_PSoil;
      parameters[3]=m_Skyl;
      parameters[4]=m_HSpot;
      parameters[5]=m_TTS;
      parameters[6]=m_TTO;
      parameters[7]=m_PSI;
      this->SetParameters(parameters);
   }
   return this->GetParameters();
}

/** Generate data */
void
SailModel
::GenerateData()
{

   SpectralResponseType::Pointer inRefl = this->GetReflectance();
   SpectralResponseType::Pointer inTrans = this->GetTransmittance();
   SpectralResponseType::Pointer outVRefl = this->GetViewingReflectance();
   SpectralResponseType::Pointer outHRefl = this->GetHemisphericalReflectance();
   SpectralResponseType::Pointer outVAbs = this->GetViewingAbsorptance();
   SpectralResponseType::Pointer outHAbs = this->GetHemisphericalAbsorptance();

   // LEAF ANGLE DISTRIBUTION
   double rd = CONST_PI/180;
   VectorType lidf;
   this->Calc_LIDF(m_Angl, lidf);

   double cts, cto, ctscto, tants, tanto, cospsi, dso;
   cts = std::cos(rd*m_TTS);
   cto = std::cos(rd*m_TTO);
   ctscto = cts*cto;
   tants = std::tan(rd*m_TTS);
   tanto = std::tan(rd*m_TTO);
   cospsi = std::cos(rd*m_PSI);
   dso = std::sqrt(tants*tants+tanto*tanto-2.*tants*tanto*cospsi);

   // angular distance, compensation of shadow length
   // Calculate geometric factors associated with extinction and scattering
   // Initialise sums
   double ks = 0;
   double ko = 0;
   double bf = 0;
   double sob = 0;
   double sof = 0;
   double ttl, ctl, ksli, koli, sobli, sofli, bfli;
   double chi_s, chi_o, frho, ftau;
   VectorType result(4);

   // Weighted sums over LIDF
   for(unsigned int i=0; i<lidf.size(); ++i)
   {
      ttl = 2.5+5*i;       // leaf inclination discrete values
      ctl = std::cos(rd*ttl);
      // SAIL volume scattering phase function gives interception and portions to be
      // multiplied by rho and tau

      this->Volscatt(m_TTS, m_TTO, m_PSI, ttl, result);
      chi_s = result[0];
      chi_o = result[1];
      frho = result[2];
      ftau = result[3];

      //********************************************************************************
      //*                   SUITS SYSTEM COEFFICIENTS
      //*
      //*       ks  : Extinction coefficient for direct solar flux
      //*       ko  : Extinction coefficient for direct observed flux
      //*       att : Attenuation coefficient for diffuse flux
      //*       sigb : Backscattering coefficient of the diffuse downward flux
      //*       sigf : Forwardscattering coefficient of the diffuse upward flux
      //*       sf  : Scattering coefficient of the direct solar flux for downward diffuse flux
      //*       sb  : Scattering coefficient of the direct solar flux for upward diffuse flux
      //*       vf   : Scattering coefficient of upward diffuse flux in the observed direction
      //*       vb   : Scattering coefficient of downward diffuse flux in the observed direction
      //*       w   : Bidirectional scattering coefficient
      //********************************************************************************

      // Extinction coefficients
      ksli = chi_s/cts;
      koli = chi_o/cto;

      // Area scattering coefficient fractions
      sobli       = frho*CONST_PI/ctscto;
      sofli       = ftau*CONST_PI/ctscto;
      bfli       = ctl*ctl;
      ks       = ks+ksli*lidf[i];
      ko       = ko+koli*lidf[i];
      bf       = bf+bfli*lidf[i];
      sob       = sob+sobli*lidf[i];
      sof       = sof+sofli*lidf[i];
   }

   // Geometric factors to be used later with rho and tau
   double sdb, sdf, dob, dof, ddb, ddf;
   sdb       = 0.5*(ks+bf);
   sdf       = 0.5*(ks-bf);
   dob       = 0.5*(ko+bf);
   dof       = 0.5*(ko-bf);
   ddb       = 0.5*(1.+bf);
   ddf       = 0.5*(1.-bf);

   double lambda, Es, Ed, Rsoil1, Rsoil2, rsoil0, rho, tau, PARdiro, PARdifo;
   double sigb, sigf, att, m2, m, sb, sf, vb, vf, w;
   double tss, too, tsstoo, rdd, tdd, rsd, tsd, rdo, tdo, rsos, rsod;
   double rddt, rsdt, rdot, rsodt, rsost, rsot, dn;
   double e1, e2, rinf, rinf2, re, denom, J1ks, J2ks, J1ko, J2ko;
   double Ps, Qs, Pv, Qv, z, g1, g2, Tv1, Tv2, T1, T2, T3;
   double alf, sumint, fhot, x1, y1, f1, fint, x2, y2, f2;
   double resh, resv, absh, absv;

   int nbdata = sizeof(DataSpecP5B) / sizeof(DataSpec);
   for (int i = 0; i < nbdata; ++i)
   {
      lambda = DataSpecP5B[i].lambda;
      Es = DataSpecP5B[i].directLight; //8
      Ed = DataSpecP5B[i].diffuseLight; //9
      Rsoil1 = DataSpecP5B[i].drySoil; //10
      Rsoil2 = DataSpecP5B[i].wetSoil; //11
      rho = inRefl->GetResponse()[i].second; //rho = LRT[1][i];
      tau = inTrans->GetResponse()[i].second; //tau = LRT[2][i];

      // direct/diffuse light
      //Es = direct
      //Ed = diffuse
      PARdiro = (1-m_Skyl/100.)*Es;
      PARdifo = (m_Skyl/100.)*Ed;

      // Soil Reflectance Properties
      //rsoil1 = dry soil
      //rsoil2 = wet soil
      if(!m_UseSoilFile)
        {
        rsoil0 = m_PSoil*Rsoil1+(1-m_PSoil)*Rsoil2;
        }
      else
        {
        rsoil0 = m_SoilDataBase->GetReflectance(m_SoilIndex, lambda)*m_PSoil;
        }

      // Here rho and tau come in
      sigb = ddb*rho+ddf*tau;
      sigf = ddf*rho+ddb*tau;
      att = 1.-sigf;
      m2 = (att+sigb)*(att-sigb);
      if(m2<=0) m2 = 0;
      m = std::sqrt(m2);

      sb = sdb*rho+sdf*tau;
      sf = sdf*rho+sdb*tau;
      vb = dob*rho+dof*tau;
      vf = dof*rho+dob*tau;
      w = sob*rho+sof*tau;

      // Here the LAI comes in
      // Outputs for the case LAI = 0
      if (m_LAI<0)
          {
          //tss = 1;
          too = 1;
          tsstoo = 1;
          rdd = 0;
          tdd = 1;
          rsd = 0;
          tsd = 0;
          rdo = 0;
          tdo = 0;
          //rso = 0;
          rsos = 0;
          rsod = 0;

          rddt = rsoil0;
          rsdt = rsoil0;
          rdot = rsoil0;
          rsodt = 0;
          rsost = rsoil0;
          //rsot = rsoil0;
          }

        // Other cases (LAI > 0)
        e1 = exp(-m*m_LAI);
        e2 = e1*e1;
        rinf = (att-m)/sigb;
        rinf2 = rinf*rinf;
        re = rinf*e1;
        denom = 1.-rinf2*e2;

        J1ks=Jfunc1(ks, m, m_LAI);
        J2ks=Jfunc2(ks, m, m_LAI);
        J1ko=Jfunc1(ko, m, m_LAI);
        J2ko=Jfunc2(ko, m, m_LAI);

        Ps = (sf+sb*rinf)*J1ks;
        Qs = (sf*rinf+sb)*J2ks;
        Pv = (vf+vb*rinf)*J1ko;
        Qv = (vf*rinf+vb)*J2ko;

        rdd = rinf*(1.-e2)/denom;
        tdd = (1.-rinf2)*e1/denom;
        tsd = (Ps-re*Qs)/denom;
        rsd = (Qs-re*Ps)/denom;
        tdo = (Pv-re*Qv)/denom;
        rdo = (Qv-re*Pv)/denom;

        tss = exp(-ks*m_LAI);
        too = exp(-ko*m_LAI);
        z = Jfunc3(ks, ko, m_LAI);
        g1 = (z-J1ks*too)/(ko+m);
        g2 = (z-J1ko*tss)/(ks+m);

        Tv1 = (vf*rinf+vb)*g1;
        Tv2 = (vf+vb*rinf)*g2;
        T1 = Tv1*(sf+sb*rinf);
        T2 = Tv2*(sf*rinf+sb);
        T3 = (rdo*Qs+tdo*Ps)*rinf;

        // Multiple scattering contribution to bidirectional canopy reflectance
        rsod = (T1+T2-T3)/(1.-rinf2);

        // Treatment of the hotspot-effect
        alf=1e6;
        // Apply correction 2/(K+k) suggested by F.-M. Bron
        if (m_HSpot>0) alf=(dso/m_HSpot)*2./(ks+ko);
        if (alf>200) alf=200;
        if (alf==0)
          {
          // The pure hotspot - no shadow
          tsstoo = tss;
          sumint = (1-tss)/(ks*m_LAI);
          }
        else
          {
          // Outside the hotspot
          fhot=m_LAI*std::sqrt(ko*ks);
          // Integrate by exponential Simpson method in 20 steps
          // the steps are arranged according to equal partitioning
          // of the slope of the joint probability function
          x1=0;
          y1=0;
          f1=1;
          fint=(1.-exp(-alf))*0.05;
          sumint=0;

          for(unsigned int j=1; j<=20; ++j)
            {
            if (j<20) x2 = -std::log(1.-j*fint)/alf;
            else x2 = 1;
            y2 = -(ko+ks)*m_LAI*x2+fhot*(1.-exp(-alf*x2))/alf;
            f2 = exp(y2);
            sumint = sumint+(f2-f1)*(x2-x1)/(y2-y1);
            x1=x2;
            y1=y2;
            f1=f2;
            }
          tsstoo=f1;
          }

        // Bidirectional reflectance
        // Single scattering contribution
        rsos = w*m_LAI*sumint;
      // Total canopy contribution
      // rso=rsos+rsod;
      //Interaction with the soil
      dn=1.-rsoil0*rdd;

      rddt = rdd+tdd*rsoil0*tdd/dn;
      rsdt = rsd+(tsd+tss)*rsoil0*tdd/dn;
      rdot = rdo+tdd*rsoil0*(tdo+too)/dn;

      rsodt = rsod+((tss+tsd)*tdo+(tsd+tss*rsoil0*rdd)*too)*rsoil0/dn;
      rsost = rsos+tsstoo*rsoil0;
      rsot = rsost+rsodt;

      resh = (rddt*PARdifo+rsdt*PARdiro)/(PARdiro+PARdifo);
      resv = (rdot*PARdifo+rsot*PARdiro)/(PARdiro+PARdifo);

      absh = (1-rddt-(1-rsoil0)*(tdd+(tdd*rdd*rsoil0)/dn));
      absv = (1-rsdt-(1-rsoil0)*(tss+(tss*rsoil0*rdd+tsd)/dn));

      SpectralResponseType::PairType response;
      response.first=lambda/1000.0;
      response.second=resh;
      outHRefl->GetResponse().push_back(response);
      response.second=resv;
      outVRefl->GetResponse().push_back(response);
      response.second=absh;
      outHAbs->GetResponse().push_back(response);
      response.second=absv;
      outVAbs->GetResponse().push_back(response);
   }
   m_FCoverView = 1-too;
}

void
SailModel
::Calc_LIDF(const double a, VectorType &lidf) const
{
   int ala=a;
   VectorType freq;
   Campbell(ala, freq);
   lidf=freq;

}

void
SailModel
::Campbell(const double ala, VectorType &freq) const
{
   unsigned int n=18;
   double excent = exp(-1.6184e-5*std::pow(ala, 3)+2.1145e-3*ala*ala-1.2390e-1*ala+3.2491);
   double sum=0;
   unsigned int tx2, tx1;
   double tl1, tl2, x1, x2, v, alpha, alpha2, x12, x22, alpx1, alpx2, dum, almx1, almx2;
   VectorType temp;

   for(unsigned int i=0; i<n; ++i)
   {
      tx2 = 5*i;
      tx1 = 5*(i+1);
      tl1 = tx1*CONST_PI/180;
      tl2 = tx2*CONST_PI/180;

      x1 = excent/sqrt(1.+excent*excent*std::tan(tl1)*std::tan(tl1));
      x2 = excent/sqrt(1.+excent*excent*std::tan(tl2)*std::tan(tl2));
      if(excent==1)
      {
         v = std::abs(cos(tl1)-cos(tl2));
         temp.push_back( v );
         sum = sum + v;
      }
      else
      {
         alpha = excent/std::sqrt(std::abs(1.-excent*excent));
         alpha2 = alpha*alpha;
         x12 = x1*x1;
         x22 = x2*x2;
         if(excent>1)
         {
            alpx1 = std::sqrt(alpha2+x12);
            alpx2 = std::sqrt(alpha2+x22);
            dum   = x1*alpx1+alpha2*log(x1+alpx1);
            v = std::abs(dum-(x2*alpx2+alpha2*log(x2+alpx2)));
            temp.push_back( v );
            sum = sum + v;
         }
         else
         {
            almx1 = sqrt(alpha2-x12);
            almx2 = sqrt(alpha2-x22);
            dum   = x1*almx1+alpha2*asin(x1/alpha);
            v = std::abs(dum-(x2*almx2+alpha2*asin(x2/alpha)));
            temp.push_back( v );
            sum = sum + v;
         }
      }
   }

   for(unsigned int i=0; i<n; ++i)
   {
      freq.push_back(temp[i]/sum);
   }

}

void
SailModel
::Volscatt(const double tts, const double tto, const double psi, const double ttl, VectorType &result) const
{

   double rd = CONST_PI/180;
   double costs = std::cos(rd*tts);
   double costo = std::cos(rd*tto);
   double sints = std::sin(rd*tts);
   double sinto = std::sin(rd*tto);
   double cospsi = std::cos(rd*psi);
   double psir = rd*psi;
   double costl = std::cos(rd*ttl);
   double sintl = std::sin(rd*ttl);
   double cs = costl*costs;
   double co = costl*costo;
   double ss = sintl*sints;
   double so = sintl*sinto;

   // ..............................................................................
   //     betas -bts- and betao -bto- computation
   //     Transition angles (beta) for solar (betas) and view (betao) directions
   //     if thetav+thetal>pi/2, bottom side of the leaves is observed for leaf azimut
   //     interval betao+phi<leaf azimut<2pi-betao+phi.
   //     if thetav+thetal<pi/2, top side of the leaves is always observed, betao=pi
   //     same consideration for solar direction to compute betas
   // ..............................................................................
   double cosbts, cosbto, bts, ds, chi_s, bto, doo, chi_o;
   double btran1, btran2, bt1, bt2, bt3, t1, t2 , denom, frho, ftau;

   cosbts = 5;
   if (std::abs(ss)>1e-6) cosbts = -cs/ss;

   cosbto=5;
   if (std::abs(so)>1e-6) cosbto = -co/so;

   if (std::abs(cosbts)<1)
   {
      bts = std::acos(cosbts);
      ds = ss;
   }
   else
   {
      bts = CONST_PI;
      ds = cs;
   }

   chi_s = 2./CONST_PI*((bts-CONST_PI*0.5)*cs+std::sin(bts)*ss);

   if (std::abs(cosbto)<1)
   {
      bto = std::acos(cosbto);
      doo = so;
   }
   else if(tto<90)
   {
      bto = CONST_PI;
      doo = co;
   }
   else
   {
      bto = 0;
      doo = -co;
   }
   chi_o = 2./CONST_PI*((bto-CONST_PI*0.5)*co+std::sin(bto)*so);

   // ..............................................................................
   //   Computation of auxiliary azimut angles bt1, bt2, bt3 used
   //   for the computation of the bidirectional scattering coefficient w
   // .............................................................................

   btran1 = std::abs(bts-bto);
   btran2 = CONST_PI - std::abs(bts+bto-CONST_PI);

   if (psir<=btran1)
   {
      bt1=psir;
      bt2=btran1;
      bt3=btran2;
   }
   else
   {
      bt1=btran1;
      if (psir<=btran2)
      {
         bt2=psir;
         bt3=btran2;
      }
      else
      {
         bt2=btran2;
         bt3=psir;
      }
   }

   t1 = 2.*cs*co+ss*so*cospsi;
   t2 = 0;
   if (bt2>0) t2=sin(bt2)*(2.*ds*doo+ss*so*cos(bt1)*cos(bt3));
   denom = 2.*CONST_PI*CONST_PI;
   frho = ((CONST_PI-bt2)*t1+t2)/denom;
   ftau = (-bt2*t1+t2)/denom;

   if (frho<0) frho = 0;
   if (ftau<0) ftau = 0;

   result[0] = chi_s;
   result[1] = chi_o;
   result[2] = frho;
   result[3] = ftau;

}

double
SailModel
::Jfunc1(const double k, const double l, const double t) const
{
   //J1 function with avoidance of singularity problem
   double v;
   double del=(k-l)*t;
   if(std::abs(del)>1e-3)
   {
      v = (exp(-l*t)-exp(-k*t))/(k-l);
      return v;
   }
   else
   {
      v = 0.5*t*(exp(-k*t)+exp(-l*t))*(1.-del*del/12.);
      return v;
   }
}

double
SailModel
::Jfunc2(const double k, const double l, const double t) const
{
   double v;
   v = (1.-exp(-(k+l)*t))/(k+l);
   return v;
}

double
SailModel
::Jfunc3(const double k, const double l, const double t) const
{
   double v;
   v =  (1.-exp(-(k+l)*t))/(k+l);
   return v;
}

void
SailModel
::PrintSelf(std::ostream& os, itk::Indent indent) const
{
   Superclass::PrintSelf(os, indent);

}

void SailModel::UseExternalSoilDB(std::shared_ptr<SoilDataBase> SoilDB,
                                  size_t SoilIndex)
{
  m_UseSoilFile = true;
  m_SoilIndex = SoilIndex;
  m_SoilDataBase = SoilDB;
}
} // end namespace otb


#include <iostream>
#include <fstream>
#include <queue>
#include <string>
#include <limits>

#include <boost/random.hpp>
#include <boost/generator_iterator.hpp>

#include <glog/logging.h>

using boost::variate_generator;
using boost::mt19937;
using boost::exponential_distribution;

#define ONLY_EVENT_TYPE     0
#define NUMBER_EVENT_TYPES  3   //NEED TO MAKE SURE THIS IS 1 MORE THAN THE LAST DEFINED EVENT

const std::string event_names[NUMBER_EVENT_TYPES] = {
    "ARRIVE",
    "DEPART",
    "CLOSE"
};

bool closed = false;

std::vector<bool> servers_idle;
std::vector< std::queue<double>* > servers_queue;

// Stats vectors
std::vector<double> times_after_close;
std::vector<double> average_times_in_queue;
std::vector< std::vector<double>* > average_server_utilizations;
std::vector< std::vector<double>* > average_length_of_queues;

// Output File
std::ofstream outfile;

/**
 *  A class for an event, which holds an int for the event type, a double for simulation time and possibly other data.
 *  We may want to subclass this with our own events
 */
class Event {
    public:
        const double time;
        const double server_start_time;
        const int server_index;
        const int type;

        Event(double time, int type) : time(time), server_start_time(0), server_index(-1), type(type) {
            //cout << "created an event with simulation time: " << this->time << endl;
            //cout << "created an event with event type: " << this->type << endl;
        }

        Event(double time, double server_start_time, int server_index, int type) : time(time), server_start_time(server_start_time), server_index(server_index), type(type) {
            //cout << "created an event with simulation time: " << this->time << endl;
            //cout << "created an event with event type: " << this->type << endl;
        }

        bool operator<( const Event& e ) const {
            return time < e.time;
        }

        bool operator>( const Event& e ) const {
            return time > e.time;
        }

        bool less( const Event& e) const {
            return time < e.time;
        }

        friend std::ostream& operator<< (std::ostream& out, Event& event);
};

class CompareEvent {
    public:
        bool operator()(Event* &e1, Event* &e2) {
            return e1->time > e2->time;
            //return false;
        }
};

// Print event
std::ostream& operator<< ( std::ostream& out, Event& event) {
    out << "[sim_time: " << std::setw(10) << std::setprecision(3) << std::fixed << event.time << ", type: " << std::setw(4) << event.type;
    if (event.type < NUMBER_EVENT_TYPES) {
        out << " - " << std::left << std::setw(50) << event_names[event.type];
    } else {
        out << std::left << std::setw(50) << " - UNKNOWN";
    }
    out << std::right << "]";
    return out;
}

bool server_idle(int &idle_index) {
    for (int i = 0; i < servers_idle.size(); i++) {
        if (servers_idle[i] == true) {
            idle_index = i;
            return true;
        }
    }
    return false;
}

void addToQueue(double enqueue_time) {
    std::queue<double> *shortest = NULL;
    for (int i = 0; i < servers_queue.size(); i++) {
        if (shortest == NULL || servers_queue[i]->size() < shortest->size()) {
            shortest = servers_queue[i];
        }
    }
    shortest->push(enqueue_time);
}

void jockey(const int &departed_queue) {
    for (int i = 1; i < servers_queue.size(); i++) {
        int pos = departed_queue - i;
        if (pos < 0) pos = servers_queue.size() + pos;
        if (servers_queue[departed_queue]->size() < servers_queue[pos]->size()) {
            servers_queue[departed_queue]->push(servers_queue[pos]->front());
            servers_queue[pos]->pop();
        }
    }
}

void run_simulation(
        const double close_time,
        variate_generator< mt19937, exponential_distribution<> > &arrival_generator,
        variate_generator< mt19937, exponential_distribution<> > &departure_generator) {
    double simulation_time_s = 0;
    double previous_time_s = 0;

    std::vector<double> servers_work_time;
    std::vector<double> servers_time_queue_length;
    for (int i = 0; i < servers_idle.size(); i++) {
        servers_work_time.push_back(0);
        servers_time_queue_length.push_back(0);
    }

    double total_queue_time = 0;
    double total_departures = 0;

    std::priority_queue<Event*, std::vector<Event*>, CompareEvent> heap;

    // Put initial event in the heap
    heap.push(new Event(simulation_time_s + arrival_generator(), 0));
    heap.push(new Event(close_time, 2));

    VLOG(2) << "Start Simulation...";

    while (!heap.empty()) {
        Event *current_event = heap.top();
        heap.pop();

        if (current_event == NULL) {
            LOG(ERROR) << "Simulation not complete and there are no events in the min heap.";
            LOG(ERROR) << "\tsimulation time: " << simulation_time_s;
            exit(0);
        }

        previous_time_s = simulation_time_s;
        simulation_time_s = current_event->time;

        // Calculate average time stuff
        double time_since_last = simulation_time_s - previous_time_s;
        // Calculate sum of time queue length for all servers
        for (int i = 0; i < servers_idle.size(); i++) {
            servers_time_queue_length[i] += servers_queue[i]->size() * time_since_last;
        }

        int idle_index = -1;
        switch (current_event->type) {
            case 0: // ARRIVE
                // If the server is busy add new arrival to waiting queue
                // otherwise set the server as busy and add new departure time.
                VLOG(2) << "Arrival Begin";
                if (!server_idle(idle_index)) {
                    addToQueue(simulation_time_s);
                } else {
                    VLOG(2) << "Idle Index: " << idle_index;
                    servers_idle[idle_index] = false;
                    heap.push(new Event(simulation_time_s + departure_generator(), simulation_time_s, idle_index, 1));
                }
                if (!closed) {
                    // Add next arrival event
                    heap.push(new Event(simulation_time_s + arrival_generator(), 0));
                }
                VLOG(2) << "Arrival End";
                break;
            case 1: // DEPART
                // Add server busy time.
                // Which server is busy?
                servers_work_time[current_event->server_index] += simulation_time_s - current_event->server_start_time;
                jockey(current_event->server_index);
                // If the queue is empty set the server to idle otherwise
                // get the next person from the queue and set their departure
                // time.
                VLOG(2) << "Depart Begin";
                if (servers_queue[current_event->server_index]->empty()) {
                    servers_idle[current_event->server_index] = true;
                } else {
                    // Add current simulation time minus time stored in the
                    // queue to the total queue time.
                    total_queue_time += simulation_time_s - servers_queue[current_event->server_index]->front();
                    servers_queue[current_event->server_index]->pop();
                    // Add a new depature to the heap.
                    heap.push(new Event(simulation_time_s + departure_generator(), simulation_time_s, current_event->server_index, 1));
                }
                total_departures++;
                VLOG(2) << "Depart End";
                break;
            case 2: // CLOSE
                // Set flag to stop arrivals
                VLOG(2) << "Closed";
                closed = true;
                break;
            default:
                LOG(ERROR) << "Simulation had an event with an unknown type: " << current_event->type;
                LOG(ERROR) << "\tsimulation time: " << simulation_time_s;
                exit(0);
        }

        VLOG(3) << *current_event << ", h: " << heap.size();

        delete current_event; // Event's are created with new, so we need to delete them when we're done with them
    }

    assert(heap.empty());

    VLOG(1) << "The simulation ended at time: " << simulation_time_s;
    times_after_close.push_back(simulation_time_s - close_time);
    VLOG(1) << "The simulation ended " << simulation_time_s - close_time << " after close.";
    average_server_utilizations.push_back(new std::vector<double>());
    average_length_of_queues.push_back(new std::vector<double>());
    VLOG(1) << "Server util vector size: " << average_server_utilizations.size();
    for (int i = 0; i < servers_idle.size(); i++) {
        VLOG(1) << "The server " << i+1 << " was busy for time: " << servers_work_time[i];
        average_server_utilizations.back()->push_back(servers_work_time[i] / simulation_time_s);
        VLOG(1) << "The server " << i+1 << " utilization was: " << servers_work_time[i] / simulation_time_s;
        average_length_of_queues.back()->push_back(servers_time_queue_length[i] / simulation_time_s);
        VLOG(1) << "The average length of server " << i+1 << " queue was: " << servers_time_queue_length[i] / simulation_time_s;
        VLOG(1) << "Server util size: " << average_server_utilizations.back()->size();
    }
    VLOG(1) << "Total time spent in the queue: " << total_queue_time;
    VLOG(1) << "Total departures: " << total_departures;
    average_times_in_queue.push_back(total_queue_time / total_departures);
    VLOG(1) << "The average time spent in queue: " << total_queue_time / total_departures;
}

int main(int argc, char **argv) {
    // Initialize Google Logging
    google::InitGoogleLogging(argv[0]);
    // Log to Stderr
    FLAGS_logtostderr = 1;

    int seed = time(0);
    double arrival_mean = 1.0;
    double departure_mean = 4.5;
    variate_generator< mt19937, exponential_distribution<> > arrival_generator(mt19937(seed), exponential_distribution<>(1/arrival_mean));
    variate_generator< mt19937, exponential_distribution<> > departure_generator(mt19937(seed+1), exponential_distribution<>(1/departure_mean));

    // Intiate
    int duration = 480;
    int min_servers = 4;
    int max_servers = 7;
    int num_iterations = 10;

    // Open files
    std::stringstream filename;
    filename << "servers_stats" << ".dat";
    outfile.open(filename.str());

    for (int sev = min_servers; sev <= max_servers; sev++) {
        int num_servers = sev;
        for (int j = 0; j < num_iterations; j++) {
            closed = false;
            servers_idle.clear();
            servers_queue.clear();
            for (int k = 0; k < num_servers; k++) {
                servers_idle.push_back(true);
                servers_queue.push_back(new std::queue<double>());
            }
            run_simulation(duration, arrival_generator, departure_generator);

            for (int j = 0; j < num_servers; j++) {
                delete servers_queue[j];
            }
        }

        // Collect and print statistics.

        double sum_average_queue_time = std::accumulate(average_times_in_queue.begin(), average_times_in_queue.end(), 0.0);
        double min_average_queue_time = *std::min_element(average_times_in_queue.begin(), average_times_in_queue.end());
        double max_average_queue_time = *std::max_element(average_times_in_queue.begin(), average_times_in_queue.end());
        double avg_average_queue_time = sum_average_queue_time / average_times_in_queue.size();
        double sqr_average_queue_time = std::inner_product(average_times_in_queue.begin(), average_times_in_queue.end(), average_times_in_queue.begin(), 0.0);
        double std_average_queue_time = std::sqrt(sqr_average_queue_time / average_times_in_queue.size() - avg_average_queue_time * avg_average_queue_time);

        double sum_times_after_close = std::accumulate(times_after_close.begin(), times_after_close.end(), 0.0);
        double min_times_after_close = *std::min_element(times_after_close.begin(), times_after_close.end());
        double max_times_after_close = *std::max_element(times_after_close.begin(), times_after_close.end());
        double avg_times_after_close = sum_times_after_close / average_times_in_queue.size();
        double sqr_times_after_close = std::inner_product(times_after_close.begin(), times_after_close.end(), times_after_close.begin(), 0.0);
        double std_times_after_close = std::sqrt(sqr_times_after_close / times_after_close.size() - avg_times_after_close * avg_times_after_close);

        double min_server_utilization = std::numeric_limits<double>::max();
        double max_server_utilization = std::numeric_limits<double>::min();
        double sum_server_utilization = 0;
        double sqr_server_utilization = 0;

        double min_average_length = std::numeric_limits<double>::max();
        double max_average_length = std::numeric_limits<double>::min();
        double sum_average_length = 0;
        double sqr_average_length = 0;

        for (int i = 0; i < num_iterations; i++) {
            double temp_avg_server_utilization = 0;
            double temp_avg_average_length = 0;

            // Find the average server utilization of the queues;
            for (int j = 0; j < num_servers; j++) {
                double current_utilization = average_server_utilizations.at(i)->at(j);
                VLOG(2) << "Server " << j+1 << " utilization: " << current_utilization;
                temp_avg_server_utilization += current_utilization;

                double current_average_length = average_length_of_queues.at(i)->at(j);
                VLOG(2) << "Server " << j+1 << " average length: " << current_average_length;
                temp_avg_average_length += current_average_length;
                temp_avg_average_length = temp_avg_average_length / num_servers;
            }
            delete average_server_utilizations.at(i);
            delete average_length_of_queues.at(i);

            temp_avg_server_utilization = temp_avg_server_utilization / num_servers;
            sum_server_utilization += temp_avg_server_utilization;
            if (temp_avg_server_utilization > max_server_utilization) max_server_utilization = temp_avg_server_utilization;
            if (temp_avg_server_utilization < min_server_utilization) min_server_utilization = temp_avg_server_utilization;
            sqr_server_utilization += temp_avg_server_utilization * temp_avg_server_utilization;

            temp_avg_average_length = temp_avg_average_length / num_servers;
            sum_average_length += temp_avg_average_length;
            if (temp_avg_average_length > max_average_length) max_average_length = temp_avg_average_length;
            if (temp_avg_average_length < min_average_length) min_average_length = temp_avg_average_length;
            sqr_average_length += temp_avg_average_length * temp_avg_average_length;
        }
        average_server_utilizations.clear();
        average_length_of_queues.clear();

        double avg_server_utilization = sum_server_utilization / num_iterations;
        double std_server_utilization = std::sqrt(sqr_server_utilization / num_iterations - avg_server_utilization * avg_server_utilization);

        double avg_average_length = sum_average_length / (num_servers * num_iterations);
        double std_average_length = std::sqrt(sqr_average_length / (num_servers * num_iterations) - avg_average_length * avg_average_length);

        outfile << "------------------------------------------" << std::endl;
        outfile << "---------------- " << num_servers << " SERVERS" << " ---------------" << std::endl;
        outfile << "------------------------------------------" << std::endl;
        outfile << std::endl;

        outfile << "-----------------------------------------" << std::endl;
        outfile << "------- AVERAGE SERVER UTILIZATION ------" << std::endl;
        outfile << "-----------------------------------------" << std::endl;
        outfile << "Min server utiliation: " << min_server_utilization << std::endl;
        outfile << "Max server utiliation: " << max_server_utilization << std::endl;
        outfile << "Average server utilization: " << avg_server_utilization << std::endl;
        outfile << "Standard deviation of server utilization: " << std_server_utilization << std::endl;
        outfile << std::endl;

        outfile << "-----------------------------------------" << std::endl;
        outfile << "---------- AVERAGE QUEUE LENGTH ---------" << std::endl;
        outfile << "-----------------------------------------" << std::endl;
        outfile << "Min queue length: " << min_average_length << std::endl;
        outfile << "Max queue length: " << max_average_length << std::endl;
        outfile << "Average queue length: " << avg_average_length << std::endl;
        outfile << "Standard deviation of queue length: " << std_average_length << std::endl;
        outfile << std::endl;

        outfile << "-----------------------------------------" << std::endl;
        outfile << "---------- AVERAGE QUEUE TIMES ----------" << std::endl;
        outfile << "-----------------------------------------" << std::endl;
        outfile << "Min of average queue times: " << min_average_queue_time << std::endl;
        outfile << "Max of average queue times: " << max_average_queue_time << std::endl;
        outfile << "Mean of average queue times: " << avg_average_queue_time << std::endl;
        outfile << "Standard deviation of average queue times: " << std_average_queue_time << std::endl;
        outfile << std::endl;

        outfile << "------------------------------------------" << std::endl;
        outfile << "-------- AVERAGE TIME AFTER CLOSE --------" << std::endl;
        outfile << "------------------------------------------" << std::endl;
        outfile << "Min of times after close: " << min_times_after_close << std::endl;
        outfile << "Max of times after close: " << max_times_after_close << std::endl;
        outfile << "Mean of times after close: " << avg_times_after_close << std::endl;
        outfile << "Standard deviation of times after close: " << std_times_after_close << std::endl;
        outfile << std::endl;
        outfile << std::endl;
        outfile << std::endl;

    }
    outfile.close();
}


#include <fstream>
#include <iostream>
//#include <boost/foreach.hpp>

#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Nef_polyhedron_3.h>
//#include <CGAL/iterator.h>
//#include <CGAL/squared_distance_3.h>

typedef CGAL::Exact_predicates_exact_constructions_kernel       Kernel;
typedef CGAL::Polyhedron_3<Kernel>                              Polyhedron;
typedef CGAL::Nef_polyhedron_3<Kernel>                          Nef_polyhedron;
typedef Polyhedron::HalfedgeDS                                  HalfedgeDS;
//typedef Polyhedron::Vertex_iterator                             Vertex_iterator;
//typedef Polyhedron::Halfedge_iterator                           Halfedge_iterator;
typedef Polyhedron::Facet_iterator                              Facet_iterator;
typedef Kernel::Point_3                                         Point_3;

//typedef Polyhedron::Halfedge_handle                             Halfedge_handle;
//typedef Polyhedron::Facet_handle                                Facet_handle;
//typedef Polyhedron::Vertex_handle                               Vertex_handle;
typedef Polyhedron::Halfedge_around_facet_circulator            Halfedge_facet_circulator;
//typedef CGAL::Inverse_index<Vertex_iterator>                    Index;

#include "gewebe_CGALBooleanOperations3.h"

using namespace std;

const bool M_DEBUG              = false;
const int NOT_FOUND             = -1;
const int INTERSECTION          = 0;
const int JOIN                  = 1;
const int DIFFERENCE            = 2;
const int SYMMETRIC_DIFFERENCE   = 3;

/**
 * @TODO
 *
 * - read CGAL doc : http://doc.cgal.org/latest/Polyhedron/classCGAL_1_1Polyhedron__3.html + http://doc.cgal.org/latest/Polyhedron/index.html
 * - check polyhedron + builder : http://doc.cgal.org/latest/Polyhedron/classCGAL_1_1Polyhedron__incremental__builder__3.html
 * - read on traits : http://doc.cgal.org/latest/Manual/devman_traits_classes.html
 * - look at example code in CGAL
 *
 */

// from http://cgal-discuss.949826.n4.nabble.com/attachment/4661367/0/union_and_intersection.cpp
// from http://jamesgregson.blogspot.de/2012/05/example-code-for-building.html
// from https://github.com/yusuketomoto/ofxCGAL/blob/master/src/ofxCGALBooleanOp.cpp

template <class HDS>
class PolyhedronBuilder : public CGAL::Modifier_base<HDS> {
public:

    vector<Point_3> & vertices;
    vector<int> & indices;
    const float kPointScale = 1.0f;

    PolyhedronBuilder(vector<Point_3> & _vertices, vector<int> & _indices) : vertices(_vertices), indices(_indices) {}

    void operator()(HDS & hds) {

        CGAL::Polyhedron_incremental_builder_3<HDS> mBuilder(hds, true);

        int numOfVertices = vertices.size();
        int numOfIndices = indices.size();

        mBuilder.begin_surface(numOfVertices, numOfIndices / 3);

        typedef typename HDS::Vertex Vertex;
        typedef typename Vertex::Point Point;

        for(int i=0; i<vertices.size(); i++) {
            mBuilder.add_vertex(Point(vertices[i].x(),
                                      vertices[i].y(),
                                      vertices[i].z()));
        }

        for(int i=0; i<numOfIndices; i+=3) {
            mBuilder.begin_facet();
            mBuilder.add_vertex_to_facet(indices[i + 0]);
            mBuilder.add_vertex_to_facet(indices[i + 1]);
            mBuilder.add_vertex_to_facet(indices[i + 2]);
            mBuilder.end_facet();
        }

        mBuilder.end_surface();
    }
};

bool near(Point_3 v0, Point_3 v1, double pMinDistance) {
    if (pMinDistance == 0) {
        return v0.x() == v1.x() && v0.y() == v1.y() && v0.z() == v1.z();
    } else {
        double x = CGAL::to_double(v0.x() - v1.x());
        double y = CGAL::to_double(v0.y() - v1.y());
        double z = CGAL::to_double(v0.z() - v1.z());
        double mDistance = sqrt(x*x+y*y+z*z);
        return mDistance < pMinDistance;
    }
}

int findVertexInIndexList(vector<Point_3> vertices_opt, Point_3 v, float pEpsilon) {
    for (int j = 0; j < vertices_opt.size(); j++) {
        Point_3 vo = vertices_opt[j];
        bool isNear = near(v, vo, pEpsilon);
        if (isNear) {
            return j;
        }
    }
    return NOT_FOUND;
}

void optimizeIndexList(vector<Point_3> & coords_raw, vector<Point_3> & coords_opt, vector<int> & indices_opt) {
    int mIndexCounter = 0;
    for (int i=0; i<coords_raw.size(); i++) {
        Point_3 v = coords_raw[i];
        int mExistingIndex = findVertexInIndexList(coords_opt, v, 0.0f);
        if (mExistingIndex == NOT_FOUND) {
            coords_opt.push_back(v);
            indices_opt.push_back(mIndexCounter);
            mIndexCounter++;
        } else {
            indices_opt.push_back(mExistingIndex);
        }
    }
}

void convertToPolyhedron(vector<Point_3> vertices, vector<int> indices, Polyhedron & polyhedron) {
    PolyhedronBuilder<HalfedgeDS> builder(vertices, indices);
    polyhedron.delegate(builder);
}

void extract_vertices(vector<Point_3> & vertices, JNIEnv * env, jfloatArray & j_coords) {
    jboolean iscopy;
    jfloat* jcoords_A_array = env->GetFloatArrayElements(j_coords, &iscopy);
    const int mNumberOfPoints_A = (env->GetArrayLength(j_coords)) / 3;

    if (M_DEBUG) cout << "--- A has points: " << mNumberOfPoints_A << endl;

    for(int i=0; i < mNumberOfPoints_A; i++) {
        double x = (double)*jcoords_A_array;
        jcoords_A_array++;
        double y = (double)*jcoords_A_array;
        jcoords_A_array++;
        double z = (double)*jcoords_A_array;
        jcoords_A_array++;
        vertices.push_back(Point_3(x, y, z));
    }
}

JNIEXPORT jfloatArray JNICALL Java_gewebe_CGALBooleanOperations3_boolean_1operation
(JNIEnv * env, jobject jobj, jint class_type, jfloatArray coords_A_array, jfloatArray coords_B_array )
{
    if (M_DEBUG) cout << "--- boolean_operation" << endl;

    /* evaluate command */

    int mBooleanOperation;
    if ( class_type == JOIN ) {
        if (M_DEBUG) cout << "--- type: JOIN" << endl;
        mBooleanOperation = JOIN;
    } else if ( class_type == INTERSECTION ) {
        if (M_DEBUG) cout << "--- type: INTERSECTION" << endl;
        mBooleanOperation = INTERSECTION;
    } else if ( class_type == DIFFERENCE ) {
        if (M_DEBUG) cout << "--- type: DIFFERENCE" << endl;
        mBooleanOperation = DIFFERENCE;
    } else if ( class_type == SYMMETRIC_DIFFERENCE ) {
        if (M_DEBUG) cout << "--- type: SYMMETRIC_DIFFERENCE" << endl;
        mBooleanOperation = SYMMETRIC_DIFFERENCE;
    } else {
        if (M_DEBUG) cout << "--- type: (DEFAULT) ?: " << class_type << endl;
        mBooleanOperation = INTERSECTION;
    }

    /* ------ */

    vector<Point_3> vertices_raw_A;
    extract_vertices(vertices_raw_A, env, coords_A_array);
    if (M_DEBUG) cout << "coords_raw_A: " << vertices_raw_A.size() << endl;

    vector<Point_3> vertices_raw_B;
    extract_vertices(vertices_raw_B, env, coords_B_array);

    /* build polyhedrons */

    Polyhedron polyhedronA;
    vector<Point_3> vertices_opt_A;
    vector<int> indices_opt_A;
    optimizeIndexList(vertices_raw_A, vertices_opt_A, indices_opt_A);
    convertToPolyhedron(vertices_opt_A, indices_opt_A, polyhedronA);

    Polyhedron polyhedronB;
    vector<Point_3> vertices_opt_B;
    vector<int> indices_opt_B;
    optimizeIndexList(vertices_raw_B, vertices_opt_B, indices_opt_B);
    convertToPolyhedron(vertices_opt_B, indices_opt_B, polyhedronB);

    /* check validity of polyhedra */

    if (!polyhedronA.is_pure_triangle() || !polyhedronB.is_pure_triangle()){
        cerr << "Inputs polyhedra must be triangulated." << std::endl;
        cerr << "A.is_pure_triangle() " << polyhedronA.is_pure_triangle() << endl;
        cerr << "B.is_pure_triangle() " << polyhedronB.is_pure_triangle() << endl;
    }

    if (!polyhedronA.size_of_vertices() || !polyhedronB.size_of_vertices()){
        cerr << "Inputs polyhedra must not be empty." << std::endl;
    }

    if (!polyhedronA.is_valid() || !polyhedronB.is_valid()){
        cerr << "Inputs polyhedra must be valid." << std::endl;
    }

    if(!polyhedronA.is_closed()) {
        cerr << "input mesh A is not closed." << endl;
    }

    if(!polyhedronB.is_closed()) {
        cerr << "input mesh B is not closed." << endl;
    }

    Nef_polyhedron Nef_A(polyhedronA);
    Nef_polyhedron Nef_B(polyhedronB);

    if (M_DEBUG) cout << "Nef_A (op) Nef_B" << endl;

    /*
     operator *= :: intersection
     operator += :: join
     operator -= :: difference
     operator ^= :: symmetric_difference
     */

    if (mBooleanOperation == INTERSECTION) {
        Nef_A *= Nef_B;
    } else if (mBooleanOperation == JOIN) {
        Nef_A += Nef_B;
    } else if (mBooleanOperation == DIFFERENCE) {
        Nef_A -= Nef_B;
    } else if (mBooleanOperation == SYMMETRIC_DIFFERENCE) {
        Nef_A ^= Nef_B;
    }

    if (M_DEBUG) cout << "Nef_A.is_simple(): " << Nef_A.is_simple() << endl;

    Polyhedron polyhedron_result;
    Nef_A.convert_to_polyhedron(polyhedron_result);
    if(Nef_A.is_simple()) {
        Nef_A.convert_to_Polyhedron(polyhedron_result);
    } else {
        cerr << "analyze/process n1 and do something..." << endl;
    }

    /* build jfloatarray */

    const int mNumberOfMeshData = distance(polyhedron_result.facets_begin(), polyhedron_result.facets_end()) * 3 * 3;
    jfloat mMeshData[mNumberOfMeshData];
    if (M_DEBUG) cout << "mNumberOfMeshData: " << mNumberOfMeshData << endl;

    int i = 0;
    for(Facet_iterator f = polyhedron_result.facets_begin();
        f != polyhedron_result.facets_end();
        ++f )
    {
        Halfedge_facet_circulator j = f->facet_begin();
        //        // Facets in polyhedral surfaces are at least triangles.
        //        CGAL_assertion( CGAL::circulator_size(j) >= 3);
        //        cout << CGAL::circulator_size(j) << ' ';
        do {
            if (M_DEBUG) cout << j->vertex()->point() << endl;
            mMeshData[i] = (float)CGAL::to_double(j->vertex()->point().x());
            i++;
            mMeshData[i] = (float)CGAL::to_double(j->vertex()->point().y());
            i++;
            mMeshData[i] = (float)CGAL::to_double(j->vertex()->point().z());
            i++;
        } while ( ++j != f->facet_begin());
    }

    jfloatArray mResultMeshData;
    mResultMeshData = env->NewFloatArray(mNumberOfMeshData);
    env->SetFloatArrayRegion(mResultMeshData, 0, mNumberOfMeshData, mMeshData);

    return mResultMeshData;
}

//    int size = std::distance(C.vertices_begin(), C.vertices_end());
//    cout << "number of points : " << size << endl;
//
//
//    //    if (/* DISABLES CODE */ (false)) {
//    cout << "coref ... ";
//
//    coref(new_A, new_B,
//          polyline_output,
//          std::back_inserter(result),
//          operation_type);
//
//    cout << "done." << endl;
//
//    //    }
//
//    //    int union_index = result[0].second == Corefinement::Join_tag ? 0:1;
//    //    int intersection_index = (union_index+1)%2;
//    //
//    //    cout << union_index << endl;
//    //    cout << intersection_index << endl;
//
//    //    cout << *( result[union_index].first );
//    //    cout << endl;
//    //    cout << *( result[intersection_index].first );
//
//    //    cout << *( result[0].first );
//
//    //    /* return results */
//
//    //    CGAL::Vertex_iterator begin = result[union_index].first->vertices_begin();
//    //    CGAL::Vertex_iterator end = result[union_index].first->vertices_end();
//
//    //    std::list<Vertex>::iterator itb = result[union_index]->vertices_begin();
//    //    for(; result[union_index].first->vertices_begin() != result[union_index].first->vertices_end(); itb++) {
//    //
//    //    }

//    /* populate vectors */
//    {
//        jfloat* jcoords_A_array = env->GetFloatArrayElements(coords_A_array, &iscopy);
//        const int mNumberOfPoints_A = (env->GetArrayLength(coords_A_array)) / 3;
//
//        if (M_DEBUG) cout << "--- A has points: " << mNumberOfPoints_A << endl;
//
//        for(int i=0; i < mNumberOfPoints_A; i++) {
//            double x = (double)*jcoords_A_array;
//            jcoords_A_array++;
//            double y = (double)*jcoords_A_array;
//            jcoords_A_array++;
//            double z = (double)*jcoords_A_array;
//            jcoords_A_array++;
//            coords_A.push_back(x);
//            coords_A.push_back(y);
//            coords_A.push_back(z);
//            //            cout << x << ", " << y << ", " << z << endl;
//        }
//    }
//    {
//        jfloat* jcoords_B_array = env->GetFloatArrayElements(coords_B_array, &iscopy);
//        const int mNumberOfPoints_B = (env->GetArrayLength(coords_B_array)) / 3;
//
//        if (M_DEBUG) cout << "--- B has points: " << mNumberOfPoints_B << endl;
//
//        for(int i=0; i < mNumberOfPoints_B; i++) {
//            double x = (double)*jcoords_B_array;
//            jcoords_B_array++;
//            double y = (double)*jcoords_B_array;
//            jcoords_B_array++;
//            double z = (double)*jcoords_B_array;
//            jcoords_B_array++;
//            coords_B.push_back(x);
//            coords_B.push_back(y);
//            coords_B.push_back(z);
//            //            cout << x << ", " << y << ", " << z << endl;
//        }
//    }

//    Vertex_iterator v;
//    for( v = polyhedron_result.vertices_begin();
//        v != polyhedron_result.vertices_end();
//        ++v )     {
//        cout << v->point() << endl;
//    }

//    vector<Alpha_shape_3::Facet> a_facets;
//    as->get_alpha_shape_facets(std::back_inserter(a_facets), facets_type);
//    const jsize nb_facets_indices = 3 * 3 * a_facets.size();
//
//    if (M_DEBUG) cout << "--- number of points: " << nb_facets_indices << endl;
//
//    Index index(as->vertices_begin(), as->vertices_end());
//    jfloat m_mesh_points [nb_facets_indices];
//
//    size_t nbf=a_facets.size();
//    for (size_t i=0;i<nbf;++i) {
//        if ( as->classify( a_facets[i].first )!=Alpha_shape_3::EXTERIOR ) {
//            a_facets[i]=as->mirror_facet( a_facets[i] );
//        }
//
//        int indices[3]={
//            (a_facets[i].second+1)%4,
//            (a_facets[i].second+2)%4,
//            (a_facets[i].second+3)%4,
//        };
//
//        /// according to the encoding of vertex indices, this is needed to get
//        /// a consistent orienation
//        if ( a_facets[i].second%2==0 ) {
//            swap(indices[0], indices[1]);
//        }
//
//        m_mesh_points[i * 9 + 0] = a_facets[i].first->vertex(indices[0])->point()[0];
//        m_mesh_points[i * 9 + 1] = a_facets[i].first->vertex(indices[0])->point()[1];
//        m_mesh_points[i * 9 + 2] = a_facets[i].first->vertex(indices[0])->point()[2];
//        m_mesh_points[i * 9 + 3] = a_facets[i].first->vertex(indices[1])->point()[0];
//        m_mesh_points[i * 9 + 4] = a_facets[i].first->vertex(indices[1])->point()[1];
//        m_mesh_points[i * 9 + 5] = a_facets[i].first->vertex(indices[1])->point()[2];
//        m_mesh_points[i * 9 + 6] = a_facets[i].first->vertex(indices[2])->point()[0];
//        m_mesh_points[i * 9 + 7] = a_facets[i].first->vertex(indices[2])->point()[1];
//        m_mesh_points[i * 9 + 8] = a_facets[i].first->vertex(indices[2])->point()[2];
//    }
//
//    jfloatArray result;
//    result = env->NewFloatArray( nb_facets_indices);
//    env->SetFloatArrayRegion(result, 0, nb_facets_indices, m_mesh_points);
//
//    return result;

//    /* build jfloatarray */
//
//    const int mNumberOfMeshData = distance(polyhedron_result.vertices_begin(), polyhedron_result.vertices_end()) * 3;
//    jfloat mMeshData[mNumberOfMeshData];
//
//    Vertex_iterator v;
//    int i = 0;
//    for( v = polyhedron_result.vertices_begin();
//        v != polyhedron_result.vertices_end();
//        ++v )
//    {
//        mMeshData[i] = (float)CGAL::to_double(v->point().x());
//        i++;
//        mMeshData[i] = (float)CGAL::to_double(v->point().y());
//        i++;
//        mMeshData[i] = (float)CGAL::to_double(v->point().z());
//        i++;
//    }
//
//    if (M_DEBUG) cout << "--- build array " << endl;
//
//    jfloatArray mResultMeshData;
//    mResultMeshData = env->NewFloatArray(mNumberOfMeshData);
//    env->SetFloatArrayRegion(mResultMeshData, 0, mNumberOfMeshData, mMeshData);
//
//    if (M_DEBUG) cout << "--- return array " << endl;
//
//    return mResultMeshData;

JNIEXPORT jint JNICALL Java_gewebe_CGALBooleanOperations3_version
(JNIEnv * env, jobject)
{
    int mVersion = 20210130;
    int mTime = 141015;
    return mVersion;
}