/*
 * ext.cc
 *
 *  Copyright (C) 2018 Diamond Light Source
 *
 *  Author: James Parkhurst
 *
 *  This code is distributed under the BSD license, a copy of which is
 *  included in the root directory of this package.
 */
#include <boost/python.hpp>
#include <boost/python/def.hpp>
#include <boost/math/distributions/chi_squared.hpp>
#include <scitbx/constants.h>
#include <scitbx/vec2.h>
#include <scitbx/vec3.h>
#include <scitbx/mat2.h>
#include <scitbx/mat3.h>
#include <scitbx/sym_mat2.h>
#include <scitbx/math/r3_rotation.h>
#include <scitbx/matrix/multiply.h>
#include <scitbx/matrix/eigensystem.h>
#include <dxtbx/model/experiment.h>
#include <dials/model/data/shoebox.h>
#include <dials/algorithms/profile_model/gaussian_rs/coordinate_system.h>
#include <dials/algorithms/shoebox/mask_code.h>
#include <dials/array_family/reflection_table.h>
#include <dials/error.h>

using namespace boost::python;

namespace dials { namespace algorithms { namespace boost_python {

  using dials::algorithms::profile_model::gaussian_rs::CoordinateSystem2d;
  using dials::model::Background;
  using dials::model::Foreground;
  using dials::model::Overlapped;
  using dials::model::Shoebox;
  using dials::model::Valid;
  using dxtbx::model::BeamBase;
  using dxtbx::model::Detector;
  using dxtbx::model::Experiment;
  using dxtbx::model::Panel;
  using scitbx::mat2;
  using scitbx::mat3;
  using scitbx::sym_mat2;
  using scitbx::vec2;
  using scitbx::vec3;
  using scitbx::af::int6;
  using scitbx::matrix::multiply_transpose;
  using scitbx::matrix::transpose_multiply;

  namespace detail {

    /**
     * Helper function to do matrix multiplication
     */
    double AT_B_A(vec2<double> A, mat2<double> B) {
      vec2<double> ATB = A * B;
      return ATB * A;
    }

    /**
     * Helper function to do matrix multiplication
     */
    double AT_B_A(vec3<double> A, mat3<double> B) {
      vec3<double> ATB = A * B;
      return ATB * A;
    }

  }  // namespace detail

  /**
   * Function to given chisq quantile value
   * @param k The degrees of freedom
   * @param p The probability
   */
  double chisq_pdf(int k, double x) {
    DIALS_ASSERT(k > 0);
    DIALS_ASSERT(x >= 0);
    boost::math::chi_squared_distribution<> dist(k);
    return boost::math::pdf(dist, x);
  }

  /**
   * Function to given chisq quantile value
   * @param k The degrees of freedom
   * @param p The probability
   */
  double chisq_quantile(int k, double p) {
    DIALS_ASSERT(k > 0);
    DIALS_ASSERT(p >= 0 && p <= 1);
    boost::math::chi_squared_distribution<> dist(k);
    return boost::math::quantile(dist, p);
  }

  /**
   * Perform the change of basis from lab coordinates to the kabsch coordinate
   * system
   * @param s0 The incident beam vector
   * @param s2 The diffracted beam vector
   * @return The change of basis matrix
   */
  mat3<double> compute_change_of_basis_operation(vec3<double> s0, vec3<double> s2) {
    const double TINY = 1e-7;
    DIALS_ASSERT((s2 - s0).length() > TINY);
    vec3<double> e1 = s2.cross(s0).normalize();
    vec3<double> e2 = s2.cross(e1).normalize();
    vec3<double> e3 = s2.normalize();
    mat3<double> R(e1[0], e1[1], e1[2], e2[0], e2[1], e2[2], e3[0], e3[1], e3[2]);
    return R;
  }

  /**
   * Perform the change of basis from reciprocal space to a coordinate system of
   * orientated with the reciprocal lattice vector to the reflection
   * @param s0 The incident beam vector
   * @param r The reciprocal lattice vector
   * @return The change of basis matrix
   */
  mat3<double> compute_change_of_basis_operation2(vec3<double> s0, vec3<double> r) {
    vec3<double> e1 = r.cross(s0).normalize();
    vec3<double> e2 = r.cross(e1).normalize();
    vec3<double> e3 = r.normalize();
    mat3<double> R(e1[0], e1[1], e1[2], e2[0], e2[1], e2[2], e3[0], e3[1], e3[2]);
    return R;
  }

  /**
   * A class to predict the reflections
   */
  class PredictorBase {
  public:
    /**
     * Initialise the predictor
     * @param experiment The experiment
     * @param probability The probability
     */
    PredictorBase(Experiment experiment, double probability)
        : experiment_(experiment), probability_(probability) {
      DIALS_ASSERT(probability > 0 && probability < 1);
    }

    /**
     * Predict the reflections
     * @param h The list of miller indices
     * @returns The reflection table
     */
    af::reflection_table predict(af::const_ref<cctbx::miller::index<> > h) const {
      // Get the beam and detector
      const double TINY = 1e-7;
      DIALS_ASSERT(experiment_.get_beam() != NULL);
      DIALS_ASSERT(experiment_.get_crystal() != NULL);
      DIALS_ASSERT(experiment_.get_detector() != NULL);
      Detector detector = *experiment_.get_detector();

      // Compute quantile
      double quantile = chisq_quantile(3, probability_);

      // Get stuff from experiment
      mat3<double> A = experiment_.get_crystal()->get_A();
      vec3<double> s0 = experiment_.get_beam()->get_s0();
      Panel panel = detector[0];

      // Initialise some arrays
      af::shared<cctbx::miller::index<> > miller_indices;
      af::shared<bool> entering;
      af::shared<vec3<double> > s1_list;
      af::shared<vec3<double> > s2_list;
      af::shared<vec3<double> > xyzcalpx;
      af::shared<vec3<double> > xyzcalmm;
      af::shared<std::size_t> panel_list;
      af::shared<int> experiment_id;

      // Loop through the input miller indices
      for (std::size_t i = 0; i < h.size(); ++i) {
        // Compute the point and the distance from the Ewald sphere
        vec3<double> r = A * h[i];
        vec3<double> s2 = s0 + r;
        vec3<double> s3 = s2.normalize() * s0.length();

        // Invert the matrix
        mat3<double> sigma = get_sigma(s0, r);
        mat3<double> sigma_inv = sigma.inverse();

        // Compute distance
        double d = detail::AT_B_A(s3 - s2, sigma_inv);

        // If it is close enough then predict stuff
        if (d < quantile) {
          // Compute the rotation of the reflection
          mat3<double> R = compute_change_of_basis_operation(s0, s2);

          // Rotate the covariance matrix and s2 vector
          mat3<double> S = R * sigma * R.transpose();
          vec3<double> mu = R * s2;
          vec3<double> zaxis(0, 0, 1);
          DIALS_ASSERT(std::abs((mu.normalize() * zaxis) - 1) < TINY);

          // Partition the covariance matrix
          mat2<double> S11(S[0], S[1], S[3], S[4]);
          vec2<double> S12(S[2], S[5]);
          vec2<double> S21(S[6], S[7]);
          double S22 = S[8];

          // Partition the mean vector
          vec2<double> mu1(mu[0], mu[1]);
          double mu2 = mu[2];

          // Compute epsilon the distance to the Ewald sphere
          double epsilon = s0.length() - mu2;

          // Compute the mean of the conditional distribution
          DIALS_ASSERT(S22 > 0);
          double S22_inv = 1.0 / S22;
          vec2<double> mubar = mu1 + S12 * S22_inv * epsilon;

          // Compute the diffracted beam vector
          vec3<double> v(mubar[0], mubar[1], s0.length());
          vec3<double> s1 = R.transpose() * (v.normalize() * s0.length());

          try {
            // Do the panel ray intersection
            vec2<double> xymm = panel.get_ray_intersection(s1);
            vec2<double> xypx = panel.millimeter_to_pixel(xymm);

            // Append the stuff to arrays
            experiment_id.push_back(0);
            miller_indices.push_back(h[i]);
            entering.push_back(false);
            panel_list.push_back(0);
            s1_list.push_back(s1);
            s2_list.push_back(s2);
            xyzcalpx.push_back(vec3<double>(xypx[0], xypx[1], 0));
            xyzcalmm.push_back(vec3<double>(xymm[0], xymm[1], 0));
          } catch (dxtbx::error) {
            continue;
          }
        }
      }

      // Construct the reflection table
      af::reflection_table reflections(miller_indices.size());
      reflections["miller_index"] = miller_indices;
      reflections["entering"] = entering;
      reflections["s1"] = s1_list;
      reflections["s2"] = s2_list;
      reflections["xyzcal.px"] = xyzcalpx;
      reflections["xyzcal.mm"] = xyzcalmm;
      reflections["panel"] = panel_list;
      reflections["id"] = experiment_id;
      return reflections;
    }

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      throw DIALS_ERROR("Overload!");
    }

