/******************************************************************************

  This source file is part of the OpenQube project.

  Copyright 2008-2010 Marcus D. Hanwell

  This source code is released under the New BSD License, (the "License").

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

******************************************************************************/

#include "slaterset.h"

#ifdef WIN32
#define _USE_MATH_DEFINES
#include <math.h> // needed for M_PI
#endif

#include "cube.h"

//#include <Eigen/Array>
#include <Eigen/Eigenvalues>
#include <Eigen/LU>
#include <Eigen/QR>

#include <cmath>

#include <QtCore/QtConcurrentMap>
#include <QtCore/QFuture>
#include <QtCore/QFutureWatcher>
#include <QtCore/QReadWriteLock>
#include <QtCore/QDebug>

using std::vector;
using Eigen::Vector3d;
using Eigen::Vector3i;
using Eigen::MatrixXd;
using Eigen::SelfAdjointEigenSolver;

namespace OpenQube
{
struct SlaterShell
{
  SlaterSet *set;    // A pointer to the SlaterSet, cannot write to member vars
  Cube *cube;        // The target cube, used to initialise temp cubes too
  unsigned int pos;  // The index of position of the point to calculate the MO for
  unsigned int state;// The MO number to calculate
};

using std::vector;

static const double BOHR_TO_ANGSTROM = 0.529177249;
static const double ANGSTROM_TO_BOHR = 1.0 / 0.529177249;

SlaterSet::SlaterSet() : m_initialized(false)
{
}

SlaterSet::~SlaterSet()
{

}

bool SlaterSet::addAtoms(const std::vector<Eigen::Vector3d> &pos)
{
  m_atomPos = pos;
  return true;
}

bool SlaterSet::addSlaterIndices(const std::vector<int> &i)
{
  m_slaterIndices = i;
  return true;
}

bool SlaterSet::addSlaterTypes(const std::vector<int> &t)
{
  m_slaterTypes = t;
  return true;
}

bool SlaterSet::addZetas(const std::vector<double> &zetas)
{
  m_zetas = zetas;
  return true;
}

bool SlaterSet::addPQNs(const std::vector<int> &pqns)
{
  m_pqns = pqns;
  return true;
}

bool SlaterSet::addOverlapMatrix(const Eigen::MatrixXd &m)
{
  m_overlap.resize(m.rows(), m.cols());
  m_overlap = m;
  return true;
}

bool SlaterSet::addEigenVectors(const Eigen::MatrixXd &e)
{
  m_eigenVectors.resize(e.rows(), e.cols());
  m_eigenVectors = e;
  return true;
}

bool SlaterSet::addDensityMatrix(const Eigen::MatrixXd &d)
{
  m_density.resize(d.rows(), d.cols());
  m_density = d;
  return true;
}

unsigned int SlaterSet::numMOs()
{
  return static_cast<unsigned int>(m_overlap.cols());
}

inline bool SlaterSet::isSmall(double val)
{
  if (val > -1e-15 && val < 1e-15)
    return true;
  else
    return false;
}

unsigned int SlaterSet::factorial(unsigned int n)
{
  if (n <= 1)
    return n;
  return (n * factorial(n-1));
}

void SlaterSet::outputAll()
{

}

bool SlaterSet::calculateCubeMO(Cube *cube, unsigned int state)
{
  // Set up the calculation and ideally use the new QtConcurrent code to
  // multithread the calculation...
  if (state < 1 || static_cast<int>(state) > m_overlap.rows())
    return false;

  if (!m_initialized)
    initialize();

  // It is more efficient to process each shell over the entire cube than it
  // is to process each MO at each point in the cube. This is probably the best
  // point at which to multithread too - QtConcurrent!
  m_slaterShells.resize(static_cast<int>(cube->data()->size()));

  qDebug() << "Number of points:" << m_slaterShells.size();

  for (int i = 0; i < m_slaterShells.size(); ++i) {
    m_slaterShells[i].set = this;
    m_slaterShells[i].cube = cube;
    m_slaterShells[i].pos = i;
    m_slaterShells[i].state = state;
  }

  // Lock the cube until we are done.
  cube->lock()->lockForWrite();

  // Set the cube type
  cube->setCubeType(Cube::MO);

  // Watch for the future
  connect(&m_watcher, SIGNAL(finished()), this, SLOT(calculationComplete()));

  // The main part of the mapped reduced function...
  m_future = QtConcurrent::map(m_slaterShells, SlaterSet::processPoint);
  // Connect our watcher to our future
  m_watcher.setFuture(m_future);

  return true;
}

bool SlaterSet::calculateCubeDensity(Cube *cube)
{
  // Set up the calculation and ideally use the new QtConcurrent code to
  // multithread the calculation...
  if (!m_initialized)
    initialize();

  // It is more efficient to process each shell over the entire cube than it
  // is to process each MO at each point in the cube. This is probably the best
  // point at which to multithread too - QtConcurrent!
  m_slaterShells.resize(static_cast<int>(cube->data()->size()));

  qDebug() << "Number of points for density:" << m_slaterShells.size();

  for (int i = 0; i < m_slaterShells.size(); ++i) {
    m_slaterShells[i].set = this;
    m_slaterShells[i].cube = cube;
    m_slaterShells[i].pos = i;
    m_slaterShells[i].state = 0;
  }

  // Lock the cube until we are done.
  cube->lock()->lockForWrite();

  // Set the cube type
  cube->setCubeType(Cube::ElectronDensity);

  // Watch for the future
  connect(&m_watcher, SIGNAL(finished()), this, SLOT(calculationComplete()));

  // The main part of the mapped reduced function...
  m_future = QtConcurrent::map(m_slaterShells, SlaterSet::processDensity);
  // Connect our watcher to our future
  m_watcher.setFuture(m_future);

  return true;
}

BasisSet * SlaterSet::clone()
{
  SlaterSet *result = new SlaterSet();
  result->m_atomPos = this->m_atomPos;
  result->m_slaterIndices = this->m_slaterIndices;
  result->m_zetas = this->m_zetas;
  result->m_pqns = this->m_pqns;
  result->m_PQNs = this->m_PQNs;

  result->m_factors = this->m_factors;
  result->m_overlap = this->m_overlap;
  result->m_eigenVectors = this->m_eigenVectors;
  result->m_density = this->m_density;
  result->m_normalized = this->m_normalized;
  result->m_initialized = this->m_initialized;

  // Skip tmp variables
  return result;
}

void SlaterSet::calculationComplete()
{
  disconnect(&m_watcher, SIGNAL(finished()), this, SLOT(calculationComplete()));
  qDebug() << m_slaterShells[0].cube->data()->at(0) << m_slaterShells[0].cube->data()->at(1);
  qDebug() << "Calculation complete - cube map...";
  m_slaterShells[0].cube->lock()->unlock();
}

bool SlaterSet::initialize()
{
  m_normalized.resize(m_overlap.cols(), m_overlap.rows());

  SelfAdjointEigenSolver<MatrixXd> s(m_overlap);
  MatrixXd p = s.eigenvectors();
  MatrixXd m = p * s.eigenvalues().array().inverse().array().sqrt()
                    .matrix().asDiagonal() * p.inverse();
  m_normalized = m * m_eigenVectors;

  if (!(m_overlap*m*m).eval().isIdentity())
    qDebug() << "Identity test FAILED - do you need a newer version of Eigen?";
  //    std::cout << m_normalized << std::endl << std::endl;
  //    std::cout << s.eigenvalues() << std::endl << std::endl;
  //    std::cout << m_overlap << std::endl << std::endl;
  //    std::cout << s.eigenvalues().minCoeff() << ' ' << s.eigenvalues().maxCoeff() << std::endl << std::endl;

  m_factors.resize(m_zetas.size());
  m_PQNs = m_pqns;
  // Calculate the normalizations of the orbitals
  for (unsigned int i = 0; i < m_zetas.size(); ++i) {
    switch (m_slaterTypes[i]) {
    case S:
      m_factors[i] = pow(2.0 * m_zetas[i], m_pqns[i] + 0.5) *
          sqrt(1.0 / (4.0*M_PI) / factorial(2*m_pqns[i]));
      m_PQNs[i] -= 1;
      break;
    case PX:
    case PY:
    case PZ:
      m_factors[i] = pow(2.0 * m_zetas[i], m_pqns[i] + 0.5) *
          sqrt(3.0 / (4.0*M_PI) / factorial(2*m_pqns[i]));
      m_PQNs[i] -= 2;
      break;
    case X2:
      m_factors[i] = 0.5 * pow(2.0 * m_zetas[i], m_pqns[i] + 0.5) *
          sqrt(15.0 / (4.0*M_PI) / factorial(2*m_pqns[i]));
      m_PQNs[i] -= 3;
      break;
    case XZ:
      m_factors[i] = pow(2.0 * m_zetas[i], m_pqns[i] + 0.5) *
          sqrt(15.0 / (4.0*M_PI) / factorial(2*m_pqns[i]));
      m_PQNs[i] -= 3;
      break;
    case Z2:
      m_factors[i] = (0.5/sqrt(3.0)) * pow(2.0 * m_zetas[i], m_pqns[i] + 0.5) *
          sqrt(15.0 / (4.0*M_PI) / factorial(2*m_pqns[i]));
      m_PQNs[i] -= 3;
      break;
    case YZ:
    case XY:
      m_factors[i] = pow(2.0 * m_zetas[i], m_pqns[i] + 0.5) *
          sqrt(15.0 / (4.0*M_PI) / factorial(2*m_pqns[i]));
      m_PQNs[i] -= 3;
      break;
    default:
      qDebug() << "Orbital" << i << "not handled, type" << m_slaterTypes[i];
    }
  }
  // Convert the exponents into Angstroms
  for (unsigned int i = 0; i < m_zetas.size(); ++i)
    m_zetas[i] = m_zetas[i] / BOHR_TO_ANGSTROM;

  m_initialized = true;

  return true;
}

void SlaterSet::processPoint(SlaterShell &shell)
{
  SlaterSet *set = shell.set;
  unsigned int atomsSize = static_cast<unsigned int>(set->m_atomPos.size());
  unsigned int basisSize = static_cast<unsigned int>(set->m_zetas.size());

  vector<Vector3d> deltas;
  vector<double> dr;
  deltas.reserve(atomsSize);
  dr.reserve(atomsSize);

  // Simply the row of the matrix to operate on
  unsigned int indexMO = shell.state - 1;

  // Calculate our position
  Vector3d pos = shell.cube->position(shell.pos);// * ANGSTROM_TO_BOHR;

  // Calculate the deltas for the position
  for (unsigned int i = 0; i < atomsSize; ++i) {
    deltas.push_back(pos - set->m_atomPos[i]);
    dr.push_back(deltas[i].norm());
  }

  // Now calculate the value at this point in space
  double tmp = 0.0;
  for (unsigned int i = 0; i < basisSize; ++i) {
    tmp += pointSlater(shell.set, deltas[set->m_slaterIndices[i]],
                       dr[set->m_slaterIndices[i]], i, indexMO);
  }
  // Set the value
  shell.cube->setValue(shell.pos, tmp);
}

void SlaterSet::processDensity(SlaterShell &shell)
{
  // Calculate the electron density
  SlaterSet *set = shell.set;
  unsigned int atomsSize = static_cast<unsigned int>(set->m_atomPos.size());
  unsigned int basisSize = static_cast<unsigned int>(set->m_zetas.size());
  unsigned int matrixSize = static_cast<unsigned int>(set->m_density.rows());

  vector<Vector3d> deltas;
  vector<double> dr;
  deltas.reserve(atomsSize);
  dr.reserve(atomsSize);

  // Calculate our position
  Vector3d pos = shell.cube->position(shell.pos);// * ANGSTROM_TO_BOHR;

  // Calculate the deltas for the position
  for (unsigned int i = 0; i < atomsSize; ++i) {
    deltas.push_back(pos - set->m_atomPos[i]);
    dr.push_back(deltas[i].norm());
  }

  // Precompute the factor * exp (-zeta * drs)
  vector<double> expZetas(basisSize);
  for (unsigned int i = 0; i < basisSize; ++i) {
    expZetas[i] = exp(- set->m_zetas[i] * dr[set->m_slaterIndices[i]]);
  }

  // Now calculate the value of the density at this point in space
  double rho = 0.0;
  for (unsigned int i = 0; i < matrixSize; ++i) {
    // Calculate the off-diagonal parts of the matrix
    for (unsigned int j = 0; j < i; ++j) {
      if (isSmall(set->m_density.coeffRef(i, j))) continue;
      double a = 0.0, b = 0.0;
      // Do the first basis
      a = calcSlater(shell.set, deltas[set->m_slaterIndices[i]],
                     dr[set->m_slaterIndices[i]], i);
      b = calcSlater(shell.set, deltas[set->m_slaterIndices[j]],
                     dr[set->m_slaterIndices[j]], j);
      rho += 2.0 * set->m_density.coeffRef(i, j) * (a*b);
    }
    // Now calculate the matrix diagonal
    double tmp = 0.0;
    tmp = calcSlater(shell.set, deltas[set->m_slaterIndices[i]],
                     dr[set->m_slaterIndices[i]], i);
    rho += set->m_density.coeffRef(i, i) * (tmp*tmp);
  }
  // Set the value
  shell.cube->setValue(shell.pos, rho);
}

inline double SlaterSet::pointSlater(SlaterSet *set, const Eigen::Vector3d &delta,
                                     double dr, unsigned int slater,
                                     unsigned int indexMO)
{
  if (isSmall(set->m_normalized.coeffRef(slater, indexMO)))
    return 0.0;
  double tmp = set->m_normalized.coeffRef(slater, indexMO) *
      set->m_factors[slater] * exp(- set->m_zetas[slater] * dr);
  // Radial part with effective PQNs
  for (int i = 0; i < set->m_PQNs[slater]; ++i)
    tmp *= dr;
  switch (set->m_slaterTypes[slater]) {
  case S:
    break;
  case PX:
    tmp *= delta.x();
    break;
  case PY:
    tmp *= delta.y();
    break;
  case PZ:
    tmp *= delta.z();
    break;
  case X2: // (x^2 - y^2)r^n
    tmp *= delta.x() * delta.x() - delta.y() * delta.y();
    break;
  case XZ: // xzr^n
    tmp *= delta.x() * delta.z();
    break;
  case Z2: // (2z^2 - x^2 - y^2)r^n
    tmp *= 2.0 * delta.z() * delta.z() - delta.x() * delta.x()
        - delta.y() * delta.y();
    break;
  case YZ: // yzr^n
    tmp *= delta.y() * delta.z();
    break;
  case XY: // xyr^n
    tmp *= delta.x() * delta.y();
    break;
  default:
    return 0.0;
  }
  return tmp;
}

inline double SlaterSet::pointSlater(SlaterSet *set, const Eigen::Vector3d &delta,
                                     double dr, unsigned int slater,
                                     unsigned int indexMO, double expZeta)
{
  if (isSmall(set->m_normalized.coeffRef(slater, indexMO)))
    return 0.0;
  double tmp = set->m_normalized.coeffRef(slater, indexMO) * expZeta;
  // Radial part with effective PQNs
  for (int i = 0; i < set->m_PQNs[slater]; ++i)
    tmp *= dr;
  switch (set->m_slaterTypes[slater]) {
  case S:
    break;
  case PX:
    tmp *= delta.x();
    break;
  case PY:
    tmp *= delta.y();
    break;
  case PZ:
    tmp *= delta.z();
    break;
  case X2: // (x^2 - y^2)r^n
    tmp *= delta.x() * delta.x() - delta.y() * delta.y();
    break;
  case XZ: // xzr^n
    tmp *= delta.x() * delta.z();
    break;
  case Z2: // (2z^2 - x^2 - y^2)r^n
    tmp *= 2.0 * delta.z() * delta.z() - delta.x() * delta.x()
        - delta.y() * delta.y();
    break;
  case YZ: // yzr^n
    tmp *= delta.y() * delta.z();
    break;
  case XY: // xyr^n
    tmp *= delta.x() * delta.y();
    break;
  default:
    return 0.0;
  }
  return tmp;
}

inline double SlaterSet::calcSlater(SlaterSet *set, const Eigen::Vector3d &delta,
                                    double dr, unsigned int slater)
{
  double tmp = set->m_factors[slater] * exp(- set->m_zetas[slater] * dr);
  // Radial part with effective PQNs
  for (int i = 0; i < set->m_PQNs[slater]; ++i)
    tmp *= dr;
  switch (set->m_slaterTypes[slater]) {
  case S:
    break;
  case PX:
    tmp *= delta.x();
    break;
  case PY:
    tmp *= delta.y();
    break;
  case PZ:
    tmp *= delta.z();
    break;
  case X2: // (x^2 - y^2)r^n
    tmp *= delta.x() * delta.x() - delta.y() * delta.y();
    break;
  case XZ: // xzr^n
    tmp *= delta.x() * delta.z();
    break;
  case Z2: // (2z^2 - x^2 - y^2)r^n
    tmp *= 2.0 * delta.z() * delta.z() - delta.x() * delta.x()
        - delta.y() * delta.y();
    break;
  case YZ: // yzr^n
    tmp *= delta.y() * delta.z();
    break;
  case XY: // xyr^n
    tmp *= delta.x() * delta.y();
    break;
  default:
    return 0.0;
  }
  return tmp;
}

}


