#include <fstream>
#include <boost/algorithm/string.hpp>
#include <harp.hpp>
#include <specex_fits.h>
#include <specex_trace.h>
#include <specex_gauss_hermite_two_psf.h>

using namespace std ;

static void AddRow1(specex::FitsTable& table,const string& PARAM, double wavemin, double wavemax, harp::vector_double& coeff) {
  std::vector<specex::FitsTableEntry> row;
  {specex::FitsTableEntry entry; entry.string_val = PARAM; row.push_back(entry);}
  {specex::FitsTableEntry entry; entry.double_vals.resize(1); entry.double_vals[0] = wavemin; row.push_back(entry);}
  {specex::FitsTableEntry entry; entry.double_vals.resize(1); entry.double_vals[0] = wavemax; row.push_back(entry);}
  {specex::FitsTableEntry entry; entry.double_vals = coeff; row.push_back(entry);}
  table.data.push_back(row);
}

void write_gauss_hermite_two_psf_fits_version_1(const specex::GaussHermite2PSF& psf, fitsfile* fp, int first_hdu) {

  ////////////////////////////
  string PSFVER = "1";
  ////////////////////////////

  int NPIX_X = psf.ccd_image_n_cols;
  int NPIX_Y = psf.ccd_image_n_rows;
  int BUNDLMIN = 0;
  int BUNDLMAX = 0;
  int FIBERMIN = 0;
  int FIBERMAX = 0;
  int NFIBERS=0;
  for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
      bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {

    if(bundle_it == psf.ParamsOfBundles.begin()) {
      BUNDLMIN = bundle_it->second.bundle_id;
      BUNDLMAX = bundle_it->second.bundle_id;
      FIBERMIN = bundle_it->second.fiber_min;
      FIBERMAX = bundle_it->second.fiber_max;
    }

    BUNDLMIN = min(BUNDLMIN,bundle_it->second.bundle_id);
    BUNDLMAX = max(BUNDLMAX,bundle_it->second.bundle_id);
    FIBERMIN = min(FIBERMIN,bundle_it->second.fiber_min);
    FIBERMAX = max(FIBERMAX,bundle_it->second.fiber_max);

    NFIBERS += (bundle_it->second.fiber_max-bundle_it->second.fiber_min+1);
  }

  int GHDEGX = psf.core_degree;
  int GHDEGY = psf.core_degree;
  int GHDEGX2 = psf.second_degree;
  int GHDEGY2 = psf.second_degree;

  int status = 0;
  fits_movabs_hdu ( fp, first_hdu, NULL, &status ); harp::fits::check ( status );

  // get largest legendre degree of all params in all bundles and check param numbers match !!
  int ncoeff=0;

  int nparams=psf.LocalNAllPar();
  int nparams_all = nparams;
  nparams_all += 2; // X and Y
  nparams_all += 1; // GH trivial order zero
#ifdef CONTINUUM
  nparams_all += 1; // continuum
#endif

  for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
      bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {

    if( int(bundle_it->second.AllParPolXW.size()) != nparams ) SPECEX_ERROR("Fatal inconsistency in number of parameters in psf between bundles: AllParPolXW.size=" << bundle_it->second.AllParPolXW.size() << " psf.LocalNAllPar()=" << nparams);

    for(size_t p=0;p<bundle_it->second.AllParPolXW.size();p++) {
      ncoeff=max(ncoeff,bundle_it->second.AllParPolXW[p]->ydeg+1);
    }
  }

  ncoeff += 2; // increase degree of legendre polynomials to minimize mapping errors

  specex::FitsTable table;

  { // column description
    table.AddColumnDescription("PARAM","8A","","");
    table.AddColumnDescription("WAVEMIN","D","","");
    table.AddColumnDescription("WAVEMAX","D","","");
    char coeff_tform[100];
    sprintf(coeff_tform,"%dD",ncoeff*NFIBERS);
    vector <int> dim; dim.resize(2);
    dim[0]=ncoeff;
    dim[1]=NFIBERS;

    string sdim = table.encode_dimension(dim);
    // debug
    vector <int> dim2 = table.decode_dimension(sdim);
    if (dim2.size()!=2 || dim2[0]!= dim[0]|| dim2[1]!=dim[1]) {
      SPECEX_ERROR("error encode/decode dimension");
    }

    table.AddColumnDescription("COEFF",coeff_tform,sdim,"");
  }

  int LEGWMIN=1000000;
  int LEGWMAX=0;
  vector<string> keys;

  { // data

    // now loop on real psf parameters

    harp::vector_double wave(ncoeff);
    harp::vector_double values(ncoeff);

    // get the max range of wavelength and convert to int

    for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
	  bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {
      const specex::PSF_Params & params_of_bundle = bundle_it->second;
      for(int p=0;p<nparams;p++) {
	LEGWMIN=min(LEGWMIN,int(floor(params_of_bundle.AllParPolXW[p]->ymin)));
	LEGWMAX=max(LEGWMAX,int(floor(params_of_bundle.AllParPolXW[p]->ymax))+1);
      }
    }

    double wavemin = double(LEGWMIN);
    double wavemax = double(LEGWMAX);
    {
      double wavestep = (wavemax-wavemin)/(ncoeff-1);
      for(int w=0;w<ncoeff;w++) {
	wave[w]   = wavemin + wavestep*w;
      }
    }

    bool need_to_add_first_gh = true;

    harp::vector_double coeff(ncoeff*NFIBERS);

    // first deal with X and Y
    {
      harp::vector_double coeff_y(ncoeff*NFIBERS);
      harp::vector_double values_y(ncoeff);
      coeff.clear();
      coeff_y.clear();
      int fiber_index=0;

      for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
	  bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {
	const specex::PSF_Params & params_of_bundle = bundle_it->second;
	for(int fiber=params_of_bundle.fiber_min; fiber<=params_of_bundle.fiber_max; fiber++,fiber_index++) {

	  const specex::Trace &trace = psf.FiberTraces.find(fiber)->second;
	  specex::Legendre1DPol pol1d_x(ncoeff-1,wavemin,wavemax);
	  specex::Legendre1DPol pol1d_y(ncoeff-1,wavemin,wavemax);
	  for(int w=0;w<ncoeff;w++) {
	    values[w]   = trace.X_vs_W.Value(wave[w]);
	    values_y[w] = trace.Y_vs_W.Value(wave[w]);
	  }
	  pol1d_x.Fit(wave,values,0,false);
	  pol1d_y.Fit(wave,values_y,0,false);
	  for(int w = 0; w < ncoeff ; w++) {
	    coeff(fiber_index*ncoeff+w)   =  pol1d_x.coeff(w);
	    coeff_y(fiber_index*ncoeff+w) =  pol1d_y.coeff(w);
	  }

	} // end of loop on fiber
      } // end of loop on bundles

      AddRow1(table,"X",LEGWMIN,LEGWMAX,coeff);
      AddRow1(table,"Y",LEGWMIN,LEGWMAX,coeff_y);

    } // end of X Y context

    // precompute coeffs of GH2-0-0
    harp::vector_double  gh200coeff;
    harp::vector_double  gh100coeff;
    {
      int gh200_index = psf.ParamIndex("GH2-0-0");
      int fiber_index=0;
      gh200coeff.resize(coeff.size());
      gh200coeff.clear();
      gh100coeff.resize(coeff.size());
      gh100coeff.clear();

      // duplicated code that need to be simplified
      for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
	  bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {
	const specex::PSF_Params & params_of_bundle = bundle_it->second;
	const specex::Pol_p pol2d = params_of_bundle.AllParPolXW[gh200_index]; // this is the 2D polynomiald of x_ccd and wave for this param and bundle

	for(int fiber=params_of_bundle.fiber_min; fiber<=params_of_bundle.fiber_max; fiber++,fiber_index++) {

	  const specex::Trace& trace = psf.FiberTraces.find(fiber)->second; // X_vs_W.Value();

	  // build a Legendre1DPol out of the Legendre2DPol
	  specex::Legendre1DPol pol1d(ncoeff-1,wavemin,wavemax);
	  for(int w=0;w<ncoeff;w++) {
	    values[w] = pol2d->Value(trace.X_vs_W.Value(wave[w]),wave[w]);
	  }
	  pol1d.Fit(wave,values,0,false);

	  // now copy parameters;

	  for(int w = 0; w < ncoeff ; w++) {
	    gh200coeff(fiber_index*ncoeff+w) = pol1d.coeff(w); // this is the definition of the ordering, (wave,fiber)
	    gh100coeff(fiber_index*ncoeff+w) = -gh200coeff(fiber_index*ncoeff+w); // this is the definition of the ordering, (wave,fiber)
	  }
	  gh100coeff(fiber_index*ncoeff) += 1;
	}
      }
    }

    for(int p=0;p<nparams;p++) {

      coeff.clear();

      string pname = psf.ParamName(p);

      if(need_to_add_first_gh && pname.find("GH-")<pname.npos) { // insert now GH param 0
	AddRow1(table,"GH-0-0",LEGWMIN,LEGWMAX,gh100coeff);
	need_to_add_first_gh = false;
	cout << "GH-0-0  : " << gh100coeff << endl;
	cout << "GH2-0-0 : " << gh200coeff << endl;
      }

      int fiber_index=0;
      for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
	  bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {
	const specex::PSF_Params & params_of_bundle = bundle_it->second;

	const specex::Pol_p pol2d = params_of_bundle.AllParPolXW[p]; // this is the 2D polynomiald of x_ccd and wave for this param and bundle

	for(int fiber=params_of_bundle.fiber_min; fiber<=params_of_bundle.fiber_max; fiber++,fiber_index++) {

	  const specex::Trace& trace = psf.FiberTraces.find(fiber)->second; // X_vs_W.Value();

	  // build a Legendre1DPol out of the Legendre2DPol
	  specex::Legendre1DPol pol1d(ncoeff-1,wavemin,wavemax);
	  for(int w=0;w<ncoeff;w++) {
	    values[w] = pol2d->Value(trace.X_vs_W.Value(wave[w]),wave[w]);
	  }
	  pol1d.Fit(wave,values,0,false);

	  // now copy parameters;

	  for(int w = 0; w < ncoeff ; w++) {
	    coeff(fiber_index*ncoeff+w) = pol1d.coeff(w); // this is the definition of the ordering, (wave,fiber)
	  }

	} // end of loop on fibers of bundle

      } // end of loop on bundles

      AddRow1(table,pname,LEGWMIN,LEGWMAX,coeff);

    } // end of loop on params

#ifdef CONTINUUM
    {
      coeff.clear();
      int fiber_index=0;
      for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
	  bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {
	const specex::PSF_Params & params_of_bundle = bundle_it->second;
	specex::Legendre1DPol pol1d(ncoeff-1,wavemin,wavemax);
	for(int w=0;w<ncoeff;w++) {
	  values[w]   = params_of_bundle.ContinuumPol.Value(wave[w]);
	}
	pol1d.Fit(wave,values,0,false);
	for(int fiber=params_of_bundle.fiber_min; fiber<=params_of_bundle.fiber_max; fiber++,fiber_index++) {
	  for(int w = 0; w < ncoeff ; w++) {
	    coeff(fiber_index*ncoeff+w)   =  pol1d.coeff(w);
	  }
	}
      }
      AddRow1(table,"CONT",LEGWMIN,LEGWMAX,coeff);
    }
#endif

  }

  // write table
  table.Write(fp);

  // write keywords
  {

    fits_write_comment(fp,"------------------------------------------------------------------------",&status); harp::fits::check ( status );
    fits_write_comment(fp,"PSF generated by specex, https://github.com/julienguy/specex",&status); harp::fits::check ( status );

    {
      char date_comment[80];
      time_t t = time(0);   // get time now
      struct tm * now = localtime( & t );
      sprintf(date_comment,"PSF fit date %04d-%02d-%02d",(now->tm_year + 1900),(now->tm_mon + 1),now->tm_mday);
      fits_write_comment(fp,date_comment,&status); harp::fits::check ( status );
    }
    ///////////////////////xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx////////
    fits_write_comment(fp,"-  ",&status); harp::fits::check ( status );
    fits_write_comment(fp,"Each row of the table contains the data vector of one PSF parameter",&status); harp::fits::check ( status );
    fits_write_comment(fp,"The size of the vector is ((FIBERMAX-FIBERMIN+1)*(LEGDEG+1))",&status); harp::fits::check ( status );
    fits_write_comment(fp,"Description of  the NPARAMS parameters : ",&status); harp::fits::check ( status );
    fits_write_comment(fp,"X        : CCD column coordinate (as a function of fiber and wavelength)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"Y        : CCD row coordinate (as a function of fiber and wavelength)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"         (X,Y)=(0,0) means that PSF is centered on center of first pixel",&status); harp::fits::check ( status );
    fits_write_comment(fp,"GHSIGX   : Sigma of first Gaussian along CCD columns for PSF core",&status); harp::fits::check ( status );
    fits_write_comment(fp,"GHSIGY   : Sigma of first Gaussian along CCD rows for PSF core",&status); harp::fits::check ( status );
    fits_write_comment(fp,"GHNSIG   : NxSigma cutoff for first Gaussian",&status); harp::fits::check ( status );
    fits_write_comment(fp,"GHSIGX2  : Sigma of second Gaussian along CCD columns for PSF wings",&status); harp::fits::check ( status );
    fits_write_comment(fp,"GHSIGY2  : Sigma of second Gaussian along CCD rows for PSF wings",&status); harp::fits::check ( status );
    fits_write_comment(fp,"GH-i-j   : Hermite pol. coefficents, i along columns, j along rows,",&status); harp::fits::check ( status );
    fits_write_comment(fp,"         i is integer from 0 to GHDEGX, j is integer from 0 to GHDEGY,",&status); harp::fits::check ( status );
    fits_write_comment(fp,"         there are (GHDEGX+1)*(GHDEGY+1) such coefficents.",&status); harp::fits::check ( status );
    fits_write_comment(fp,"GH2-i-j  : Hermite pol. coefficents for sec. GH psf, i along columns, j along rows,",&status); harp::fits::check ( status );
    fits_write_comment(fp,"TAILAMP  : Amplitude of PSF tail",&status); harp::fits::check ( status );
    fits_write_comment(fp,"TAILCORE : Size in pixels of PSF tail saturation in PSF core",&status); harp::fits::check ( status );
    fits_write_comment(fp,"TAILXSCA : Scaling apply to CCD coordinate along columns for PSF tail",&status); harp::fits::check ( status );
    fits_write_comment(fp,"TAILYSCA : Scaling apply to CCD coordinate along rows for PSF tail",&status); harp::fits::check ( status );
    fits_write_comment(fp,"TAILINDE : Asymptotic power law index of PSF tail",&status); harp::fits::check ( status );
    fits_write_comment(fp,"CONT     : Continuum flux in arc image (not part of PSF)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"-  ",&status); harp::fits::check ( status );
    fits_write_comment(fp,"PSF_core(X,Y) = SUM_ij (GH-i-j)*HERM(i,X/GHSIGX)*HERM(j,Y/GHSIGY)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"               * GAUS(X,GHSIGX)*GAUS(Y,GHSIGY)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"              +SUM_ij (GH2-i-j)*HERM(i,X/GHSIGX2)*HERM(j,Y/GHSIGY2)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"                * GAUS(X,GHSIGX2)*GAUS(Y,GHSIGY2)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"-  ",&status); harp::fits::check ( status );
    fits_write_comment(fp,"PSF_tail(X,Y) = TAILAMP*R^2/(TAILCORE^2+R^2)^(1+TAILINDE/2)",&status); harp::fits::check ( status );
    fits_write_comment(fp,"                with R^2=(X*TAILXSCA)^2+(Y*TAILYSCA)^2",&status); harp::fits::check ( status );
    fits_write_comment(fp,"-  ",&status); harp::fits::check ( status );
    fits_write_comment(fp,"PSF_core is integrated in pixel",&status); harp::fits::check ( status );
    fits_write_comment(fp,"PSF_tail is not, it is evaluated at center of pixel",&status); harp::fits::check ( status );
    fits_write_comment(fp,"------------------------------------------------------------------------",&status); harp::fits::check ( status );
    harp::fits::key_write(fp,"EXTNAME","PSF","");
    harp::fits::key_write(fp,"PSFTYPE","GAUSS-HERMITE2","");
    harp::fits::key_write(fp,"PSFVER",PSFVER,"");

    harp::fits::key_write(fp,"MJD",(long long int)psf.mjd,"MJD of arc lamp exposure");
    harp::fits::key_write(fp,"PLATEID",psf.plate_id,"plate ID of arc lamp exposure");
    harp::fits::key_write(fp,"CAMERA",psf.camera_id,"camera ID");
    harp::fits::key_write(fp,"ARCEXP",psf.arc_exposure_id,"ID of arc lamp exposure used to fit PSF");

    harp::fits::key_write(fp,"NPIX_X",(long long int)NPIX_X,"number of columns in input CCD image");
    harp::fits::key_write(fp,"NPIX_Y",(long long int)NPIX_Y,"number of rows in input CCD image");
    harp::fits::key_write(fp,"HSIZEX",(long long int)psf.hSizeX,"Half size of PSF in fit, NX=2*HSIZEX+1");
    harp::fits::key_write(fp,"HSIZEY",(long long int)psf.hSizeY,"Half size of PSF in fit, NY=2*HSIZEY+1");
    harp::fits::key_write(fp,"BUNDLMIN",(long long int)BUNDLMIN,"first bundle of fibers (starting at 0)");
    harp::fits::key_write(fp,"BUNDLMAX",(long long int)BUNDLMAX,"last bundle of fibers (included)");
    harp::fits::key_write(fp,"FIBERMIN",(long long int)FIBERMIN,"first fiber (starting at 0)");
    harp::fits::key_write(fp,"FIBERMAX",(long long int)FIBERMAX,"last fiber (included)");
    harp::fits::key_write(fp,"NPARAMS",(long long int)nparams_all,"number of PSF parameters");
    harp::fits::key_write(fp,"LEGDEG",(long long int)(ncoeff-1),"degree of Legendre pol.(wave) for parameters");
    harp::fits::key_write(fp,"GHDEGX",(long long int)GHDEGX,"degree of Hermite polynomial along CCD columns");
    harp::fits::key_write(fp,"GHDEGY",(long long int)GHDEGY,"degree of Hermite polynomial along CCD rows");
    harp::fits::key_write(fp,"GHDEGX2",(long long int)GHDEGX2,"degree of Hermite polynomial along CCD columns (sec. term)");
    harp::fits::key_write(fp,"GHDEGY2",(long long int)GHDEGY2,"degree of Hermite polynomial along CCD rows (sec. term)");

    // add chi2
    harp::fits::key_write(fp,"PSFERROR",psf.psf_error,"assumed PSF fractional error in chi2");
    harp::fits::key_write(fp,"READNOIS",psf.readout_noise,"assumed read out noise in chi2");
    harp::fits::key_write(fp,"GAIN",psf.gain,"assumed gain in chi2");

    for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf.ParamsOfBundles.begin();
	bundle_it != psf.ParamsOfBundles.end(); ++bundle_it) {

      const specex::PSF_Params & params_of_bundle = bundle_it->second;

      int ndf = params_of_bundle.ndata - params_of_bundle.nparams;
      double chi2pdf = 0;
      if(ndf>0) chi2pdf = params_of_bundle.chi2/ndf;

      char key[20];
      char comment[800];

      sprintf(key,"B%02dRCHI2",params_of_bundle.bundle_id);
      sprintf(comment,"best fit chi2/ndf for fiber bundle %d",params_of_bundle.bundle_id);
      harp::fits::key_write(fp,key,chi2pdf,comment);

      sprintf(key,"B%02dNDATA",params_of_bundle.bundle_id);
      sprintf(comment,"number of pixels in fit for fiber bundle %d",params_of_bundle.bundle_id);
      harp::fits::key_write(fp,key,(long long int)params_of_bundle.ndata,comment);

      sprintf(key,"B%02dNPAR",params_of_bundle.bundle_id);
      sprintf(comment,"number of parameters in fit for fiber bundle %d",params_of_bundle.bundle_id);
      harp::fits::key_write(fp,key,(long long int)params_of_bundle.nparams,comment);

    }
  }

}