    Experiment experiment_;
    double probability_;
  };

  /**
   * The predictor for simple profile models
   */
  class PredictorSimple : public PredictorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    PredictorSimple(Experiment experiment, mat3<double> sigma, double probability)
        : PredictorBase(experiment, probability), sigma_(sigma) {}

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      return sigma_;
    }

    mat3<double> sigma_;
  };

  /**
   * The predictor for angular profile models
   */
  class PredictorAngular : public PredictorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    PredictorAngular(Experiment experiment, mat3<double> sigma, double probability)
        : PredictorBase(experiment, probability), sigma_(sigma) {}

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      const double TINY = 1e-7;
      mat3<double> Q = compute_change_of_basis_operation2(s0, r);
      vec3<double> zaxis(0, 0, 1);
      DIALS_ASSERT(std::abs(((Q * r.normalize()) * zaxis) - 1) < TINY);
      return (Q.transpose() * sigma_ * Q);
    }

    mat3<double> sigma_;
  };

  /**
   * A class to compute the bounding box
   */
  class BBoxCalculatorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    BBoxCalculatorBase(Experiment experiment, double probability, int border)
        : experiment_(experiment), probability_(probability), border_(border) {
      DIALS_ASSERT(border > 0);
      DIALS_ASSERT(probability < 1.0);
      DIALS_ASSERT(probability > 0);
    }

    /**
     * Compute the bounding box
     * @param reflections The reflection table
     */
    void compute(af::reflection_table reflections) const {
      // Get some array from the reflection table
      af::const_ref<vec3<double> > s1 = reflections["s1"];
      af::const_ref<vec3<double> > s2 = reflections["s2"];
      af::const_ref<std::size_t> panel = reflections["panel"];
      af::ref<int6> bbox = reflections["bbox"];

      // Compute quantile
      double D = chisq_quantile(2, probability_);

      // Compute mask for all reflections
      for (std::size_t i = 0; i < reflections.size(); ++i) {
        bbox[i] = compute_single(s1[i], s2[i], panel[i], D);
      }
    }

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      throw DIALS_ERROR("Overload!");
    }

    /**
     * Compute the bounding box for a single reflection
     * @param s1 The diffracted beam vector touching the Ewald sphere
     * @param s2 The diffracted beam vector to the centre of the rlp
     * @param panel_id The panel id
     * @param D the distance
     */
    int6 compute_single(vec3<double> s1,
                        vec3<double> s2,
                        std::size_t panel_id,
                        double D) const {
      const double TINY = 1e-7;
      DIALS_ASSERT(s1.length() > 0);
      DIALS_ASSERT(s2.length() > 0);
      DIALS_ASSERT(D > 0);

      // Get the beam and detector
      DIALS_ASSERT(experiment_.get_beam() != NULL);
      DIALS_ASSERT(experiment_.get_detector() != NULL);
      Detector detector = *experiment_.get_detector();

      // The the indicident beam vector
      vec3<double> s0 = experiment_.get_beam()->get_s0();
      double s0_length = s0.length();
      DIALS_ASSERT(std::abs(s0_length - s1.length()) < TINY);

      vec3<double> r = s2 - s0;

      // Compute the change of basis for the reflection
      mat3<double> R = compute_change_of_basis_operation(s0, s2);

      // Rotate the covariance matrix and s2 vector
      mat3<double> S = R * get_sigma(s0, r) * R.transpose();
      vec3<double> mu = R * s2;
      vec3<double> zaxis(0, 0, 1);
      DIALS_ASSERT(std::abs((mu.normalize() * zaxis) - 1) < TINY);

      // Partition the covariance matrix
      mat2<double> S11(S[0], S[1], S[3], S[4]);
      vec2<double> S12(S[2], S[5]);
      vec2<double> S21(S[6], S[7]);
      double S22 = S[8];

      // Partition the mean vector
      vec2<double> mu1(mu[0], mu[1]);
      double mu2 = mu[2];

      // Compute epsilon the distance to the Ewald sphere
      double epsilon = s0.length() - mu2;

      // Compute the mean of the conditional distribution
      DIALS_ASSERT(S22 > 0);
      double S22_inv = 1.0 / S22;
      vec2<double> mubar = mu1 + S12 * S22_inv * epsilon;

      // Compute the covariance of the conditional distribution
      mat2<double> S12_S21;
      multiply_transpose(&S12[0], &S21[0], 2, 1, 2, &S12_S21[0]);
      mat2<double> Sbar = S11 - S12_S21 * S22_inv;

      // Get the panel model
      Panel panel = detector[panel_id];

      // Compute the the min/max bounding box on the ellipse
      double A1 = Sbar[0];
      double A2 = Sbar[3];
      DIALS_ASSERT(A1 >= 0 && A2 >= 0);
      double delta1 = std::sqrt(D * A1);
      double delta2 = std::sqrt(D * A2);

      // The corner points in conditional space
      vec2<double> p1 = mubar + vec2<double>(-delta1, -delta2);
      vec2<double> p2 = mubar + vec2<double>(-delta1, +delta2);
      vec2<double> p3 = mubar + vec2<double>(+delta1, -delta2);
      vec2<double> p4 = mubar + vec2<double>(+delta1, +delta2);

      // The corner points in lab space
      vec3<double> sp1 = R.transpose() * vec3<double>(p1[0], p1[1], s0.length());
      vec3<double> sp2 = R.transpose() * vec3<double>(p2[0], p2[1], s0.length());
      vec3<double> sp3 = R.transpose() * vec3<double>(p3[0], p3[1], s0.length());
      vec3<double> sp4 = R.transpose() * vec3<double>(p4[0], p4[1], s0.length());

      // The xy coordinates on the detector
      vec2<double> xy1 = panel.get_ray_intersection_px(sp1);
      vec2<double> xy2 = panel.get_ray_intersection_px(sp2);
      vec2<double> xy3 = panel.get_ray_intersection_px(sp3);
      vec2<double> xy4 = panel.get_ray_intersection_px(sp4);

      // Get the min and max x and y coords
      double xmin = std::min(std::min(xy1[0], xy2[0]), std::min(xy3[0], xy4[0]));
      double ymin = std::min(std::min(xy1[1], xy2[1]), std::min(xy3[1], xy4[1]));
      double xmax = std::max(std::max(xy1[0], xy2[0]), std::max(xy3[0], xy4[0]));
      double ymax = std::max(std::max(xy1[1], xy2[1]), std::max(xy3[1], xy4[1]));

      // Create bounding box
      int x0 = ((int)std::floor(xmin)) - border_;
      int y0 = ((int)std::floor(ymin)) - border_;
      int x1 = ((int)std::ceil(xmax)) + border_;
      int y1 = ((int)std::ceil(ymax)) + border_;
      DIALS_ASSERT(x1 > x0);
      DIALS_ASSERT(y1 > y0);
      return int6(x0, x1, y0, y1, 0, 1);
    }

    Experiment experiment_;
    double probability_;
    int border_;
  };

  /**
   * The bbox calculator for simple profile models
   */
  class BBoxCalculatorSimple : public BBoxCalculatorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    BBoxCalculatorSimple(Experiment experiment,
                         mat3<double> sigma,
                         double probability,
                         int border)
        : BBoxCalculatorBase(experiment, probability, border), sigma_(sigma) {}

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      return sigma_;
    }

    mat3<double> sigma_;
  };

  /**
   * The bbox calculator for angular profile models
   */
  class BBoxCalculatorAngular : public BBoxCalculatorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    BBoxCalculatorAngular(Experiment experiment,
                          mat3<double> sigma,
                          double probability,
                          int border)
        : BBoxCalculatorBase(experiment, probability, border), sigma_(sigma) {}

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      const double TINY = 1e-7;
      mat3<double> Q = compute_change_of_basis_operation2(s0, r);
      vec3<double> zaxis(0, 0, 1);
      DIALS_ASSERT(std::abs(((Q * r.normalize()) * zaxis) - 1) < TINY);
      return (Q.transpose() * sigma_ * Q);
    }

    mat3<double> sigma_;
  };

  /**
   * A class to compute the reflection mask
   */
  class MaskCalculatorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    MaskCalculatorBase(Experiment experiment, double probability)
        : experiment_(experiment), probability_(probability) {
      DIALS_ASSERT(probability < 1.0);
      DIALS_ASSERT(probability > 0);
    }

    /**
     * Compute the reflection mask
     * @param reflections The reflection table
     */
    void compute(af::reflection_table reflections) const {
      // Get some array from the reflection table
      af::const_ref<vec3<double> > s1 = reflections["s1"];
      af::const_ref<vec3<double> > s2 = reflections["s2"];
      af::ref<Shoebox<> > sbox = reflections["shoebox"];

      // Compute quantile
      double D = chisq_quantile(2, probability_);

      // Compute mask for all reflections
      for (std::size_t i = 0; i < reflections.size(); ++i) {
        compute_single(s1[i], s2[i], sbox[i], D);
      }
    }

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      throw DIALS_ERROR("Overload!");
    }

    /**
     * Compute the bounding box for a single reflection
     * @param s1 The diffracted beam vector touching the Ewald sphere
     * @param s2 The diffracted beam vector to the centre of the rlp
     * @param sbox The shoebox
     * @param D the distance
     */
    void compute_single(vec3<double> s1,
                        vec3<double> s2,
                        Shoebox<> sbox,
                        double D) const {
      const double TINY = 1e-7;
      DIALS_ASSERT(s1.length() > 0);
      DIALS_ASSERT(s2.length() > 0);
      DIALS_ASSERT(sbox.is_consistent());
      DIALS_ASSERT(D > 0);

      // Get the beam and detector
      DIALS_ASSERT(experiment_.get_beam() != NULL);
      DIALS_ASSERT(experiment_.get_detector() != NULL);
      Detector detector = *experiment_.get_detector();

      // The the indicident beam vector
      vec3<double> s0 = experiment_.get_beam()->get_s0();
      double s0_length = s0.length();
      DIALS_ASSERT(std::abs(s0_length - s1.length()) < TINY);

      vec3<double> r = s2 - s0;

      // Compute the change of basis for the reflection
      mat3<double> R = compute_change_of_basis_operation(s0, s2);

      // Rotate the covariance matrix and s2 vector
      mat3<double> S = R * get_sigma(s0, r) * R.transpose();
      vec3<double> mu = R * s2;
      vec3<double> zaxis(0, 0, 1);
      DIALS_ASSERT(std::abs((mu.normalize() * zaxis) - 1) < TINY);

      // Partition the covariance matrix
      mat2<double> S11(S[0], S[1], S[3], S[4]);
      vec2<double> S12(S[2], S[5]);
      vec2<double> S21(S[6], S[7]);
      double S22 = S[8];

      // Partition the mean vector
      vec2<double> mu1(mu[0], mu[1]);
      double mu2 = mu[2];

      // Compute epsilon the distance to the Ewald sphere
      double epsilon = s0.length() - mu2;

      // Compute the mean of the conditional distribution
      DIALS_ASSERT(S22 > 0);
      double S22_inv = 1.0 / S22;
      vec2<double> mubar = mu1 + S12 * S22_inv * epsilon;

      // Compute the covariance of the conditional distribution
      mat2<double> S12_S21;
      multiply_transpose(&S12[0], &S21[0], 2, 1, 2, &S12_S21[0]);
      mat2<double> Sbar = S11 - S12_S21 * S22_inv;
      mat2<double> Sbar_inv = Sbar.inverse();

      // Get the mask array
      af::ref<int, af::c_grid<3> > mask = sbox.mask.ref();

      // Get the bounding box
      int x0 = sbox.bbox[0];
      int x1 = sbox.bbox[1];
      int y0 = sbox.bbox[2];
      int y1 = sbox.bbox[3];
      int z0 = sbox.bbox[4];
      int z1 = sbox.bbox[5];
      DIALS_ASSERT(x0 < x1);
      DIALS_ASSERT(y0 < y1);
      DIALS_ASSERT(z0 < z1);
      DIALS_ASSERT(z1 - z0 == 1);

      // Create the coordinate system
      CoordinateSystem2d cs(s0, s2);

      // Get the panel model
      Panel panel = detector[sbox.panel];

      // Set the mask value for each pixel
      DIALS_ASSERT(mask.accessor()[0] == 1);
      for (std::size_t j = 0; j < mask.accessor()[1]; ++j) {
        for (std::size_t i = 0; i < mask.accessor()[2]; ++i) {
          int ii = x0 + ((int)i);
          int jj = y0 + ((int)j);

          // The pixel coordinates of the corners
          vec2<double> p1(ii, jj);
          vec2<double> p2(ii + 1, jj);
          vec2<double> p3(ii, jj + 1);
          vec2<double> p4(ii + 1, jj + 1);

          // The lab coordinates of the pixel corners
          vec3<double> sp1 = panel.get_pixel_lab_coord(p1).normalize() * s0_length;
          vec3<double> sp2 = panel.get_pixel_lab_coord(p2).normalize() * s0_length;
          vec3<double> sp3 = panel.get_pixel_lab_coord(p3).normalize() * s0_length;
          vec3<double> sp4 = panel.get_pixel_lab_coord(p4).normalize() * s0_length;

          // The coordinates in kabsch space
          vec2<double> x1 = cs.from_beam_vector(sp1);
          vec2<double> x2 = cs.from_beam_vector(sp2);
          vec2<double> x3 = cs.from_beam_vector(sp3);
          vec2<double> x4 = cs.from_beam_vector(sp4);

          // The distance from the mean
          double d1 = detail::AT_B_A(x1 - mubar, Sbar_inv);
          double d2 = detail::AT_B_A(x2 - mubar, Sbar_inv);
          double d3 = detail::AT_B_A(x3 - mubar, Sbar_inv);
          double d4 = detail::AT_B_A(x4 - mubar, Sbar_inv);

          // The minimum distance
          if (std::min(std::min(d1, d2), std::min(d3, d4)) < D) {
            mask(0, j, i) |= Foreground;
          } else {
            mask(0, j, i) |= Background;
          }
        }
      }
    }

    Experiment experiment_;
    double probability_;
  };

  /**
   * The mask calculator for simple profile models
   */
  class MaskCalculatorSimple : public MaskCalculatorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    MaskCalculatorSimple(Experiment experiment, mat3<double> sigma, double probability)
        : MaskCalculatorBase(experiment, probability), sigma_(sigma) {}

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      return sigma_;
    }

    mat3<double> sigma_;
  };

  /**
   * The mask calculator for angular profile models
   */
  class MaskCalculatorAngular : public MaskCalculatorBase {
  public:
    /**
     * Initialise the class
     * @param experiment The experiment
     * @param sigma The covariance matrix
     */
    MaskCalculatorAngular(Experiment experiment, mat3<double> sigma, double probability)
        : MaskCalculatorBase(experiment, probability), sigma_(sigma) {}

  protected:
    /**
     * Get the sigma for a reflection
     */
    virtual mat3<double> get_sigma(vec3<double> s0, vec3<double> r) const {
      const double TINY = 1e-7;
      mat3<double> Q = compute_change_of_basis_operation2(s0, r);
      vec3<double> zaxis(0, 0, 1);
      DIALS_ASSERT(std::abs(((Q * r.normalize()) * zaxis) - 1) < TINY);
      return (Q.transpose() * sigma_ * Q);
    }

    mat3<double> sigma_;
  };

  BOOST_PYTHON_MODULE(dials_algorithms_profile_model_ellipsoid_ext) {
    def("chisq_quantile", &chisq_quantile);
    def("chisq_pdf", &chisq_pdf);

    class_<PredictorBase>("PredictorBase", no_init)
      .def("predict", &PredictorBase::predict);

    class_<PredictorSimple, bases<PredictorBase> >("PredictorSimple", no_init)
      .def(init<Experiment, mat3<double>, double>());

    class_<PredictorAngular, bases<PredictorBase> >("PredictorAngular", no_init)
      .def(init<Experiment, mat3<double>, double>());

    class_<BBoxCalculatorBase>("BBoxCalculatorBase", no_init)
      .def("compute", &BBoxCalculatorBase::compute);

    class_<BBoxCalculatorSimple, bases<BBoxCalculatorBase> >("BBoxCalculatorSimple",
                                                             no_init)
      .def(init<Experiment, mat3<double>, double, int>());

    class_<BBoxCalculatorAngular, bases<BBoxCalculatorBase> >("BBoxCalculatorAngular",
                                                              no_init)
      .def(init<Experiment, mat3<double>, double, int>());

    class_<MaskCalculatorBase>("MaskCalculatorBase", no_init)
      .def("compute", &MaskCalculatorBase::compute);

    class_<MaskCalculatorSimple, bases<MaskCalculatorBase> >("MaskCalculatorSimple",
                                                             no_init)
      .def(init<Experiment, mat3<double>, double>());

    class_<MaskCalculatorAngular, bases<MaskCalculatorBase> >("MaskCalculatorAngular",
                                                              no_init)
      .def(init<Experiment, mat3<double>, double>());
  }

}}}  // namespace dials::algorithms::boost_python