#define BOOST_TEST_MODULE pcraster model_engine opimpl
#include <boost/test/unit_test.hpp>
#include "geo_rasterspace.h"
#include "com_file.h"
#include "calc_runtimeenv.h"
#include "calc_spatial.h"
#include "calc_nonspatial.h"
#include "com_csfcell.h"
#include "calc_findsymbol.h" // major2op
#include "calc_operator.h"
#include "calc_lookuptable.h"
#include "calc_timetable.h"
#include "calc_domainerror.h"
#include "calc_dvautoptr.h"

// NOTE use string failureExpected in files expected to fail, see style guide

namespace calc {
 //! exec all fields on stack, just a shorthand
 static void execAll(RunTimeEnv& rte, const Operator *o)
 {
   size_t nrArgs=rte.stackSize();
   o->exec(&rte,nrArgs);
 }
}

BOOST_AUTO_TEST_CASE(testSameBin)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  REAL4 v1=1;
  REAL4 vb[NR]={2,0,6};
  pcr::setMV(vb[1]);
  const Operator* a=major2op(OP_BADD);
  { // SS
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_S,vb,NR));
    rte.pushField(new Spatial(VS_S,vb,NR));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());
    double v;
    BOOST_CHECK(r->getCell(v,0));
    BOOST_CHECK(v==4);
    BOOST_CHECK(!r->getCell(v,1));
    BOOST_CHECK( r->getCell(v,2));
    BOOST_CHECK(v==12);
  }

  { // NS  - SN
    RunTimeEnv rte(rs);

    for(size_t i=0; i<2; ++i) {
       if (i) {
        // NS
        rte.pushField(new NonSpatial(VS_S,v1));
        rte.pushField(new Spatial(VS_S,vb,NR));
      } else { // SN
        rte.pushField(new Spatial(VS_S,vb,NR));
        rte.pushField(new NonSpatial(VS_S,v1));
      }

      execAll(rte,a);
      DVAutoPtr<Field>r(rte.popField());
      BOOST_CHECK(r->isSpatial());
      BOOST_CHECK(r->vs() == VS_S);
      double v;
      BOOST_CHECK(r->getCell(v,0));
      BOOST_CHECK(v==3);
      BOOST_CHECK(!r->getCell(v,1));
      BOOST_CHECK( r->getCell(v,2));
      BOOST_CHECK(v==7);
    }
  }
  { // NN as SS
    RunTimeEnv rte(rs);

    rte.pushField(new NonSpatial(VS_S,v1));
    rte.pushField(new NonSpatial(VS_S,v1));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(!r->isSpatial());
    double v;
    BOOST_CHECK(r->getCell(v,0));
    BOOST_CHECK(v==2);
  }
}