/*!
@file
Forward declares `boost::hana::Group`.

@copyright Louis Dionne 2013-2016
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_FWD_CONCEPT_GROUP_HPP
#define BOOST_HANA_FWD_CONCEPT_GROUP_HPP

#include <boost/hana/config.hpp>

BOOST_HANA_NAMESPACE_BEGIN
    //! @ingroup group-concepts
    //! @defgroup group-Group Group
    //! The `Group` concept represents `Monoid`s where all objects have
    //! an inverse w.r.t. the `Monoid`'s binary operation.
    //!
    //! A [Group][1] is an algebraic structure built on top of a `Monoid`
    //! which adds the ability to invert the action of the `Monoid`'s binary
    //! operation on any element of the set. Specifically, a `Group` is a
    //! `Monoid` `(S, +)` such that every element `s` in `S` has an inverse
    //! (say `s'`) which is such that
    //! @code
    //!     s + s' == s' + s == identity of the Monoid
    //! @endcode
    //!
    //! There are many examples of `Group`s, one of which would be the
    //! additive `Monoid` on integers, where the inverse of any integer
    //! `n` is the integer `-n`. The method names used here refer to
    //! exactly this model.
    //!
    //!
    //! Minimal complete definitions
    //! ----------------------------
    //! 1. `minus`\n
    //! When `minus` is specified, the `negate` method is defaulted by setting
    //! @code
    //!     negate(x) = minus(zero<G>(), x)
    //! @endcode
    //!
    //! 2. `negate`\n
    //! When `negate` is specified, the `minus` method is defaulted by setting
    //! @code
    //!     minus(x, y) = plus(x, negate(y))
    //! @endcode
    //!
    //!
    //! Laws
    //! ----
    //! For all objects `x` of a `Group` `G`, the following laws must be
    //! satisfied:
    //! @code
    //!     plus(x, negate(x)) == zero<G>() // right inverse
    //!     plus(negate(x), x) == zero<G>() // left inverse
    //! @endcode
    //!
    //!
    //! Refined concept
    //! ---------------
    //! `Monoid`
    //!
    //!
    //! Concrete models
    //! ---------------
    //! `hana::integral_constant`
    //!
    //!
    //! Free model for non-boolean arithmetic data types
    //! ------------------------------------------------
    //! A data type `T` is arithmetic if `std::is_arithmetic<T>::%value` is
    //! true. For a non-boolean arithmetic data type `T`, a model of `Group`
    //! is automatically defined by setting
    //! @code
    //!     minus(x, y) = (x - y)
    //!     negate(x) = -x
    //! @endcode
    //!
    //! @note
    //! The rationale for not providing a Group model for `bool` is the same
    //! as for not providing a `Monoid` model.
    //!
    //!
    //! Structure-preserving functions
    //! ------------------------------
    //! Let `A` and `B` be two `Group`s. A function `f : A -> B` is said to
    //! be a [Group morphism][2] if it preserves the group structure between
    //! `A` and `B`. Rigorously, for all objects `x, y` of data type `A`,
    //! @code
    //!     f(plus(x, y)) == plus(f(x), f(y))
    //! @endcode
    //! Because of the `Group` structure, it is easy to prove that the
    //! following will then also be satisfied:
    //! @code
    //!     f(negate(x)) == negate(f(x))
    //!     f(zero<A>()) == zero<B>()
    //! @endcode
    //! Functions with these properties interact nicely with `Group`s, which
    //! is why they are given such a special treatment.
    //!
    //!
    //! [1]: http://en.wikipedia.org/wiki/Group_(mathematics)
    //! [2]: http://en.wikipedia.org/wiki/Group_homomorphism
    template <typename G>
    struct Group;
BOOST_HANA_NAMESPACE_END

#endif // !BOOST_HANA_FWD_CONCEPT_GROUP_HPP


#pragma once

#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <memory>
#include <functional>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>

namespace Symbolic {

#define DEF_TYPE_PREDICATE_DEFAULT(predicate_name) \
    virtual bool predicate_name() const {          \
        return false;                              \
    }

    struct TypeCheckError {
        std::string msg;
    };

    class Type {
    public:
        virtual bool is_val() const = 0;
        virtual bool equal_to(std::shared_ptr<Type> t) const = 0;
        virtual void print(std::ostream &os) const = 0;
        virtual int get_bv_width() const {
            throw TypeCheckError { "not a bit vector" };
        }
        virtual bool is_bv_type() const {
            return false;
        }
        virtual bool is_func_type() const {
            return false;
        }
    };

    class ValType : public Type {
    public:
        enum class T {
            BITVEC,
        };

        T type;

        virtual bool is_val() const override {
            return true;
        }
    };

    class BitVecType : public ValType {
    public:
        int bitwidth;

        virtual int get_bv_width() const override {
            return bitwidth;
        }

        virtual bool is_bv_type() const override {
            return true;
        }

        virtual void print(std::ostream &os) const override;
        virtual bool equal_to(std::shared_ptr<Type> t) const override;

        BitVecType(): bitwidth(0) {}
        BitVecType(int bw): bitwidth(bw) {}
    };

    class UFType : public Type {
    public:
        std::vector<std::shared_ptr<ValType>> key_types;
        std::shared_ptr<ValType> val_type;

        UFType(const std::vector<std::shared_ptr<ValType>> &kts,
               std::shared_ptr<ValType> vt): key_types(kts), val_type(vt) {}

        virtual bool is_val() const override {
            return false;
        }

        virtual bool is_func_type() const override {
            return true;
        }

        virtual void print(std::ostream &os) const override;
        virtual bool equal_to(std::shared_ptr<Type> t) const override;
    };

    class Expr;
    class Lambda;
    class ConcreteBv;
    class SymbolicVar;
    class AddExpr;
    class SubExpr;
    class MulExpr;
    class DivExpr;
    class ModExpr;
    class UDivExpr;
    class UModExpr;

    class LAndExpr;
    class LOrExpr;
    class LXorExpr;
    class LNotExpr;
    class ImpliesExpr;

    class AndExpr;
    class OrExpr;
    class XorExpr;
    class NotExpr;
    class LshExpr;
    class LRshExpr;
    class ARshExpr;

    class EqExpr;
    class NeqExpr;
    class LeExpr;
    class LtExpr;
    class GeExpr;
    class GtExpr;
    class UleExpr;
    class UltExpr;
    class UgeExpr;
    class UgtExpr;

    class ExtractExpr;
    class SExtExpr;
    class UExtExpr;

    class IteExpr;
    class FuncApply;
    class ConcatExpr;

    class ForallExpr;
    class ExistsExpr;

#define DECLARE_EXPR_VISITOR(t) virtual void visit_expr(t &expr) { \
    this->visit_expr(*(Expr *)&expr);                      \
    }

    class ExprVisitor {
    public:
        virtual void visit(Expr &expr);

        virtual void visit_expr(Expr &e) {
        }
        DECLARE_EXPR_VISITOR(Lambda);
        DECLARE_EXPR_VISITOR(ConcreteBv);
        DECLARE_EXPR_VISITOR(SymbolicVar);

        DECLARE_EXPR_VISITOR(AddExpr);
        DECLARE_EXPR_VISITOR(SubExpr);
        DECLARE_EXPR_VISITOR(MulExpr);
        DECLARE_EXPR_VISITOR(DivExpr);
        DECLARE_EXPR_VISITOR(ModExpr);
        DECLARE_EXPR_VISITOR(UDivExpr);
        DECLARE_EXPR_VISITOR(UModExpr);

        DECLARE_EXPR_VISITOR(LAndExpr);
        DECLARE_EXPR_VISITOR(LOrExpr);
        DECLARE_EXPR_VISITOR(LXorExpr);
        DECLARE_EXPR_VISITOR(LNotExpr);
        DECLARE_EXPR_VISITOR(ImpliesExpr);

        DECLARE_EXPR_VISITOR(AndExpr);
        DECLARE_EXPR_VISITOR(OrExpr);
        DECLARE_EXPR_VISITOR(XorExpr);
        DECLARE_EXPR_VISITOR(NotExpr);
        DECLARE_EXPR_VISITOR(LshExpr);
        DECLARE_EXPR_VISITOR(LRshExpr);
        DECLARE_EXPR_VISITOR(ARshExpr);

        DECLARE_EXPR_VISITOR(EqExpr);
        DECLARE_EXPR_VISITOR(NeqExpr);
        DECLARE_EXPR_VISITOR(LeExpr);
        DECLARE_EXPR_VISITOR(LtExpr);
        DECLARE_EXPR_VISITOR(GeExpr);
        DECLARE_EXPR_VISITOR(GtExpr);
        DECLARE_EXPR_VISITOR(UleExpr);
        DECLARE_EXPR_VISITOR(UltExpr);
        DECLARE_EXPR_VISITOR(UgeExpr);
        DECLARE_EXPR_VISITOR(UgtExpr);

        DECLARE_EXPR_VISITOR(ExtractExpr);
        DECLARE_EXPR_VISITOR(SExtExpr);
        DECLARE_EXPR_VISITOR(UExtExpr);

        DECLARE_EXPR_VISITOR(IteExpr);
        DECLARE_EXPR_VISITOR(FuncApply);
        DECLARE_EXPR_VISITOR(ConcatExpr);

        DECLARE_EXPR_VISITOR(ForallExpr);
        DECLARE_EXPR_VISITOR(ExistsExpr);
    };

    using ExprPtr = std::shared_ptr<Expr>;

#define VISITOR_ACCEPT                                   \
    virtual void accept(ExprVisitor &v) override {       \
        v.visit_expr(*this);                             \
    }

    class Expr : public std::enable_shared_from_this<Expr> {
    public:
        std::shared_ptr<Type> type;
        DEF_TYPE_PREDICATE_DEFAULT(is_symbolic);
        DEF_TYPE_PREDICATE_DEFAULT(is_var);

        Expr();

        virtual std::shared_ptr<Expr> simplify() {
            return shared_from_this();
        }

        virtual void accept(ExprVisitor &v) {
            v.visit_expr(*this);
        }

        boost::uuids::uuid get_uuid() const { return uuid; }

    protected:
        boost::uuids::uuid uuid;
    };

    template <typename T>
    using PtrList = std::vector<std::shared_ptr<T>>;

    class OpApplyNode : public Expr {
    public:
        using ArgList = PtrList<Expr>;

        OpApplyNode(const ArgList &args);

        void type_check(const PtrList<Type> &types);

        virtual bool is_symbolic() const override {
            return is_symbolic_;
        }

        ArgList simplify_args() const;

    public:
        ArgList args_;
        bool is_symbolic_;
    };

    class ConcreteVal : public Expr {
    public:

    };

    class Lambda : public Expr {
    public:
        using FuncT = std::function<ExprPtr(OpApplyNode::ArgList)>;
        FuncT func;

        Lambda(std::shared_ptr<Type> func_type,
               FuncT func);

        Lambda(const PtrList<Type> &arg_t,
               std::shared_ptr<Type> ret_t,
               FuncT func);
        VISITOR_ACCEPT;
    };

    class ConcreteBv : public ConcreteVal {
    public:
        ConcreteBv(int bv_size, uint64_t val) {
            auto t = std::make_shared<BitVecType>();
            t->bitwidth = bv_size;
            type = std::dynamic_pointer_cast<Type>(t);
            val_ = val;
        }

        uint64_t get_val() const {
            return val_;
        }

        VISITOR_ACCEPT;
    protected:
        uint64_t val_;
    };

    class SymbolicVar : public Expr {
    public:
        SymbolicVar(std::shared_ptr<Type> t, const std::string &n):
            name(n) { type = t; }
        virtual bool is_symbolic() const override {
            return true;
        }
        virtual bool is_var() const override {
            return true;
        }

        VISITOR_ACCEPT;

        std::string name;
    };

    class FuncApply : public OpApplyNode {
    public:
        FuncApply(ExprPtr func, const ArgList &args);
        VISITOR_ACCEPT;

        ExprPtr func;
    };

    class BvBinOpExpr : public OpApplyNode {
    public:
        BvBinOpExpr(const ArgList &args);
        virtual ExprPtr simplify() override;
        virtual ExprPtr concrete_binop(uint64_t a1, uint64_t a2) {
            return shared_from_this();
        }
    };

    class BvBinPredExpr : public OpApplyNode {
    public:
        BvBinPredExpr(const ArgList &args);
        virtual ExprPtr simplify() override;
        virtual ExprPtr concrete_binop(uint64_t a1, uint64_t a2) {
            return shared_from_this();
        }
    };

#define DECLARE_OP(class_name) class class_name : public BvBinOpExpr {  \
    public:                                                             \
    class_name(const ArgList &args);                                    \
    VISITOR_ACCEPT;                                                     \
    virtual ExprPtr concrete_binop(uint64_t a1, uint64_t a2) override;  \
    }

#define DECLARE_PRED(class_name) class class_name : public BvBinPredExpr { \
    public:                                                             \
    class_name(const ArgList &args);                                    \
    VISITOR_ACCEPT;                                                     \
    virtual ExprPtr concrete_binop(uint64_t a1, uint64_t a2) override;  \
    }

    DECLARE_OP(AddExpr);
    DECLARE_OP(SubExpr);
    DECLARE_OP(MulExpr);
    DECLARE_OP(DivExpr);
    DECLARE_OP(ModExpr);
    DECLARE_OP(UDivExpr);
    DECLARE_OP(UModExpr);

    DECLARE_OP(LAndExpr);
    DECLARE_OP(LOrExpr);
    DECLARE_OP(LXorExpr);
    DECLARE_OP(ImpliesExpr);

    DECLARE_OP(AndExpr);
    DECLARE_OP(OrExpr);
    DECLARE_OP(XorExpr);
    DECLARE_OP(LshExpr);
    DECLARE_OP(LRshExpr);
    DECLARE_OP(ARshExpr);

    DECLARE_PRED(EqExpr);
    DECLARE_PRED(NeqExpr);
    DECLARE_PRED(LeExpr);
    DECLARE_PRED(LtExpr);
    DECLARE_PRED(GeExpr);
    DECLARE_PRED(GtExpr);
    DECLARE_PRED(UleExpr);
    DECLARE_PRED(UltExpr);
    DECLARE_PRED(UgeExpr);
    DECLARE_PRED(UgtExpr);

    class ConcatExpr : public OpApplyNode {
    public:
        ConcatExpr(const ArgList &args);
        VISITOR_ACCEPT;
    };
    class LNotExpr : public OpApplyNode {
    public:
        LNotExpr(ExprPtr e);
        VISITOR_ACCEPT;
    };

    class NotExpr : public OpApplyNode {
    public:
        NotExpr(ExprPtr e);
        VISITOR_ACCEPT;
    };

    class ExtractExpr : public Expr {
    public:
        ExtractExpr(ExprPtr e, int start, int end);
        VISITOR_ACCEPT;

        virtual bool is_symbolic() const override {
            return true;
        }

        ExprPtr v;
        int from, to;
    };

    class SExtExpr : public Expr {
    public:
        SExtExpr(ExprPtr e, int to);
        VISITOR_ACCEPT;

        virtual bool is_symbolic() const override {
            return true;
        }

        ExprPtr v;
        int to;
    };

    class UExtExpr : public Expr {
    public:
        UExtExpr(ExprPtr e, int to);
        VISITOR_ACCEPT;

        virtual bool is_symbolic() const override {
            return true;
        }

        ExprPtr v;
        int to;
    };

    class IteExpr : public Expr {
    public:
        IteExpr(ExprPtr cond, ExprPtr t, ExprPtr f);
        VISITOR_ACCEPT;
        virtual std::shared_ptr<Expr> simplify() override;

        virtual bool is_symbolic() const override {
            return true;
        }

        ExprPtr cond;
        ExprPtr t_val;
        ExprPtr f_val;
    };

    class ForallExpr : public Expr {
    public:
        ForallExpr(const std::vector<ExprPtr> &vars, ExprPtr cond);
        VISITOR_ACCEPT;
        virtual std::shared_ptr<Expr> simplify() override;

        virtual bool is_symbolic() const override {
            return true;
        }

        std::vector<ExprPtr> vars;
        ExprPtr cond;
    };

    class ExistsExpr : public Expr {
    public:
        ExistsExpr(const std::vector<ExprPtr> &vars, ExprPtr cond);
        VISITOR_ACCEPT;
        virtual std::shared_ptr<Expr> simplify() override;

        virtual bool is_symbolic() const override {
            return true;
        }

        std::vector<ExprPtr> vars;
        ExprPtr cond;
    };

#undef DECLARE_PRED
#undef DECLARE_OP

    ExprPtr endian_reverse(ExprPtr val);

    uint64_t get_concrete_val(ExprPtr v);
}

#define mk_expr_ptr(T, ...) std::dynamic_pointer_cast<::Symbolic::Expr>(std::shared_ptr<::Symbolic::T>(new ::Symbolic::T(__VA_ARGS__)))

#define mk_concrete_bv(bw, n) mk_expr_ptr(ConcreteBv, bw, n)


///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1998-2011, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// *       Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// *       Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// *       Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////

#include "PyIlmBaseConfigInternal.h"

#include "PyImathFun.h"
#include "PyImathDecorators.h"
#include "PyImathExport.h"
#include "PyImathAutovectorize.h"
#include <Python.h>
#include <boost/python.hpp>
#include <boost/python/make_constructor.hpp>
#include <boost/format.hpp>
#include <ImathVec.h>
#include <ImathMatrixAlgo.h>
#include <ImathFun.h>

namespace PyImath {

using namespace boost::python;
using namespace PyImath;

namespace {

template <class T>
struct rotationXYZWithUpDir_op
{
    static IMATH_NAMESPACE::Vec3<T>
    apply(const IMATH_NAMESPACE::Vec3<T> &from, const IMATH_NAMESPACE::Vec3<T> &to,
          const IMATH_NAMESPACE::Vec3<T> &up)
    {
        IMATH_NAMESPACE::Vec3<T> retval;
        IMATH_NAMESPACE::extractEulerXYZ(IMATH_NAMESPACE::rotationMatrixWithUpDir(from,to,up),retval);
        return retval;
    }
};

template <class T>
struct abs_op
{
    static T
    apply(T value)
    {
        return IMATH_NAMESPACE::abs<T>(value);
    }
};

template <class T>
struct sign_op
{
    static T
    apply(T value)
    {
        return IMATH_NAMESPACE::sign<T>(value);
    }
};

template <class T>
struct log_op
{
    static T
    apply(T value)
    {
        return ::log(value);
    }
};

template <class T>
struct log10_op
{
    static T
    apply(T value)
    {
        return ::log10(value);
    }
};

template <class T>
struct lerp_op
{
    static T
    apply(T a, T b, T t)
    {
        return IMATH_NAMESPACE::lerp<T>(a,b,t);
    }
};

template <class T>
struct ulerp_op
{
    static T
    apply(T a, T b, T t)
    {
        return IMATH_NAMESPACE::ulerp<T>(a,b,t);
    }
};

template <class T>
struct lerpfactor_op
{
    static T
    apply(T a, T b, T t)
    {
        return IMATH_NAMESPACE::lerpfactor<T>(a,b,t);
    }
};

template <class T>
struct clamp_op
{
    static T
    apply(T value, T low, T high)
    {
        return IMATH_NAMESPACE::clamp<T>(value,low,high);
    }
};

template <class T>
struct cmp_op
{
    static T
    apply(T value)
    {
        return IMATH_NAMESPACE::cmp<T>(value);
    }
};

template <class T>
struct cmpt_op
{
    static T
    apply(T value)
    {
        return IMATH_NAMESPACE::cmpt<T>(value);
    }
};

template <class T>
struct iszero_op
{
    static T
    apply(T value)
    {
        return IMATH_NAMESPACE::iszero<T>(value);
    }
};

template <class T>
struct equal_op
{
    static T
    apply(T value)
    {
        return IMATH_NAMESPACE::equal<T>(value);
    }
};

template <class T>
struct floor_op
{
    static int
    apply(T value)
    {
        return IMATH_NAMESPACE::floor<T>(value);
    }
};

template <class T>
struct ceil_op
{
    static int
    apply(T value)
    {
        return IMATH_NAMESPACE::ceil<T>(value);
    }
};

template <class T>
struct trunc_op
{
    static int
    apply(T value)
    {
        return IMATH_NAMESPACE::trunc<T>(value);
    }
};

struct divs_op
{
    static int
    apply(int x, int y)
    {
        return IMATH_NAMESPACE::divs(x,y);
    }
};

struct mods_op
{
    static int
    apply(int x, int y)
    {
        return IMATH_NAMESPACE::mods(x,y);
    }
};

struct divp_op
{
    static int
    apply(int x, int y)
    {
        return IMATH_NAMESPACE::divp(x,y);
    }
};

struct modp_op
{
    static int
    apply(int x, int y)
    {
        return IMATH_NAMESPACE::modp(x,y);
    }
};

struct bias_op
{
    static inline float
    apply(float x, float b)
    {
        if (b != 0.5f)
        {
            static const float inverse_log_half = 1.0f / std::log(0.5f);
            const float biasPow = std::log(b)*inverse_log_half;
            return std::pow(x, biasPow);
        }
        return x;
    }
};

struct gain_op
{
    static inline float
    apply(float x, float g)
    {
        if (x < 0.5f)
            return 0.5f*bias_op::apply(2.0f*x, 1.0f - g);
        else
            return 1.0f - 0.5f*bias_op::apply(2.0f - 2.0f*x, 1.0f - g);
    }
};

} // namespace

void register_functions()
{
    //
    // Utility Functions
    //

    // nb: MSVC gets confused about which arg we want (it thinks it
    // might be boost::arg), so telling it which one explicitly here.
    typedef boost::python::arg arg;

    PyImath::generate_bindings<abs_op<int>,boost::mpl::true_>(
        "abs",
        "return the absolute value of 'value'",
        (arg("value")));
    PyImath::generate_bindings<abs_op<float>,boost::mpl::true_>(
        "abs",
        "return the absolute value of 'value'",
        (arg("value")));
    PyImath::generate_bindings<abs_op<double>,boost::mpl::true_>(
        "abs",
        "return the absolute value of 'value'",
        (arg("value")));

    PyImath::generate_bindings<sign_op<int>,boost::mpl::true_>(
        "sign",
        "return 1 or -1 based on the sign of 'value'",
        (arg("value")));
    PyImath::generate_bindings<sign_op<float>,boost::mpl::true_>(
        "sign",
        "return 1 or -1 based on the sign of 'value'",
        (arg("value")));
    PyImath::generate_bindings<sign_op<double>,boost::mpl::true_>(
        "sign",
        "return 1 or -1 based on the sign of 'value'",
        (arg("value")));

    PyImath::generate_bindings<log_op<float>,boost::mpl::true_>(
        "log",
        "return the natural log of 'value'",
        (arg("value")));
    PyImath::generate_bindings<log_op<double>,boost::mpl::true_>(
        "log",
        "return the natural log of 'value'",
        (arg("value")));

    PyImath::generate_bindings<log10_op<float>,boost::mpl::true_>(
        "log10",
        "return the base 10 log of 'value'",
        (arg("value")));
    PyImath::generate_bindings<log10_op<double>,boost::mpl::true_>(
        "log10",
        "return the base 10 log of 'value'",
        (arg("value")));

    PyImath::generate_bindings<lerp_op<float>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "lerp",
        "return the linear interpolation of 'a' to 'b' using parameter 't'",
        (arg("a"),arg("b"),arg("t")));
    PyImath::generate_bindings<lerp_op<double>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "lerp",
        "return the linear interpolation of 'a' to 'b' using parameter 't'",
        (arg("a"),arg("b"),arg("t")));

    PyImath::generate_bindings<lerpfactor_op<float>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "lerpfactor",
        "return how far m is between a and b, that is return t such that\n"
        "if:\n"
        "    t = lerpfactor(m, a, b);\n"
        "then:\n"
        "    m = lerp(a, b, t);\n"
        "\n"
        "If a==b, return 0.\n",
        (arg("m"),arg("a"),arg("b")));
    PyImath::generate_bindings<lerpfactor_op<double>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "lerpfactor",
        "return how far m is between a and b, that is return t such that\n"
        "    if:\n"
        "        t = lerpfactor(m, a, b);\n"
        "    then:\n"
        "        m = lerp(a, b, t);\n"
        "    if a==b, return 0.\n",
        (arg("m"),arg("a"),arg("b")));

    PyImath::generate_bindings<clamp_op<int>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "clamp",
        "return the value clamped to the range [low,high]",
        (arg("value"),arg("low"),arg("high")));
    PyImath::generate_bindings<clamp_op<float>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "clamp",
        "return the value clamped to the range [low,high]",
        (arg("value"),arg("low"),arg("high")));
    PyImath::generate_bindings<clamp_op<double>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "clamp",
        "return the value clamped to the range [low,high]",
        (arg("value"),arg("low"),arg("high")));

    def("cmp", IMATH_NAMESPACE::cmp<float>);
    def("cmp", IMATH_NAMESPACE::cmp<double>);

    def("cmpt", IMATH_NAMESPACE::cmpt<float>);
    def("cmpt", IMATH_NAMESPACE::cmpt<double>);

    def("iszero", IMATH_NAMESPACE::iszero<float>);
    def("iszero", IMATH_NAMESPACE::iszero<double>);

    def("equal", IMATH_NAMESPACE::equal<float, float, float>);
    def("equal", IMATH_NAMESPACE::equal<double, double, double>);

    PyImath::generate_bindings<floor_op<float>,boost::mpl::true_>(
        "floor",
        "return the closest integer less than or equal to 'value'",
        (arg("value")));
    PyImath::generate_bindings<floor_op<double>,boost::mpl::true_>(
        "floor",
        "return the closest integer less than or equal to 'value'",
        (arg("value")));

    PyImath::generate_bindings<ceil_op<float>,boost::mpl::true_>(
        "ceil",
        "return the closest integer greater than or equal to 'value'",
        (arg("value")));
    PyImath::generate_bindings<ceil_op<double>,boost::mpl::true_>(
        "ceil",
        "return the closest integer greater than or equal to 'value'",
        (arg("value")));

    PyImath::generate_bindings<trunc_op<float>,boost::mpl::true_>(
        "trunc",
        "return the closest integer with magnitude less than or equal to 'value'",
        (arg("value")));
    PyImath::generate_bindings<trunc_op<double>,boost::mpl::true_>(
        "trunc",
        "return the closest integer with magnitude less than or equal to 'value'",
        (arg("value")));

    PyImath::generate_bindings<divs_op,boost::mpl::true_,boost::mpl::true_>(
        "divs",
        "return x/y where the remainder has the same sign as x:\n"
        "    divs(x,y) == (abs(x) / abs(y)) * (sign(x) * sign(y))\n",
        (arg("x"),arg("y")));
    PyImath::generate_bindings<mods_op,boost::mpl::true_,boost::mpl::true_>(
        "mods",
        "return x%y where the remainder has the same sign as x:\n"
        "    mods(x,y) == x - y * divs(x,y)\n",
        (arg("x"),arg("y")));

    PyImath::generate_bindings<divp_op,boost::mpl::true_,boost::mpl::true_>(
        "divp",
        "return x/y where the remainder is always positive:\n"
        "    divp(x,y) == floor (double(x) / double (y))\n",
        (arg("x"),arg("y")));
    PyImath::generate_bindings<modp_op,boost::mpl::true_,boost::mpl::true_>(
        "modp",
        "return x%y where the remainder is always positive:\n"
        "    modp(x,y) == x - y * divp(x,y)\n",
        (arg("x"),arg("y")));

    PyImath::generate_bindings<bias_op,boost::mpl::true_,boost::mpl::true_>(
         "bias",
         "bias(x,b) is a gamma correction that remaps the unit interval such that bias(0.5, b) = b.",
         (arg("x"),arg("b")));

    PyImath::generate_bindings<gain_op,boost::mpl::true_,boost::mpl::true_>(
         "gain",
         "gain(x,g) is a gamma correction that remaps the unit interval with the property that gain(0.5, g) = 0.5.\n"
         "The gain function can be thought of as two scaled bias curves forming an 'S' shape in the unit interval.",
         (arg("x"),arg("g")));

    //
    // Vectorized utility functions
    //
    PyImath::generate_bindings<rotationXYZWithUpDir_op<float>,boost::mpl::true_,boost::mpl::true_,boost::mpl::true_>(
        "rotationXYZWithUpDir",
        "return the XYZ rotation vector that rotates 'fromDir' to 'toDir'"
        "using the up vector 'upDir'",
        args("fromDir","toDir","upDir"));
}

} // namespace PyImath