//
// Created by Сергей Кривонос on 01.09.17.
//
#include "Integer.h"

#include "i.h"
#include "Infinity.h"
#include "Exponentiation.h"
#include "Fraction.h"
#include "Modulo.h"
#include "PrincipalSurd.h"
#include "Product.h"
#include "Sum.h"

#include <rt/Prime.h>
#include <rt/tasq.h>

#include <algorithm>
#include <codecvt>
#include <cmath>
#include <fstream>
#include <iostream>

#include <boost/archive/binary_oarchive.hpp>
#if __cplusplus >= 201700
#include <random>
#ifndef __GNUC__
namespace std {
    template< class It >
    void random_shuffle( It f, It l )
    {
        std::random_device rd;
        std::mt19937 g(rd());
        std::shuffle(f, l, g);
    }
}
#endif
#endif
#ifdef OPENMIND_USE_OPENCL
#include <boost/compute.hpp>
#endif
#include <boost/functional/hash.hpp>
#include <boost/numeric/conversion/converter.hpp>
#include <boost/math/special_functions/prime.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/detail/default_ops.hpp>
#include <boost/multiprecision/detail/integer_ops.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/multiprecision/miller_rabin.hpp>

using boost::multiprecision::cpp_int;

namespace omnn{
namespace math {

    const Integer::ranges_t Integer::empty_zero_zone;