BOOST_AUTO_TEST_CASE(testCover)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  REAL4 v1=1;
  REAL4 vb[NR]={2,0,6};
  pcr::setMV(vb[1]);
  const Operator* a=major2op(OP_COVER);
  { // SS
    RunTimeEnv rte(rs);
    REAL4 vb2[NR]={8,-1,8};

    rte.pushField(new Spatial(VS_S,vb,NR));  // left
    rte.pushField(new Spatial(VS_S,vb2,NR)); // right

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());
    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_S);
    REAL4 res[NR]={2,-1,6};
    BOOST_CHECK(std::equal(res,res+NR,r->src_f()));
  }
  { // NS  - SN
    RunTimeEnv rte(rs);

    for(size_t i=0; i<2; ++i) {
       if (i) {
        // NS
        rte.pushField(new NonSpatial(VS_S,v1));
        rte.pushField(new Spatial(VS_S,vb,NR));
      } else { // SN
        rte.pushField(new Spatial(VS_S,vb,NR));
        rte.pushField(new NonSpatial(VS_S,v1));
      }

      execAll(rte,a);
      DVAutoPtr<Field>r(rte.popField());
      BOOST_CHECK(r->isSpatial());
      BOOST_CHECK(r->vs() == VS_S);
      if (i) {
        REAL4 res[NR]={1,1,1};
        BOOST_CHECK(std::equal(res,res+NR,r->src_f()));
      } else {
        REAL4 res[NR]={2,1,6};
        BOOST_CHECK(std::equal(res,res+NR,r->src_f()));
      }
    }
  }
  { // NN as SS
    RunTimeEnv rte(rs);

    rte.pushField(new NonSpatial(VS_S,v1));
    rte.pushField(new NonSpatial(VS_S,v1));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(!r->isSpatial());
    double v;
    BOOST_CHECK(r->getCell(v,0));
    BOOST_CHECK(v==1);
  }
}

BOOST_AUTO_TEST_CASE(testSameUn)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  REAL4 v1=1;
  REAL4 vb[NR]={4,0,-6};
  pcr::setMV(vb[1]);
  const Operator* a=major2op(OP_SQRT);
  { // S
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_S,vb,NR));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());
    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);

    BOOST_CHECK(r->src_f()[0]==2);
    BOOST_CHECK(pcr::isMV(r->src_f()[1]));
    BOOST_CHECK(pcr::isMV(r->src_f()[2])); // domain error
  }

  { // N
    RunTimeEnv rte(rs);

    rte.pushField(new NonSpatial(VS_S,v1));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(!r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);
    BOOST_CHECK(r->src_f()[0]==1);
  }
}

BOOST_AUTO_TEST_CASE(testDomainError)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  { // N SameUn
    RunTimeEnv rte(rs);
    REAL4 minus1=-1;
    rte.pushField(new NonSpatial(VS_S,minus1));

    bool catched(false);
    try {
     major2op(OP_SQRT)->exec(&rte,1);
    } catch(const calc::DomainError& ) {
      catched=true;
    }
    BOOST_CHECK(catched);
  }
  { // SN
    RunTimeEnv rte(rs);

    REAL4 vb[NR]={2,0,6};
    pcr::setMV(vb[1]);
    rte.pushField(new Spatial(VS_S,vb,NR));

    REAL4 v0=0;
    rte.pushField(new NonSpatial (VS_S,v0));

    const Operator* a=major2op(OP_FDIV);

    bool catched(false);
    try {
    execAll(rte,a);
    } catch(const calc::DomainError& ) {
      catched=true;
    }
    BOOST_CHECK(catched);
  }
  { // NN
    RunTimeEnv rte(rs);
    REAL4 v0=0;
    rte.pushField(new NonSpatial (VS_S,v0));
    rte.pushField(new NonSpatial (VS_S,v0));

    const Operator* a=major2op(OP_FDIV);

    bool catched(false);
    try {
    execAll(rte,a);
    } catch(const calc::DomainError& ) {
      catched=true;
    }
    BOOST_CHECK(catched);
  }
  { // NN  -0.047**1.5; test pcrcalc337a
    RunTimeEnv rte(rs);
    REAL4 vMin= -0.047f;
    REAL4 vFrac= 1.5f;
    rte.pushField(new NonSpatial (VS_S,vMin));
    rte.pushField(new NonSpatial (VS_S,vFrac));

    const Operator* a=major2op(OP_POW);

    bool catched(false);
    try {
    execAll(rte,a);
    } catch(const calc::DomainError& ) {
      catched=true;
    }
    BOOST_CHECK(catched);
  }
}