    Integer::Integer(const Fraction& f)
    : arbitrary(f.getNumerator().ca() / f.getDenominator().ca())
    {

    }

    Integer::operator a_int() const {
        return arbitrary;
    }

    a_int& Integer::a() {
        return arbitrary;
    }

    const a_int& Integer::ca() const {
        return arbitrary;
    }

    Integer::operator int64_t() const {
        return boost::numeric_cast<int64_t>(arbitrary);
    }

    Integer::operator uint64_t() const {
        return boost::numeric_cast<uint64_t>(arbitrary);
    }

    Valuable::YesNoMaybe Integer::IsEven() const {
        return (arbitrary >= 0 ? arbitrary : -arbitrary) & 1 ? YesNoMaybe::No : YesNoMaybe::Yes;
    }

    Valuable Integer::operator -() const
    {
        return Integer(-arbitrary);
    }

    Valuable& Integer::operator +=(const Valuable& v)
    {
        if (v.IsInt())
        {
            arbitrary += v.ca();
            hash = std::hash<base_int>()(arbitrary);
        }
        else
        {
            Become(v + *this);
        }
        return *this;
    }

    Valuable& Integer::operator +=(int v)
    {
        arbitrary += v;
        hash = std::hash<base_int>()(arbitrary);
        return *this;
    }

    Valuable& Integer::operator *=(const Valuable& v)
    {
        if (v.IsInt())
        {
            arbitrary *= v.ca();
            hash = std::hash<base_int>()(arbitrary);
        }
        else
        {
            // all other types should handle multiplication by Integer
            Become(v**this);
        }
        return *this;
    }

    bool Integer::MultiplyIfSimplifiable(const Valuable& v)
    {
        auto is = v.IsInt();
        if(is) {
            arbitrary *= v.ca();
            hash = std::hash<base_int>()(arbitrary);
        } else if (v.IsVa()) {
        } else {
            auto s = v.IsMultiplicationSimplifiable(*this);
            is = s.first;
            if(is)
                Become(std::move(s.second));
        }
        return is;
    }

    std::pair<bool,Valuable> Integer::IsMultiplicationSimplifiable(const Valuable& v) const
    {
        std::pair<bool,Valuable> is;
        is.first = v.IsInt() || v.IsFraction();
        if (is.first) {
            is.second = v * *this;
            if (is.second.Complexity() > v.Complexity())
                IMPLEMENT; // TODO: not a simple fraction
        } else if (v.IsVa() || v.IsExponentiation()) {
        } else {
            is = v.IsMultiplicationSimplifiable(*this);
        }
        return is;
    }

    bool Integer::SumIfSimplifiable(const Valuable& v)
    {
        auto is = v.IsInt();
        if(is) {
            arbitrary += v.ca();
            hash = std::hash<base_int>()(arbitrary);
        } else {
            auto s = v.IsSummationSimplifiable(*this);
            is = s.first;
            if(is)
                Become(std::move(s.second));
        }
        return is;
    }

    std::pair<bool,Valuable> Integer::IsSummationSimplifiable(const Valuable& v) const
    {
        std::pair<bool,Valuable> is;
        is.first = v.IsInt();
        if (is.first) {
            is.second = v + *this;
        } else if (v.IsVa() || v.IsExponentiation()) {
        } else if (v.IsProduct()) {
        } else {
            is = v.IsSummationSimplifiable(*this);
            if (is.first && is.second.Complexity() > v.Complexity())
                LOG_AND_IMPLEMENT("Simplification complexidy exceeds source complexity: " << v << "   +   " << str() << "   =   " << is.second);
        }
        return is;
    }

    Valuable& Integer::operator /=(const Valuable& v)
    {
        if (v.IsInt())
        {
            auto& a = v.ca();
            if (a == 0) {
                if (arbitrary < 0) {
                    Become(MInfinity());
                } else if (arbitrary > 0) {
                    Become(Infinity());
                } else {
                    Become(NaN());
                }
            } else if (arbitrary % a == 0) {
                arbitrary /= a;
                hash = std::hash<base_int>()(arbitrary);
            } else {
                Become(Fraction(*this, v));
            }
        }
        else if(v.FindVa())
            *this *= v^-1;
        else
            Become(Fraction(*this,v));
        return *this;
    }

    Valuable& Integer::operator %=(const Valuable& v)
    {
        if (v.IsInt())
        {
            arbitrary %= v.ca();
            hash = std::hash<base_int>()(arbitrary);
        }
        else
        {
            Become(Modulo(*this, v));
        }
        return *this;
    }

    Valuable& Integer::operator --()
    {
        arbitrary--;
        hash = std::hash<base_int>()(arbitrary);
        return *this;
    }

    Valuable& Integer::operator ++()
    {
        arbitrary++;
        hash = std::hash<base_int>()(arbitrary);
        return *this;
    }

    Integer::operator int() const
    {
        return boost::numeric_cast<int>(arbitrary);
    }

    Integer::operator uint32_t() const
    {
        return boost::numeric_cast<uint32_t>(arbitrary);
    }

    Integer::operator double() const
    {
        return boost::numeric_cast<double>(arbitrary);
    }

    Integer::operator long double() const
    {
        return boost::numeric_cast<long double>(arbitrary);
    }

    Integer::operator unsigned char() const
    {
        return boost::numeric_cast<unsigned char>(arbitrary);
    }

    Valuable Integer::bit(const Valuable& n) const
    {
        if (n.IsInt()) {
            if (arbitrary < 0) {
                if (arbitrary == -1) {
                    return 1;
                }
                IMPLEMENT
            }
            unsigned N = static_cast<unsigned>(n);
            return static_cast<int>(bit_test(arbitrary, N));
        }
        else
            IMPLEMENT;
    }

    Valuable& Integer::shl(const Valuable& n)
    {
        if (n.IsInt())
            arbitrary = arbitrary << static_cast<int>(n);
        else
            base::shl(n);
        return *this;
    }

    Valuable& Integer::shr(const Valuable& n)
    {
        if (n.IsInt())
            arbitrary = arbitrary >> static_cast<int>(n);
        else
            base::shl(n);
        return *this;
    }

    Valuable Integer::Shr() const
    {
        return Integer(decltype(arbitrary)(arbitrary>>1));
    }

    Valuable Integer::Shr(const Valuable& n) const
    {
        if (!n.IsInt()) {
            IMPLEMENT
        }
        return Integer(decltype(arbitrary)(arbitrary>>static_cast<unsigned>(n)));
    }

    Valuable Integer::Or(const Valuable& n, const Valuable& v) const
    {
        IMPLEMENT
    }
    Valuable Integer::And(const Valuable& n, const Valuable& v) const
    {
        if (v.IsInt()) {
            auto mask = std::move(--((vo<2>() ^ n).a()));
            if (arbitrary < v.ca()) {
                mask &= arbitrary;
                mask &= v.ca();
            } else {
                mask &= v.ca();
                mask &= arbitrary;
            }
            return Valuable(std::move(mask));
        }
        else
            return base::And(n, v);
    }
    Valuable Integer::Xor(const Valuable& n, const Valuable& v) const
    {
        IMPLEMENT
    }
    Valuable Integer::Not(const Valuable& n) const
    {
        return Integer(~arbitrary);
    }

    std::pair<Valuable,Valuable> Integer::GreatestCommonExp(const Valuable &e) const {
        // test : 50_v.GCE(2) == 5_v
        //        32_v.GCE(2) == 4_v
        //  which is needed to be powered into 2 to get gretest devisor that may be powered into 2 to get devisor of the initial value
        if (e == constants::one)
            return {*this,*this};

        auto xFactors = Facts();
        std::sort(xFactors.begin(), xFactors.end());
        while(xFactors.size() > 1) {
            auto&& xFactor = std::move(xFactors.back());
            if(xFactor > constants::one) {
                if(e == constants::two){
                    Valuable v = boost::multiprecision::sqrt(xFactor.ca());
                    if((v^e) == xFactor)
                        return {std::move(v),std::move(xFactor)};
                } else if (e < constants::zero) {
                    auto me = -e;
                    IMPLEMENT
                } else {
                    IMPLEMENT
//                    auto v = boost::multiprecision::pow(xFactor, 1/e);
                }
            }
            xFactors.pop_back();
        }
        return {constants::one, constants::one};
    }