BOOST_AUTO_TEST_CASE(testDiffUn)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
 { // Manual typed in
    RunTimeEnv rte(rs);

    INT4 vb[NR]={2,MV_INT4,6};
    pcr::setMV(vb[1]);
    rte.pushField(new Spatial(VS_O,vb,NR));

    const Operator* a=major2op(OP_MAPMINIMUM);

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(!r->isSpatial());
    BOOST_CHECK(r->vs()==VS_O);
    double v;
    BOOST_CHECK(r->getCell(v,0));
    BOOST_CHECK(v==2);
 }
 { // Spatial
    RunTimeEnv rte(rs);

    INT4 vb[NR]={2,MV_INT4,6};
    pcr::setMV(vb[1]);
    rte.pushField(new Spatial(VS_O,vb,NR));

    const Operator* a=major2op(OP_SPATIAL);

    execAll(rte,a);
    { // nop
      DVAutoPtr<Field>r(rte.popField());

      BOOST_CHECK(r->isSpatial());
      BOOST_CHECK(r->vs()==VS_O);
      BOOST_CHECK(std::equal(vb,vb+3,r->src_4()));
    }

    rte.pushField(createNonSpatial<INT4>(VS_O,4));
    execAll(rte,a);
    { // did it
      INT4 vb4[NR]={4,4,4};
      DVAutoPtr<Field>r(rte.popField());
      BOOST_CHECK(r->isSpatial());
      BOOST_CHECK(r->vs()==VS_O);
      BOOST_CHECK(std::equal(vb4,vb4+3,r->src_4()));
    }
 }
 { // Generated DiffUn
    RunTimeEnv rte(rs);

    REAL4 vb[NR]={1,-1,-1};
    pcr::setMV(vb[1]);
    rte.pushField(new Spatial(VS_D,vb,NR));

    const Operator* a=major2op(OP_NODIRECTION);
    execAll(rte,a);

    DVAutoPtr<Field>r(rte.popField());
    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_B);

    double v;
    BOOST_CHECK(r->getCell(v,0));
    BOOST_CHECK(v==0);
    BOOST_CHECK(!r->getCell(v,1));
    BOOST_CHECK( r->getCell(v,2));
    BOOST_CHECK(v==1);
 }
}

BOOST_AUTO_TEST_CASE(testCompare)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  REAL4 v1=1;
  REAL4 vb[NR]={2,0,6};
  pcr::setMV(vb[1]);
  const Operator* a=major2op(OP_LT);
  { // SS
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_S,vb,NR));
    rte.pushField(new Spatial(VS_S,vb,NR));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_B);
    BOOST_CHECK(r->src_1()[0]==0);
    BOOST_CHECK(r->src_1()[1]==MV_UINT1);
    BOOST_CHECK(r->src_1()[2]==0);
  }
  { // NS  - SN
    RunTimeEnv rte(rs);

    for(size_t i=0; i<2; ++i) {
       if (i) {
        // NS  1 < ( 2,MV, 6) -> (1,MV,1)
        rte.pushField(new NonSpatial(VS_S,v1));
        rte.pushField(new Spatial(VS_S,vb,NR));
      } else { // SN (2,MV, 6) < 1 -> (0,MV,0)
        rte.pushField(new Spatial(VS_S,vb,NR));
        rte.pushField(new NonSpatial(VS_S,v1));
      }

      execAll(rte,a);
      DVAutoPtr<Field>r(rte.popField());

      BOOST_CHECK(r->isSpatial());
      BOOST_CHECK(r->vs() == VS_B);
      BOOST_CHECK(r->src_1()[0]==i);
      BOOST_CHECK(r->src_1()[1]==MV_UINT1);
      BOOST_CHECK(r->src_1()[2]==i);

    }
  }
  { // NN as SS
    RunTimeEnv rte(rs);

    rte.pushField(new NonSpatial(VS_S,v1));
    rte.pushField(new NonSpatial(VS_S,v1));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(!r->isSpatial());
    BOOST_CHECK(r->vs() == VS_B);
    BOOST_CHECK(r->src_1()[0]==0);
  }
}

BOOST_AUTO_TEST_CASE(testIfThen)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  UINT1 sCond[NR]={1,1,0};
  REAL4 sTrue[NR]={2,0,6};
  pcr::setMV(sTrue[1]);
  const Operator* a=major2op(OP_IFTHEN);
  { // SS
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_B,sCond,NR));
    rte.pushField(new Spatial(VS_S,sTrue,NR));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);
    BOOST_CHECK(r->src_f()[0]==2);
    BOOST_CHECK(pcr::isMV(r->src_f()[1]));
    BOOST_CHECK(pcr::isMV(r->src_f()[2]));
  }
  { // NS
    RunTimeEnv rte(rs);

    rte.pushField(createNonSpatial<UINT1>(VS_B,0));
    rte.pushField(new Spatial(VS_S,sTrue,NR));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);
    BOOST_CHECK(pcr::isMV(r->src_f()[0]));
    BOOST_CHECK(pcr::isMV(r->src_f()[1]));
    BOOST_CHECK(pcr::isMV(r->src_f()[2]));
  }
  { // SN
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_B,sCond,NR));
    rte.pushField(createNonSpatial<REAL4>(VS_S,7));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);
    BOOST_CHECK(r->src_f()[0] == 7);
    BOOST_CHECK(r->src_f()[1] == 7);
    BOOST_CHECK(pcr::isMV(r->src_f()[2]));
  }
}

BOOST_AUTO_TEST_CASE(testIfThenElse)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  UINT1 sCond[NR] ={1,1,0};

  REAL4 sTrue[NR] ={2,0,6};
  pcr::setMV(sTrue[1]);

  REAL4 sFalse[NR]={3,4,8};
  const Operator* a=major2op(OP_IFTHENELSE);
  { // SSS
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_B,sCond,NR));
    rte.pushField(new Spatial(VS_S,sTrue,NR));
    rte.pushField(new Spatial(VS_S,sFalse,NR));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);
    BOOST_CHECK(r->src_f()[0]==2);
    BOOST_CHECK(pcr::isMV(r->src_f()[1]));
    BOOST_CHECK(r->src_f()[2]==8);
  }
  { // NNS cond false
    RunTimeEnv rte(rs);

    rte.pushField(createNonSpatial<UINT1>(VS_B,0));
    rte.pushField(new Spatial(VS_S,sTrue,NR));
    rte.pushField(createNonSpatial<REAL4>(VS_S,7));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);
    BOOST_CHECK(r->src_f()[0]==7);
    BOOST_CHECK(r->src_f()[1]==7);
    BOOST_CHECK(r->src_f()[2]==7);
  }
  { // NNS cond true
    RunTimeEnv rte(rs);

    rte.pushField(createNonSpatial<UINT1>(VS_B,1));
    rte.pushField(new Spatial(VS_S,sTrue,NR));
    rte.pushField(createNonSpatial<REAL4>(VS_S,7));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs() == VS_S);
    BOOST_CHECK(r->src_f()[0]==2);
    BOOST_CHECK(pcr::isMV(r->src_f()[1]));
    BOOST_CHECK(r->src_f()[2]==6);
  }
}

BOOST_AUTO_TEST_CASE(testGen)
{
  using namespace calc;

#define NR 3
  const geo::RasterSpace rs(1,NR);
  {
    RunTimeEnv rte(rs);

    const Operator* a=major2op(OP_TIMESLICE);

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(!r->isSpatial());
    BOOST_CHECK(r->vs()==VS_S);
    double v;
    BOOST_CHECK(r->getCell(v,0));
    BOOST_CHECK(v==1);
  }
}

BOOST_AUTO_TEST_CASE(testGlobal)
{
  using namespace calc;

#define NR6 6
  const geo::RasterSpace rs(1,NR6);
  const Operator* a=major2op(OP_AREAMAJORITY);

  {
    RunTimeEnv rte(rs);

    INT4 vbClass[NR6]={1,1,1,8,8,8};
    INT4 vbVals[NR6] ={2,6,6,4,4,1};
    INT4 vbRes[NR6]  ={6,6,6,4,4,4};

    rte.pushField(new Spatial(VS_N,vbVals,NR6));
    rte.pushField(new Spatial(VS_N,vbClass,NR6));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_N);
    BOOST_CHECK(std::equal(vbRes,vbRes+6,r->src_4()));
  }
  {
    RunTimeEnv rte(rs);

    INT4 vbClass[NR6] ={1,1,1,8,8,8};
    UINT1 vbVals[NR6] ={0,1,0,0,1,1};
    UINT1 vbRes [NR6] ={0,0,0,1,1,1};

    rte.pushField(new Spatial(VS_B,vbVals,NR6));
    rte.pushField(new Spatial(VS_N,vbClass,NR6));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_B);
    BOOST_CHECK(std::equal(vbRes,vbRes+6,r->src_1()));
  }
  {
    RunTimeEnv rte(rs);

    INT4 vbClass[NR6] ={1,1,1,8,8,8};
    UINT1 val =1;
    UINT1 vbRes [NR6] ={1,1,1,1,1,1};

    rte.pushField(new NonSpatial(VS_B,val));
    rte.pushField(new Spatial(VS_N,vbClass,NR6));

    execAll(rte,a);
    DVAutoPtr<Field>r(rte.popField());

    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_B);
    BOOST_CHECK(std::equal(vbRes,vbRes+6,r->src_1()));
  }