    Valuable& Integer::operator^=(const Valuable& v)
    {
        if(v == constants::one)
            return *this;
        if(arbitrary == 0 || (arbitrary == 1 && v.IsInt()))
        {
            if (v == 0) {
                IMPLEMENT; //NaN
            }
            return *this;
        } else if ((arbitrary == 1 || arbitrary == -1) && v.IsSimpleFraction()) {
            if(!v.as<Fraction>().getDenominator().IsEven())
                return *this;
            else
                return Become(Exponentiation{*this,v});
        }
        if(v.IsInt())
        {
// FIXME:     arbitrary = boost::multiprecision::pow(a(), v.ca());
//            hash = std::hash<base_int>()(arbitrary);
//            return *this;
            if (v != 0_v) {
                if (v > 1) {
                    Valuable x = *this;
                    Valuable n = v;
                    if (n < 0_v)
                    {
                        x = 1_v / x;
                        n = -n;
                    }
                    if (n == 0_v)
                    {
                        arbitrary = 1;
                        hash = std::hash<base_int>()(arbitrary);
                        return *this;
                    }
                    auto y = 1_v;
                    while(n > 1)
                    {
                        auto nIsInt = n.IsInt();
                        if (!nIsInt) IMPLEMENT;
                        if (n.ca() & 1)
                        {
                            y *= x;
                            --n;
                        }
                        x.sq();
                        n /= 2;
                    }
                    x *= y;
                    if (x.IsInt()) {
                        arbitrary = std::move(x.a());
                        hash = std::hash<base_int>()(arbitrary);
                    }
                    else
                        return Become(std::move(x));
                } else if (v != 1) {
                    return Become(Exponentiation{*this, v});
                }
            }
            else { // zero
                if (arbitrary == 0)
                    throw "NaN"; // feel free to handle this properly
                else
                {
                    arbitrary = 1;
                    hash = std::hash<base_int>()(arbitrary);
                }
            }
        }
        else if(v.IsSimpleFraction())
        {
            auto& f = v.as<Fraction>();
            auto& nu = f.getNumerator();
            Valuable mn;
            auto nlz = nu < 0;
            if(nlz)
                mn = -nu;
            auto n = std::cref(nlz ? mn : nu);
            auto dn = nlz ? -f.getDenominator() : f.getDenominator();

            auto numeratorIsOne = n == 1_v;
            if (!numeratorIsOne){
                *this ^= n;
                n = std::cref(constants::one);
            }

            auto isNeg = operator<(0_v);
            auto signs = 0; //dimmensions
            while(dn.IsEven() == YesNoMaybe::Yes) {
                auto minus = arbitrary < 0;
                auto _ = boost::multiprecision::sqrt(minus ? -arbitrary : arbitrary);
                auto _sq = _ * _;
                if (_sq == boost::multiprecision::abs(arbitrary)){
                    arbitrary = _;
    //                Become(isNeg ? -operator-().Sqrt() : Sqrt());
                    dn /= 2;
                    ++signs;
                } else {
//                    IMPLEMENT
                    if(n!=1_v)
                        IMPLEMENT;
                    auto gce = GreatestCommonExp(dn);
                    return Become(gce.first*Exponentiation{operator/=(gce.second),n/dn});
                }
            }
            if(signs)
                hash = std::hash<base_int>()(arbitrary);

            auto dnSubZ = dn < 0;
            if(dnSubZ)
                dn = -dn;
            if(dn != 1_v) {
                auto even = dn.IsEven();
                if(even == YesNoMaybe::Yes){
                    Valuable x = *this;
                    if(x<0_v){
                        Valuable exp;
                        if (!numeratorIsOne) {
                            *this ^= n;
                            exp = 1_v / dn;
                        }
                        auto& exponentiating = numeratorIsOne ? v : exp;
                        auto xFactors = FactSet();
                        auto rb = xFactors.rbegin(), re = xFactors.rend();
                        for (auto it = rb; it != re; ++it) {
                            auto& xFactor = *it;
                            if(xFactor > 1_v /* && !operator==(xFactor) */){
                                auto e = xFactor ^ dn;
                                if(operator==(e))
                                    return Become(e*(1_v^exponentiating));
                                auto f = xFactor ^ exponentiating;
                                if(!f.IsInt())
                                    f /= 1_v ^ exponentiating;
                                if(f.IsInt())
                                    return Become(f*((x/xFactor)^exponentiating));
                            }
                        }
                        IMPLEMENT
                    }
                }
//                else if(dn==2_v) {
//                    return Become(Sqrt()*(1^(1_v/2)));
//                }

                Valuable nroot;
                bool rootFound = false;
                Valuable left =0, right = *this;

                while (!rootFound)
                {
                    auto d = right - left;
                    d -= d % 2;
                    if (d!=0) {
                        nroot = left + d / 2;
                        auto result = nroot ^ dn;
                        rootFound = result == *this;
                        if (rootFound)
                            break;
                        else
                            if (result > *this)
                                right = nroot;
                            else
                                left = nroot;
                    }
                    else {
                        nroot = Exponentiation(*this, 1_v/dn);
                        break;
                    }
                    // *this ^ 1/dn  == (nroot^dn + t)^ 1/dn
                    // this == nroot^dn +
                    // TODO : IMPLEMENT//return Become(Sum {nroot, (*this-(nroot^dn))^(1/dn)});
                }
                return Become(std::move(nroot));
            }
            if(dnSubZ)
                Become(1_v / *this);
            if(signs) {
                return operator*=((isNeg?-1_v:1_v)^(1_v/(2_v^signs)));
            }
        }
        else
            return Become(Exponentiation(*this, v));

        optimize();
        return *this;
    }

    Valuable& Integer::d(const Variable& x)
    {
        arbitrary = 0;
        return *this;
    }

    bool Integer::IsComesBefore(const Valuable& v) const
    {
        return v.IsInt() && *this > v;
    }

    Valuable Integer::InCommonWith(const Valuable& v) const
    {
        return (v.IsInt() && v!=0_v && *this!=0_v) ? boost::gcd(v.ca(),ca()) : 1_v;
    }

    // sqrt using boost
    Valuable Integer::Sqrt() const
    {
        auto minus = arbitrary < 0;
        auto _ = boost::multiprecision::sqrt(minus ? -arbitrary : arbitrary); // integer square root
        auto _sq = _ * _;
        if (_sq != boost::multiprecision::abs(arbitrary)){ // no integer square root
            auto d = GreatestCommonExp(2);
            if (d.second != 1)
                return (*this / d.second).Sqrt() * d.first;
            else
            {
                return Valuable(std::make_shared<PrincipalSurd>(*this));
                LOG_AND_IMPLEMENT(str() << " integer square root is " << _ << " and " << _ << "^2=" << _sq); // implement radicals support
            }
        }
        return minus ? constant::i * _ : _;
    }