#ifdef NDEBUG
  {
    RunTimeEnv rte(rs);
    UINT1 vbLdd[NR6] ={6,6,6,6,6,5};
    REAL4 val =-1; // illegal friction material

    rte.pushField(new Spatial(VS_L,vbLdd,NR6));
    rte.pushField(new NonSpatial(VS_S,val));

    const Operator* l=major2op(OP_SLOPELENGTH);
    bool catched=false;
    try {
     execAll(rte,l);
    } catch (const DomainError& ) {
      catched=true;
    }
    BOOST_CHECK(catched);

  }
#else
  // OLS: I think this test never worked in Debug mode
  // sloplength is implemented in C (and others in source/calc)
  // and return error codes instead of raising exceptions
  BOOST_WARN_MESSAGE(false, "OP_SLOPELENGTH triggers partly 'empty exception' assert in Debug build");
#endif
}

BOOST_AUTO_TEST_CASE(testMRF)
{
  using namespace calc;

#define NR6 6
  const geo::RasterSpace rs(1,NR6);

  UINT1 vbPoints[NR6]={1,0,0,0,0,0};
  INT4 vbZone[NR6]   ={1,1,1,1,1,1};
  REAL4 vbCost[NR6]  ={3,4,5,6,7,8};

  {
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_B,vbPoints,NR6));
    rte.pushField(createNonSpatial<REAL4>(VS_S,3)); // initial costs
    rte.pushField(createNonSpatial<REAL4>(VS_S,1)); // friction

    const Operator* a=major2op(OP_SPREAD_MRF);
    execAll(rte,a);

    DVAutoPtr<Field>r1(rte.popField());
    BOOST_CHECK(r1->isSpatial());
    BOOST_CHECK(r1->vs()==VS_S);
    BOOST_CHECK(std::equal(vbCost,vbCost+6,r1->src_f()));

    DVAutoPtr<Field>r2(rte.popField());
    BOOST_CHECK(r2->isSpatial());
    BOOST_CHECK(r2->vs()==VS_B);
    BOOST_CHECK(std::equal(vbZone,vbZone+6,r2->src_1()));
  }
  { // spread only
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_B,vbPoints,NR6));
    rte.pushField(createNonSpatial<REAL4>(VS_S,3)); // initial costs
    rte.pushField(createNonSpatial<REAL4>(VS_S,1)); // friction

    const Operator* a=major2op(OP_SPREAD);
    execAll(rte,a);

    DVAutoPtr<Field>r2(rte.popField());
    BOOST_CHECK(r2->isSpatial());
    BOOST_CHECK(r2->vs()==VS_S);
    BOOST_CHECK(std::equal(vbCost,vbCost+6,r2->src_f()));

    BOOST_CHECK(rte.stackSize()==0);
  }
  { // spreadzone only
    RunTimeEnv rte(rs);

    rte.pushField(new Spatial(VS_B,vbPoints,NR6));
    rte.pushField(createNonSpatial<REAL4>(VS_S,3)); // initial costs
    rte.pushField(createNonSpatial<REAL4>(VS_S,1)); // friction

    const Operator* a=major2op(OP_SPREADZONE);
    execAll(rte,a);

    DVAutoPtr<Field>r1(rte.popField());
    BOOST_CHECK(r1->isSpatial());
    BOOST_CHECK(r1->vs()==VS_B);
    BOOST_CHECK(std::equal(vbZone,vbZone+6,r1->src_1()));

    BOOST_CHECK(rte.stackSize()==0);
  }
}

BOOST_AUTO_TEST_CASE(testConversion)
{
  using namespace calc;

#define NR6 6
  const geo::RasterSpace rs(1,NR6);

  UINT1 vbBol[NR6]={1  ,1  ,0,0,1,1};
  INT4 vbNom[NR6] ={3  ,4  ,0,0,7,8};
  REAL4 vbIn[NR6] ={3.3f,4.8f,0.0f,0.0f,7.0f,8.0f};

  {
    RunTimeEnv rte(rs);

    Field *f=new Spatial(VS_S,vbIn,NR6);
    rte.pushField(f);

    const Operator* a=major2op(OP_SCALAR);
    // A NO_OP
    execAll(rte,a);

    DVAutoPtr<Field>r(rte.popField());
    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_S);
    BOOST_CHECK(std::equal(vbIn,vbIn+6,r->src_f()));

    //  assert A NO_OP
    BOOST_CHECK(r.get() == f);

  }
  {
    RunTimeEnv rte(rs);

    Field *f=new Spatial(VS_S,vbIn,NR6);
    rte.pushField(f);

    const Operator* a=major2op(OP_NOMINAL);
    execAll(rte,a);

    DVAutoPtr<Field>r(rte.popField());
    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_N);
    BOOST_CHECK(std::equal(vbNom,vbNom+6,r->src_4()));
  }
  {
    RunTimeEnv rte(rs);

    Field *f=new Spatial(VS_S,vbIn,NR6);
    rte.pushField(f);

    const Operator* a=major2op(OP_BOOLEAN_);
    execAll(rte,a);

    DVAutoPtr<Field>r(rte.popField());
    BOOST_CHECK(r->isSpatial());
    BOOST_CHECK(r->vs()==VS_B);
    BOOST_CHECK(std::equal(vbBol,vbBol+6,r->src_1()));
  }
}

BOOST_AUTO_TEST_CASE(testLookup)
{
  using namespace calc;

/*
  <!-- should be non-spatial, number 1 -->
  <model>fileoutput lookupnominal(inp_1.tbl, 5, 8, 10);</model>
  <msg>1</msg>
</test>
*/
#define NR6 6
  const geo::RasterSpace rs(1,NR6);

  LookupTable* l(new LookupTable());
  l->setReadOnlyReference(true);

  std::vector<VS> colVs(4,VS_O);
  colVs.back()=VS_N;
  l->setRecords("inp_1.tbl",colVs);

  {
    RunTimeEnv rte(rs);

    rte.pushDataValue(l);
    rte.pushField(createNonSpatial<INT4>(VS_O,5));
    rte.pushField(createNonSpatial<INT4>(VS_O,8));
    rte.pushField(createNonSpatial<INT4>(VS_O,10));

    const Operator* a=major2op(OP_LOOKUPNOMINAL);

    a->exec(&rte,4); // 3 fields and the table
    { // nonspatial result original pcrcalc9 test
      DVAutoPtr<Field>r(rte.popField());

      BOOST_CHECK(!r->isSpatial());
      BOOST_CHECK( r->vs()==VS_N);
      BOOST_CHECK( r->src_4()[0]==1);
    }
  }
  {
    RunTimeEnv rte(rs);

    rte.pushDataValue(l);
    rte.pushField(createNonSpatial<INT4>(VS_O,5));
    INT4 vb[NR6] = { 8, 4, 4, 8, MV_INT4, MV_INT4 };
    rte.pushField(new Spatial(VS_O,vb,NR6));
    rte.pushField(createNonSpatial<INT4>(VS_O,10));

    const Operator* a=major2op(OP_LOOKUPNOMINAL);

    a->exec(&rte,4); // 3 fields and the table
    {
      DVAutoPtr<Field>r(rte.popField());

      INT4 vbRes[NR6] = { 1, 0, 0, 1, MV_INT4, MV_INT4 };
      BOOST_CHECK(r->isSpatial());
      BOOST_CHECK(r->vs()==VS_N);
      BOOST_CHECK(std::equal(vbRes,vbRes+NR6,r->src_4()));
    }
  }
  deleteAlways(l);
}