    // Sqrt using rational root test
//    Valuable Integer::Sqrt() const
//    {
//        auto minus = arbitrary < 0;
//
//        // build an equation, find using RRT:
//        Variable x;
//        Valuable eq = *this;
//        auto _ = x.Sq();
//        eq += minus ? _ : -_;
//        eq.SetView(View::Solving);
//
//        auto esurrto = enforce_solve_using_rational_root_test_only;
//        enforce_solve_using_rational_root_test_only = true;
//        auto sols = eq.Solutions(x);
//        enforce_solve_using_rational_root_test_only = esurrto;
//
//        if(!sols.size()){
//            IMPLEMENT
//        }
//        auto it = sols.begin();
//        if(*it < 0)
//            ++it;
//        _ = *it;
//
//        return minus ? constant::i * _ : _;
//    }

    std::wstring Integer::save(const std::wstring& f) const
    {
        using namespace std;
        using convert_typeX = codecvt_utf8<wchar_t>;
        std::wstring_convert<convert_typeX, wchar_t> c;
        std::ofstream s(c.to_bytes(f));
        boost::archive::binary_oarchive a(s);
        a & arbitrary;
        return f;
    }

    Valuable Integer::Sign() const {
        return arbitrary.sign();
    }

    bool Integer::operator <(const Valuable& v) const
    {
        if (v.IsInt())
            return arbitrary < v.ca();
        else if (v.IsFraction())
            return !(v < *this) && *this!=v;
        else if(v.IsMInfinity())
            return {};
        else if(v.IsInfinity())
            return true;
        else if (!v.FindVa()) {
            double _1 = boost::numeric_cast<double>(arbitrary);
            double _2 = static_cast<double>(v);
            if(_1 == _2) {
                IMPLEMENT
            }
            return _1 < _2;
       } else
            return base::operator <(v);
    }

    bool Integer::operator ==(const Valuable& v) const
    {
        if (v.IsInt())
            return Hash() == v.Hash() && arbitrary == v.ca();
        else if(v.FindVa())
            return false;
        else
            return v.operator==(*this);
    }

    std::ostream& Integer::print(std::ostream& out) const
    {
        return out << arbitrary;
    }

    std::wostream& Integer::print(std::wostream& out) const
    {
        return out << arbitrary.str().c_str();
    }

    Valuable Integer::calcFreeMember() const
    {
        return *this;
    }

    std::deque<Valuable> Integer::Facts() const
    {
        std::deque<Valuable> f;
        Factorization([&](auto& v){
            f.push_back(v);
            return false;
        }, abs());
        return f;
    }

	void Integer::factorial() {
        if (arbitrary == 0)
            arbitrary = 1;
        else for (auto i = arbitrary; i-- > 1;)
            arbitrary *= i;
        hash = std::hash<base_int>()(arbitrary);
	}

    bool Integer::IsPrime() const
    {
        auto is = //boost::multiprecision::miller_rabin_test(boost::numeric_cast<uint64_t>(ca()), 3) &&
            !Factorization(
            [&](auto& v) {
                auto isPrimeFactor = v == 0_v || v == 1_v || v == *this;
                auto stop = !isPrimeFactor;
                return stop;
            },
            abs());
        return is;
    }

    std::set<Valuable> Integer::SimpleFactsSet() const {
        std::set<Valuable> f;
        SimpleFactorization(
            [&](auto& v) {
                f.emplace(std::move(v));
                return false;
            },
            abs());
        f.erase(0);
        return f;
    }

    std::set<Valuable> Integer::FactSet() const {
        std::set<Valuable> f;
        Factorization(
            [&](auto& v) {
                f.emplace(std::move(v));
                return false;
            },
            abs());
        f.erase(0);
        return f;
    }

    bool Integer::SimpleFactorization(const std::function<bool(const Valuable&)>& f, const Valuable& max,
                                const ranges_t& zz) const {
        auto h = arbitrary;
        if(h < 0)
            h = -h;
        if (f(0)) {
            return true;
        }
        bool scanz = zz.second.size();
        auto scanIt = zz.second.end();
        Valuable up = Integer(h);
        if (up > max) up = max;
        auto from = 1_v;
        if (scanz) {
            if (zz.first.second < up) {
                if(zz.first.second.IsInt())
                    up = zz.first.second;
                else
                    up = static_cast<int>(static_cast<double>(zz.first.second));
            }
            if (zz.first.first > from) {
                if(zz.first.first.IsInt())
                    from = zz.first.first;
                else
                    from = static_cast<int>(static_cast<double>(zz.first.first));
            }
            scanIt = zz.second.begin();
            while (scanIt != zz.second.end() && scanIt->second < from) {
                ++scanIt;
            }
        }
        auto absolute = abs();
        if(from==up)
            return f(up);
        else
            for (auto i = from; i < up; ++i) {
                auto a = absolute / i;
                if (a.IsInt()) {
                    if(f(i) || f(a))
                        return true;
                    if (a < up) {
                        up = a;
                    }
                }

                if (scanz && i > scanIt->second) {
                    ++scanIt;
                    if (scanIt == zz.second.end()) {
                        break;
                    }
                    i = scanIt->first;
                    if (!i.IsInt()) {
                        IMPLEMENT;
                    }
                }
            }
        return false;
    }

    namespace {
        StoringTasksQueue factorizationTasks;
    }
    bool Integer::Factorization(const std::function<bool(const Valuable&)>& f, const Valuable& max, const ranges_t& zz) const
    //{
    //    std::set<Integer> factors;
    //    return Factorization(f, max, factors, zz);
    //}