//! test class TimeinputTssOp
BOOST_AUTO_TEST_CASE(testTimeinputTssOp)
{
  using namespace calc;

  struct SetTimer {
    void operator()(RunTimeEnv& rte, size_t c) {
     Timer t(rte.timer());
     t.setCurrentInt(c);
     rte.setTimer(t);
    }
  } setTimer;

#define NR6 6
  const geo::RasterSpace rs(1,NR6);

  // 2 steps with 3 columns
  // -1 is important (--small cellrepr of ordinal testing)
  com::write("1 10 100 -1\n2 20 200 1e31","testTimeinputTssOp.tss");
  TimeTable* l(new TimeTable("testTimeinputTssOp.tss",VS_O,1));
  l->setReadOnlyReference(true);

  const Operator* a=major2op(OP_TIMEINPUTORDINAL);
  {
    RunTimeEnv rte(rs);
    setTimer(rte,2);

    rte.pushDataValue(l);
    rte.pushField(createNonSpatial<INT4>(VS_O,2));

    a->exec(&rte,2); // 1 id-field and the tss
    { // nonspatial result
      DVAutoPtr<Field>r(rte.popField());

      BOOST_CHECK(!r->isSpatial());
      BOOST_CHECK(r->vs()==VS_O);
      BOOST_CHECK(r->src_4()[0]==200);
    }
  }

  {
    RunTimeEnv rte(rs);
    setTimer(rte,2);

    INT4 vb[NR6] =    {  2, 1,        0,   2, 3,       MV_INT4 };
    INT4 vbRes[NR6] = {200, 20, MV_INT4, 200, MV_INT4, MV_INT4 };

    rte.pushDataValue(l);
    rte.pushField(new Spatial(VS_O,vb,NR6));

    a->exec(&rte,2); // 1 id-field and the tss
    {
      DVAutoPtr<Field>r(rte.popField());

      BOOST_CHECK(r->isSpatial());
      BOOST_CHECK(r->vs()==VS_O);
      BOOST_CHECK(std::equal(vbRes,vbRes+NR6,r->src_4()));
    }

    // also test too short of timesteps, can happen runtime
    setTimer(rte,4);

    rte.pushDataValue(l);
    rte.pushField(new Spatial(VS_O,vb,NR6));
    bool catched=false;
    try {
     a->exec(&rte,2); // 1 id-field and the tss
    } catch(const DomainError& ) {
      catched=true;
    }
    BOOST_CHECK(catched);
  }
  deleteAlways(l);
}


#include "test_Prefix.h"
#include <boost/range/distance.hpp>
#include <CGAL/boost/graph/Euler_operations.h>

#include <CGAL/IO/OFF.h>
#include <CGAL/Polygon_mesh_processing/border.h>
#include <CGAL/boost/graph/copy_face_graph.h>

template <typename T>
void
test_copy_face_graph_nm_umbrella()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  T g;
  Kernel::Point_3 p;

  CGAL::make_tetrahedron(p, p, p, p, g);
  CGAL::make_tetrahedron(p, p, p, p, g);

  std::vector<vertex_descriptor> verts(vertices(g).begin(), vertices(g).end());

  //merge verts[0] and verts[4]
  for (halfedge_descriptor h : CGAL::halfedges_around_target(verts[4], g))
    set_target(h, verts[0], g);
  remove_vertex(verts[4], g);

  T g_copy;
  CGAL::copy_face_graph(g, g_copy);

  for (halfedge_descriptor h : halfedges(g_copy))
  {
    assert( target(h, g_copy) != Traits::null_vertex() );
  }
}

template <typename T>
void
join_face_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_1<T> f;

  bool found;
  halfedge_descriptor e;
  boost::tie(e, found) = halfedge(f.w, f.v, f.m);
  assert(found);
  // manually set the halfedge of f.f1 to the edge that is to be
  // removed to provoke a special case
  set_halfedge(f.f1, e, f.m);
  CGAL::Euler::join_face(e,f.m);

  assert(CGAL::internal::exact_num_faces(f.m) == 2);
  assert(CGAL::internal::exact_num_edges(f.m) == 6);

  CGAL::Halfedge_around_face_iterator<T> begin, end;
  boost::tie(begin, end) = CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m);
  assert(std::distance(begin, end) == 4);
  for(; begin != end; ++begin)
  {

    halfedge_descriptor hd = *begin;
    assert(face(hd, f.m) == f.f1);

  }

  face_iterator fit, fend;
  for(boost::tie(fit, fend) = faces(f.m); fit != fend; ++fit) {
    assert(*fit == f.f1 || *fit == f.f3);
  }

  assert(degree(f.w, f.m) == 2);
  assert(degree(f.v, f.m) == 3);
  assert(CGAL::is_valid_polygon_mesh(f.m));

}

template <typename T>
void
remove_face_test_1()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_1<T> f;

  // find the edge between x and y
  bool found;
  halfedge_descriptor e;
  boost::tie(e, found) = halfedge(f.x, f.y, f.m);
  assert(found);
  assert(face(e, f.m) == f.f3);

  CGAL::Euler::remove_face(e,f.m);

  assert(CGAL::is_valid_polygon_mesh(f.m));

  assert_EQUAL(degree(f.v, f.m) == 3);
  assert_EQUAL(degree(f.x, f.m) == 2);
  assert_EQUAL(CGAL::internal::exact_num_faces(f.m) == 2);
  assert_EQUAL(CGAL::internal::exact_num_edges(f.m) == 5);
  assert_EQUAL(CGAL::internal::exact_num_vertices(f.m) == 4);
  halfedge_iterator eb, ee;
  int count = 0;
  for(boost::tie(eb, ee) = halfedges(f.m); eb != ee; ++eb) {
    if(face(*eb,f.m) == boost::graph_traits<T>::null_face())
      ++count;
  }
  assert(count == 4);
}

template <typename T>
void
remove_face_test_2()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_2<T> f;

  // find the edge between x and v
  bool found;
  halfedge_descriptor e;

  boost::tie(e, found) = halfedge(f.x, f.w, f.m);
  assert(found);
  boost::tie(e, found) = halfedge(f.x, f.v, f.m);
  assert(found);
  assert(face(e, f.m) == f.f1);
  CGAL::Euler::remove_face(e,f.m);
  assert(CGAL::is_valid_polygon_mesh(f.m));

  assert(CGAL::internal::exact_num_faces(f.m) == 3);
  assert(CGAL::internal::exact_num_edges(f.m) == 7);
  assert(CGAL::internal::exact_num_vertices(f.m) == 5);

  boost::tie(e, found) = halfedge(f.x, f.w, f.m);
  assert(found);
  assert(face(e,f.m) == boost::graph_traits<T>::null_face());

  // check the boundary

  halfedge_descriptor n = next(e, f.m);
  while(n != e) {
    assert(face(n,f.m) == boost::graph_traits<T>::null_face() );
    n = next(n, f.m);
  }
}

template <typename T>
void
add_face_to_border_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_5<T> f;

  CGAL::Euler::add_face_to_border(f.h1, f.h2, f.m);

  assert(CGAL::is_valid_polygon_mesh(f.m));

}

template <typename T>
void
add_vertex_and_face_to_border_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_5<T> f;
  typedef typename boost::graph_traits<T>::halfedge_descriptor  halfedge_descriptor;
  halfedge_descriptor h1 = f.h1, h2 = f.h2;
  const T& m = f.m;

  int dist=0;
  halfedge_descriptor hd = h1;
  while(hd != h2){
    ++dist;
    hd = next(hd,m);
  }
  assert(dist == 2);

  int blength = 0;
  for(halfedge_descriptor hd : CGAL::halfedges_around_face(h1,m)){
    CGAL_USE(hd);
    blength++;
  }

  halfedge_descriptor res = CGAL::Euler::add_vertex_and_face_to_border(f.h1, f.h2, f.m);
  assert(CGAL::is_valid_polygon_mesh(f.m));

  assert(! CGAL::is_border(res,m));
  assert(CGAL::is_border(opposite(res,m),m));
  res = opposite(res,m);
  for(halfedge_descriptor hd : CGAL::halfedges_around_face(res,m)){
    CGAL_USE(hd);
    blength--;
  }
  assert(blength == 0);

}

template <typename T>
void
join_vertex_interior_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_3<T> f;
  halfedge_descriptor e;

  bool found;
  boost::tie(e, found) = halfedge(f.w, f.x, f.m);
  assert(found);
  CGAL::Euler::join_vertex(e,f.m);
  assert(CGAL::internal::exact_num_faces(f.m) == 2);
  assert(CGAL::internal::exact_num_vertices(f.m) == 5);
  assert(CGAL::internal::exact_num_edges(f.m) == 6);
  assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m)) == 3);
  assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f2, f.m), f.m)) == 3);
  assert(degree(f.x, f.m) == 4);
  assert(CGAL::is_valid_polygon_mesh(f.m));
}

template <typename T>
void
join_vertex_exterior_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  {
    // exterior edge is collapsed
    Surface_fixture_3<T> f;
    halfedge_descriptor e;
    bool found;
    boost::tie(e, found) = halfedge(f.w, f.y, f.m);
    assert(source(e,f.m) == f.w);
    assert(target(e,f.m) == f.y);
    assert(found);
    CGAL::Euler::join_vertex(e,f.m);
    assert(CGAL::internal::exact_num_faces(f.m) == 2);
    assert(CGAL::internal::exact_num_vertices(f.m) == 5);
    assert(CGAL::internal::exact_num_edges(f.m) == 6);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m)) == 4);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f2, f.m), f.m)) == 3);
    assert(degree(f.y, f.m) == 3);
    assert(CGAL::is_valid_polygon_mesh(f.m));
  }

  {
    Surface_fixture_3<T> f;
    halfedge_descriptor e;
    bool found;
    boost::tie(e, found) = halfedge(f.y, f.w, f.m);

    assert(source(e,f.m) == f.y);
    assert(target(e,f.m) == f.w);
    assert(found);
    CGAL::Euler::join_vertex(e,f.m);
    assert(CGAL::internal::exact_num_faces(f.m) == 2);
    assert(CGAL::internal::exact_num_vertices(f.m) == 5);
    assert(CGAL::internal::exact_num_edges(f.m) == 6);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m)) == 4);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f2, f.m), f.m)) == 3);

    assert(CGAL::is_valid_polygon_mesh(f.m));
    assert(degree(f.w, f.m) == 3);

  }
}

template <typename T>
void
split_vertex()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_3<T> f;
  halfedge_descriptor h1, h2;
  bool found;
  boost::tie(h1, found) = halfedge(f.w, f.y, f.m);
  assert(found);
  boost::tie(h2, found) = halfedge(f.z, f.y, f.m);
  assert(found);
  assert(face(h2, f.m) == Traits::null_face());

  // split border vertex y
  CGAL::Euler::split_vertex(h1, h2,f.m);
  assert(CGAL::is_valid_polygon_mesh(f.m));
  assert(CGAL::internal::exact_num_vertices(f.m) == 7);
  assert(CGAL::internal::exact_num_edges(f.m) == 8);
  assert(boost::distance(CGAL::halfedges_around_face(h1, f.m)) == 5);
  assert(boost::distance(CGAL::halfedges_around_face(h2, f.m)) == 7);
}

template <typename T>
void
split_join_vertex_inverse()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_3<T> f;
  halfedge_descriptor h, h1, h2;
  bool found;
  boost::tie(h, found) = halfedge(f.w, f.x, f.m);
  assert(found);
  CGAL::Euler::join_vertex(h,f.m);
  assert(CGAL::is_valid_polygon_mesh(f.m));
  boost::tie(h1, found) = halfedge(f.z, f.x, f.m);
  assert(found);
  boost::tie(h2, found) = halfedge(f.v, f.x, f.m);
  assert(found);
  CGAL::Euler::join_vertex(CGAL::Euler::split_vertex(h1, h2,f.m),f.m);
  assert(CGAL::is_valid_polygon_mesh(f.m));

  assert(CGAL::internal::exact_num_vertices(f.m)== 5);
  assert(CGAL::internal::exact_num_faces(f.m) == 2);
  assert(CGAL::internal::exact_num_edges(f.m) == 6);
  assert(CGAL::internal::exact_num_halfedges(f.m) == 12);
  assert(boost::distance(CGAL::halfedges_around_face(h1, f.m)) == 3);
  assert(boost::distance(CGAL::halfedges_around_face(h2, f.m)) == 3);
}

template <typename T>
void
join_loop_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_4<T> f;

  CGAL::Euler::join_loop(f.h1, f.h2, f.m);

  assert(CGAL::is_valid_polygon_mesh(f.m));
}

template <typename T>
void
split_loop_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_8<T> f;

  CGAL::Euler::split_loop(f.h1, f.h2, f.h3, f.m);
  assert(CGAL::internal::exact_num_vertices(f.m) == 8);
  assert(CGAL::internal::exact_num_faces(f.m) == 8);
  assert(CGAL::internal::exact_num_halfedges(f.m) == 24);
  assert(CGAL::is_valid_polygon_mesh(f.m));
}

template <typename T>
void
split_face_test()
{
 Surface_fixture_6<T> f;
 CGAL::Euler::split_face(f.h1, f.h2,f.m);
 assert(num_vertices(f.m) == 4);
 assert(num_faces(f.m) == 2);
 assert(num_halfedges(f.m) == 10);
}

template <typename T>
void
 make_hole_test()
{
 Surface_fixture_7<T> f;
 std::size_t nv = num_vertices(f.m);
 std::size_t nf = num_faces(f.m);
 std::size_t nh = num_halfedges(f.m);

 CGAL::Euler::make_hole(f.h, f.m);

 assert(CGAL::internal::exact_num_vertices(f.m) == nv);
 assert(CGAL::internal::exact_num_faces(f.m) == nf-1 );
 assert(CGAL::internal::exact_num_halfedges(f.m) == nh);
}

template <typename T>
void
remove_center_vertex_test()
{
 Surface_fixture_7<T> f;
 std::size_t nv = num_vertices(f.m);
 std::size_t nf = num_faces(f.m);
 std::size_t nh = num_halfedges(f.m);

 typename boost::graph_traits<T>::degree_size_type deg = degree(target(f.h,f.m),f.m);
 CGAL::Euler::remove_center_vertex(f.h,f.m);

 assert(CGAL::internal::exact_num_vertices(f.m) == nv-1);
 assert(CGAL::internal::exact_num_faces(f.m) == (nf-deg)+1);
 assert(CGAL::internal::exact_num_halfedges(f.m) == nh-(2*deg));
}

template <typename T>
void
join_split_inverse()
{

}

template <typename T>
void
does_satisfy_link_condition()
{
  Surface_fixture_7<T> f;

  assert(CGAL::Euler::does_satisfy_link_condition(*edges(f.m).first,f.m));
}

template <typename Graph>
void
test_swap_edges()
{
  typedef typename boost::graph_traits<Graph>::halfedge_descriptor halfedge_descriptor;
  std::size_t nbh=12;
  Kernel::Point_3 pt(0,0,0);
  // test all possible pairs of halfedges
  for (std::size_t i=0; i<nbh-1; ++i)
  {
    for(std::size_t j=i+1; j<nbh; ++j)
    {
      Graph g;
      CGAL::make_tetrahedron(pt,pt,pt,pt,g);
      halfedge_descriptor h1 = *std::next(boost::begin(halfedges(g)), i);
      halfedge_descriptor h2 = *std::next(boost::begin(halfedges(g)), j);
      CGAL::internal::swap_edges(h1, h2, g);
      CGAL_assertion(CGAL::is_valid_polygon_mesh(g));
    }
  }
}

template <typename T>
void
add_face_bug()
{
  typedef boost::graph_traits<T> GT;
  typedef typename GT::vertex_descriptor vertex_descriptor;
  typedef typename GT::halfedge_descriptor halfedge_descriptor;

  T g;

  std::vector<vertex_descriptor> vs;
  vs.push_back( add_vertex(g) ); // Kernel::Point_3(0,1,0)
  vs.push_back( add_vertex(g) ); // Kernel::Point_3(4,1,0)
  vs.push_back( add_vertex(g) ); // Kernel::Point_3(5,2,0)
  vs.push_back( add_vertex(g) ); // Kernel::Point_3(4,0,0)

  CGAL::Euler::add_face(CGAL::make_array(vs[0], vs[1], vs[2]), g);
  CGAL::Euler::add_face(CGAL::make_array(vs[1], vs[3], vs[2]), g);

  // force vertex halfedge to not be a border halfedge
  for(vertex_descriptor v : vertices(g))
  {
    halfedge_descriptor h = halfedge(v, g);
    if ( CGAL::is_border(h, g) )
      set_halfedge(v, prev(opposite(h, g), g), g);
    assert(target(halfedge(v, g), g)==v);
  }

  vs.push_back( add_vertex(g) ); // Kernel::Point_3(0,0,0)
  vs.push_back( add_vertex(g) );  // Kernel::Point_3(1,0,0)
  CGAL::Euler::add_face(CGAL::make_array(vs[4],vs[5],vs[0]), g);

  vs.push_back( add_vertex(g) ); // Kernel::Point_3(2,0,0)
  vs.push_back( add_vertex(g) ); // Kernel::Point_3(3,0,0)
  CGAL::Euler::add_face(CGAL::make_array(vs[6],vs[7],vs[1]), g);
  CGAL::Euler::add_face(CGAL::make_array(vs[7],vs[3],vs[1]), g);
}