    //bool Integer::Factorization(const std::function<bool(const Valuable&)>& f,
    //    const Valuable& max,
    //    std::set<Integer>& factors,
    //    const ranges_t& zz) const
    {   // check division by primes
        // iterate by primes to find factor and store it in factors set
        // togather with lowest factor found highest factor as result of division
        // the highest factor reduces the search range to its value exclusively (1)
        // after scan all the primes, need to scan permutations
        //
        // starting from smallest prime factor,
        //   new set of non-prime factors is enreached with muiltiple multiplications to current prime to self and to each member of the new set
        //   new set iterating starting from smallest too and until range out of upper bound (1)
        //  ...

        // Multi-threaded way:
        // for devisible of thouse: isDevisible = (ResultOfDivision = This / iPrime).IsInt():
        // add products of the Divisibles with factorization set numbers of result of division:
        // (add all [ResultOfDivision factors starting from iPrime inclusively] time iPrime)  recursively
        auto absolute = arbitrary;
        if (absolute < 0)
            absolute = -absolute;
        decltype(arbitrary) from = 2;
        if (zz.first.first > from) {
            if(zz.first.first.IsInt())
                from = zz.first.first.ca();
            else
                from = static_cast<int>(static_cast<double>(zz.first.first)); // FIXME: precision issues
        }
        else if (f(0_v) || f(1_v)) {
            return true;
        }
        bool scanz = zz.second.size();
        auto scanIt = zz.second.end();
        Valuable up(absolute);
        if (up > max) up = max;
        auto primeIdx = 0;
        if (zz.first.first < zz.first.second) {
            if (zz.first.second < up) {
                if (zz.first.second.IsInt())
                    up = zz.first.second;
                else
                    up = static_cast<int>(static_cast<double>(zz.first.second)); // FIXME: precision issues
            }
        } else {
            // assuming its current prime index stored
            primeIdx = boost::numeric_cast<decltype(primeIdx)>(zz.first.second.ca());
        }
        if (absolute <= up && f(absolute)) {
            return true;
        }
        if (scanz) {
            scanIt = zz.second.begin();
            while (scanIt != zz.second.end() && scanIt->second < from) {
                ++scanIt;
            }
        }
        if(from==up)
            return f(up);
        else {
            std::set<decltype(arbitrary)> primeFactors, nonPrimeFactors;
            auto maxPrimeIdx = omnn::rt::primes();
            auto& primeUpmost = omnn::rt::prime(maxPrimeIdx);
            auto prime = omnn::rt::prime(primeIdx);
            while (prime < from)
                prime = omnn::rt::prime(++primeIdx);
            if (prime != from) { // from is not a prime number
                for (auto i = from; i < prime; ++i) { // slow scan till first prime
                    if (absolute % i == 0) {
                        auto a = absolute;
                        a /= i;
                        if (f(i) || f(a))
                            return true;
                        if (a < up) {
                            up = a;
                        }
                        nonPrimeFactors.emplace(a);
                    }
                    if (scanz && i > scanIt->second) {
                        ++scanIt;
                        if (scanIt == zz.second.end()) {
                            break;
                        }
                        if (!scanIt->first.IsInt()) {
                            IMPLEMENT;
                        }
                        i = scanIt->first.ca();
                    }
                }
            }

            // iterating by primes
            auto primeScanUp = up;
            while (from <= primeUpmost
                && primeScanUp >= prime
				&& primeIdx < maxPrimeIdx)
            {
                if (absolute % prime == 0) {
                    auto a = absolute;
                    a /= prime;
                    if(prime > a)
                        break;
                    if (f(prime) || f(a))
                        return true;
                    primeFactors.emplace(prime);
                    nonPrimeFactors.emplace(a);
                    if (a < primeScanUp)
                        primeScanUp = a;
                }
                prime = omnn::rt::prime(++primeIdx);
            }

            if (primeIdx == maxPrimeIdx)
            {
#ifdef OPENMIND_PRIME_MINING
                static bool OutOfPrimesTableWarning = {};
                if (!OutOfPrimesTableWarning) {
                    std::cerr
                        << primeUpmost
                        << " is the biggest prime number in the hardcoaded primes table used for fast factorization to "
                           "solve equations using RRT. Consider extending primes table to make this perform much "
                           "quicker."
                        << std::endl;
                    OutOfPrimesTableWarning = true;
                    //rt::GrowPrime(absolute,
                    //    [](const decltype(absolute)& v) {
                    //        return Integer(v).IsPrime();
                    //    });
                }
#endif
                // Fallback algorithm
                for (auto i = from; i < up; ++i) {
                    if (absolute % i == 0) {
                        auto a = absolute;
                        a /= i;
                        if (f(i) || f(a))
                            return true;
                        if (a < up) {
                            up = a;
                        }
                    }

                    if (scanz && i > scanIt->second) {
                        ++scanIt;
                        if (scanIt == zz.second.end()) {
                            break;
                        }
                        if (!scanIt->first.IsInt()) {
                            IMPLEMENT;
                        }
                        i = scanIt->first.ca();
                    }
                }
                return false;
            }

            for (auto& prime : primeFactors) {
                auto mul = prime;
                mul *= prime;
                decltype(primeFactors) addNonPrime;
                while (mul <= up)
                {
                    if (absolute % mul == 0) {
                        auto a = absolute;
                        a /= mul;
                        if (nonPrimeFactors.find(mul) == nonPrimeFactors.end()
                            && addNonPrime.emplace(mul).second)
                        {
                            if (f(mul))
                                return true;
                            if (nonPrimeFactors.find(a) == nonPrimeFactors.end()
                                && addNonPrime.emplace(a).second) {
                                if (f(a))
                                    return true;
                            }
                        }
                    } else {
                        break;
                    }
                    mul *= prime;
                }

                for (auto& nonPrime : nonPrimeFactors) {
                    mul = nonPrime;
                    for (;;) {
                        mul *= prime;
                        if (mul > up)
                            break;
                        if (absolute % mul == 0) {
                            auto a = absolute;
                            a /= mul;
                            if (nonPrimeFactors.find(mul) == nonPrimeFactors.end() && addNonPrime.emplace(mul).second) {
                                if (f(mul))
                                    return true;
                                if (nonPrimeFactors.find(a) == nonPrimeFactors.end() && addNonPrime.emplace(a).second) {
                                    if (f(a))
                                        return true;
                                }
                            } else {
                                break;
                            }
                        } else {
                            break;
                        }
                    }
                }

                nonPrimeFactors.merge(std::move(addNonPrime));
            }

        }
        //        }
//        else
//        {
//            // build OpenCL kernel
//            using namespace boost::compute;
//            auto copy = *this;
//            copy.optimize();
//            std::stringstream source;
//            source << "__kernel void f(__global long16 a, __global long16 *c) {"
//                << "    const uint i = get_global_id(0);"
//                << "    c[i] = a/i;"
//                << ";}";
//
//            device cuwinner = system::default_device();
//            for(auto& p: system::platforms())
//                for(auto& d: p.devices())
//                    if (d.compute_units() > cuwinner.compute_units())
//                        cuwinner = d;
//            auto wgsz = cuwinner.max_work_group_size();
//            context context(cuwinner);
//
//            kernel k(program::build_with_source(source.str(), context), "f");
//            auto sz = wgsz * sizeof(cl_long16);
//            buffer c(context, sz);
//            k.set_arg(0, c);
//
//            command_queue queue(context, cuwinner);
//            // run the add kernel
//            queue.enqueue_1d_range_kernel(k, 0, wgsz, 0);
//
//            // transfer results to the host array 'c'
//            std::vector<cl_long> z(wgsz);
//            queue.enqueue_read_buffer(c, 0, sz, &z[0]);
//            queue.finish();

//        }

//#pragma omp for
        return false;
    }
}}


#include <NTL/ctools.h>

#include <cstdlib>
#include <wmmintrin.h>
#include <iostream>

#if (!defined(__GNUC__) || !defined(__x86_64__) || !defined(__AVX__))
#error "PCLMUL not supported"
#endif

#if (NTL_BITS_PER_LONG != 64)
#error "PCLMUL not supported"
#endif

// NOTE: gcc and clang define __PCLMUL__, but icc does not

using namespace std;

static inline void
pclmul_mul1 (unsigned long *c, unsigned long a, unsigned long b)
{
   __m128i aa = _mm_setr_epi64( _mm_cvtsi64_m64(a), _mm_cvtsi64_m64(0));
   __m128i bb = _mm_setr_epi64( _mm_cvtsi64_m64(b), _mm_cvtsi64_m64(0));
   _mm_storeu_si128((__m128i*)c, _mm_clmulepi64_si128(aa, bb, 0));
}

int main()
{
   unsigned long a = ((unsigned long) atoi("15")) << (NTL_BITS_PER_LONG-4);
   unsigned long b = atoi("4");
   unsigned long c[2];

   pclmul_mul1(c, a, b);

   unsigned long c0 = ((unsigned long) atoi("3")) << (NTL_BITS_PER_LONG-2);
   unsigned long c1 = atoi("3");

   if (c[0] == c0 && c[1] == c1)
      return 0;
   else
      return -1;
}