template <typename T>
void
add_faces()
{
  typedef typename boost::graph_traits<T>::vertex_descriptor vertex_descriptor;
  typedef typename boost::graph_traits<T>::face_descriptor face_descriptor;
  typedef typename boost::graph_traits<T>::halfedge_descriptor halfedge_descriptor;

  // read a mesh with bord + test append
  {
  T m;

  for (int i=0; i<2; ++i)
  {
    std::ifstream in("data/head.off");
    std::vector<Kernel::Point_3> points;
    std::vector<std::array<std::size_t, 3> > faces_ids;
    CGAL::read_OFF(in, points, faces_ids);

    std::vector<vertex_descriptor> verts;
    verts.reserve(points.size());
    std::vector<std::array<vertex_descriptor, 3> > faces_vs;
    faces_vs.reserve(faces_ids.size());
    for (std::size_t k=0; k<points.size(); ++k)
      verts.push_back( add_vertex(m) );
    for (const std::array<std::size_t, 3>& f : faces_ids)
      faces_vs.push_back( CGAL::make_array(verts[f[0]], verts[f[1]], verts[f[2]]) );
    CGAL::Euler::add_faces(faces_vs, m);
  }
  assert(faces(m).size()==2*2918);
  assert(vertices(m).size()==2*1487);
  assert( CGAL::is_valid_polygon_mesh(m) );
  }

  // closing a cube no extra vertex
  {
    std::ifstream in("data/open_cube.off");
    T m;
    CGAL::read_OFF(in, m);
    std::vector<vertex_descriptor> verts(vertices(m).begin(), vertices(m).end());
    std::list< std::vector<vertex_descriptor> > new_faces;
    new_faces.push_back({verts[1], verts[7], verts[4]});
    new_faces.push_back({verts[1], verts[0], verts[7]});
    CGAL::Euler::add_faces(new_faces, m);
    assert(CGAL::is_closed(m));
    assert(CGAL::is_valid_polygon_mesh(m));
  }
  // closing a cube with extra vertex
  {
    std::ifstream in("data/open_cube.off");
    T m;
    CGAL::read_OFF(in, m);
    std::vector<vertex_descriptor> verts(vertices(m).begin(), vertices(m).end());
    verts.push_back(add_vertex(m));
    put(CGAL::vertex_point, m, verts.back(), Kernel::Point_3(50,0,50));
    std::list< std::vector<vertex_descriptor> > new_faces;
    new_faces.push_back({verts[1], verts[0], verts.back()});
    new_faces.push_back({verts[0], verts[7], verts.back()});
    new_faces.push_back({verts[7], verts[4], verts.back()});
    new_faces.push_back({verts[4], verts[1], verts.back()});

    CGAL::Euler::add_faces(new_faces, m);
    assert(CGAL::is_closed(m));
    assert(CGAL::is_valid_polygon_mesh(m));
  }

  // build a model with non-manifold vertices
  for (int run=0; run<4; ++run)
  {
    T m;
    std::vector<Kernel::Point_3> points;
    points.push_back( Kernel::Point_3(0,0,0) );//v0
    points.push_back( Kernel::Point_3(4,0,0) );//v1
    points.push_back( Kernel::Point_3(8,0,0) );//v2
    points.push_back( Kernel::Point_3(4,1,0) );//v3
    points.push_back( Kernel::Point_3(4,2,0) );//v4
    points.push_back( Kernel::Point_3(4,3,0) );//v5
    points.push_back( Kernel::Point_3(0,3,0) );//v6
    points.push_back( Kernel::Point_3(1,0,3) );//v7
    points.push_back( Kernel::Point_3(2,0,3) );//v8
    points.push_back( Kernel::Point_3(3,0,3) );//v9
    points.push_back( Kernel::Point_3(5,0,3) );//v10
    points.push_back( Kernel::Point_3(6,0,3) );//v11
    points.push_back( Kernel::Point_3(7,0,3) );//v12
    points.push_back( Kernel::Point_3(4,-3,0) );//v13

    std::vector<vertex_descriptor> v;
    v.reserve(points.size());

    for (const Kernel::Point_3& p : points)
    {
      v.push_back(add_vertex(m));
      put(CGAL::vertex_point, m, v.back(), p);
    }

    // used only to control which border halfedge is created first
    std::vector<std::array<vertex_descriptor, 3> > face_array;
    face_array.push_back(CGAL::make_array(v[0], v[1], v[3]));
    face_array.push_back(CGAL::make_array(v[2], v[3], v[1]));
    if (run>1)
      std::swap(face_array[0], face_array[1]);

    // add faces
    CGAL::Euler::add_face(face_array[0], m);
    CGAL::Euler::add_face(face_array[1], m);
    face_descriptor to_be_removed1 = CGAL::Euler::add_face(CGAL::make_array(v[0], v[3], v[4]), m);
    face_descriptor to_be_removed2 = CGAL::Euler::add_face(CGAL::make_array(v[2], v[4], v[3]), m);
    CGAL::Euler::add_face(CGAL::make_array(v[0], v[4], v[5], v[6]), m);
    CGAL::Euler::add_face(CGAL::make_array(v[2], v[5], v[4]), m);
    CGAL::Euler::add_face(CGAL::make_array(v[1], v[7], v[8]), m);
    CGAL::Euler::add_face(CGAL::make_array(v[1], v[8], v[9]), m);
    CGAL::Euler::add_face(CGAL::make_array(v[1], v[10], v[11]), m);
    CGAL::Euler::add_face(CGAL::make_array(v[1], v[11], v[12]), m);

    if (run%2==0)
    {
      for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(to_be_removed1, m), m))
        set_face(h, boost::graph_traits<T>::null_face(), m);
      remove_face(to_be_removed1, m);
    }
    else
    {
      for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(to_be_removed2, m), m))
        set_face(h, boost::graph_traits<T>::null_face(), m);
      remove_face(to_be_removed2, m);
    }
    std::vector< std::vector<vertex_descriptor> > new_faces;
    new_faces.push_back( {v[0], v[13], v[1]} );
    new_faces.push_back( {v[1], v[13], v[2]} );

    std::vector<halfedge_descriptor> border_hedges;
    CGAL::Polygon_mesh_processing::extract_boundary_cycles(m, std::back_inserter(border_hedges));
    assert(border_hedges.size()==2);

    CGAL::Euler::add_faces(new_faces, m);

    border_hedges.clear();
    CGAL::Polygon_mesh_processing::extract_boundary_cycles(m, std::back_inserter(border_hedges));
    assert(border_hedges.size()==3);
  }
}

template <typename Graph>
void
test_Euler_operations()
{
  test_copy_face_graph_nm_umbrella<Graph>();
  join_face_test<Graph>();
  add_vertex_and_face_to_border_test<Graph>();
  add_face_to_border_test<Graph>();
  join_vertex_interior_test<Graph>();
  join_vertex_exterior_test<Graph>();
  split_vertex<Graph>();
  split_join_vertex_inverse<Graph>();
  join_loop_test<Graph>();
  split_loop_test<Graph>();
  split_face_test<Graph>();
  make_hole_test<Graph>();
  remove_center_vertex_test<Graph>();
  join_split_inverse<Graph>();
  does_satisfy_link_condition<Graph>();
  test_swap_edges<Graph>();
  add_face_bug<Graph>();
  add_faces<Graph>();
}

int main()
{
  test_Euler_operations<Polyhedron>();
  test_Euler_operations<SM>();
  test_Euler_operations<LCC>();

#ifdef CGAL_USE_OPENMESH
  test_Euler_operations<OMesh>();
#endif

  std::cerr << "done\n";
  return 0;
}