// Copyright (c) 2005  INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1/Principal_component_analysis/include/CGAL/PCA_util.h $
// $Id: include/CGAL/PCA_util.h b26b07a1242 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Pierre Alliez and Sylvain Pion and Ankit Gupta

#ifndef CGAL_LINEAR_LEAST_SQUARES_FITTING_UTIL_H
#define CGAL_LINEAR_LEAST_SQUARES_FITTING_UTIL_H

#include <CGAL/license/Principal_component_analysis.h>


#include <CGAL/Linear_algebraCd.h>
#include <CGAL/Dimension.h>

namespace CGAL {

namespace internal {

// Initialize a matrix in n dimension by an array or numbers
template <typename FT>
typename CGAL::Linear_algebraCd<FT>::Matrix
init_matrix(const int n,
            FT entries[])
{
  CGAL_assertion(n > 1); // dimension > 1
  typedef typename CGAL::Linear_algebraCd<FT>::Matrix Matrix;

  Matrix m(n);
  int i,j;
  for(i = 0; i < n; i++)
    for(j = 0; j < n; j++)
      m[i][j] = entries[i*n+j];

  return m;
} // end initialization of matrix

// assemble covariance matrix from a point set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K& k,                    // kernel
                             const typename K::Point_3*,   // used for indirection
                             const CGAL::Dimension_tag<0>&,
                             const DiagonalizeTraits&)
{
  typedef typename K::FT       FT;
  typedef typename K::Point_3  Point;
  typedef typename K::Vector_3 Vector;

  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  covariance[0] = covariance[1] = covariance[2] =
  covariance[3] = covariance[4] = covariance[5] = (FT)0.0;
  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    const Point& p = *it;
    Vector d = k.construct_vector_3_object()(c,p);
    covariance[0] += d.x() * d.x();
    covariance[1] += d.x() * d.y();
    covariance[2] += d.x() * d.z();
    covariance[3] += d.y() * d.y();
    covariance[4] += d.y() * d.z();
    covariance[5] += d.z() * d.z();
  }
}

// assemble covariance matrix from a triangle set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K&,                    // kernel
                             const typename K::Triangle_3*,// used for indirection
                             const CGAL::Dimension_tag<2>&,
                             const DiagonalizeTraits&)
{
  // Use FT from the DiagonalizeTraits to avoid warnings if the Kernel
  // uses double but the user wants to compute diagonalization using floats
  typedef typename DiagonalizeTraits::Vector::value_type FT;

  typedef typename K::Triangle_3  Triangle;
  typedef typename CGAL::Linear_algebraCd<FT> LA;
  typedef typename LA::Matrix Matrix;


  // assemble covariance matrix as a semi-definite matrix.
  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  //Final combined covariance matrix for all triangles and their combined mass
  FT mass = FT(0.0);

  // assemble 2nd order moment about the origin.
  FT temp[9] = {FT(1.0/12.0), FT(1.0/24.0), FT(1.0/24.0),
                FT(1.0/24.0), FT(1.0/12.0), FT(1.0/24.0),
                FT(1.0/24.0), FT(1.0/24.0), FT(1.0/12.0)};
  Matrix moment = init_matrix<FT>(3,temp);

  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    // Now for each triangle, construct the 2nd order moment about the origin.
    // assemble the transformation matrix.
    const Triangle& t = *it;

    // defined for convenience.
    FT delta[9] = {FT(t[0].x()), FT(t[1].x()), FT(t[2].x()),
                   FT(t[0].y()), FT(t[1].y()), FT(t[2].y()),
                   FT(t[0].z()), FT(t[1].z()), FT(t[2].z())};
    Matrix transformation = init_matrix<FT>(3,delta);
    FT area = FT(std::sqrt(t.squared_area()));

                // skip zero measure primitives
    if(area == (FT)0.0)
                        continue;

    // Find the 2nd order moment for the triangle wrt to the origin by an affine transformation.

    // Transform the standard 2nd order moment using the transformation matrix
    transformation = 2 * area * transformation * moment * LA::transpose(transformation);

    // and add to covariance matrix
    covariance[0] += transformation[0][0];
    covariance[1] += transformation[1][0];
    covariance[2] += transformation[2][0];
    covariance[3] += transformation[1][1];
    covariance[4] += transformation[2][1];
    covariance[5] += transformation[2][2];

    mass += area;
  }

  // Translate the 2nd order moment calculated about the origin to
  // the center of mass to get the covariance.
  covariance[0] += mass * FT(-1.0 * c.x() * c.x());
  covariance[1] += mass * FT(-1.0 * c.x() * c.y());
  covariance[2] += mass * FT(-1.0 * c.z() * c.x());
  covariance[3] += mass * FT(-1.0 * c.y() * c.y());
  covariance[4] += mass * FT(-1.0 * c.z() * c.y());
  covariance[5] += mass * FT(-1.0 * c.z() * c.z());

}

// assemble covariance matrix from a cuboid set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K& ,                    // kernel
                             const typename K::Iso_cuboid_3*,// used for indirection
                             const CGAL::Dimension_tag<3>&,
                             const DiagonalizeTraits&)
{
  typedef typename K::FT          FT;
  typedef typename K::Iso_cuboid_3    Iso_cuboid;
  typedef typename CGAL::Linear_algebraCd<FT> LA;
  typedef typename LA::Matrix Matrix;

  // assemble covariance matrix as a semi-definite matrix.
  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  // final combined covariance matrix for all cuboids and their combined mass
  FT mass = (FT)0.0;

  // assemble 2nd order moment about the origin.
  FT temp[9] = {(FT)(1.0/3.0), (FT)(1.0/4.0), (FT)(1.0/4.0),
                                                                (FT)(1.0/4.0), (FT)(1.0/3.0), (FT)(1.0/4.0),
                (FT)(1.0/4.0), (FT)(1.0/4.0), (FT)(1.0/3.0)};
  Matrix moment = init_matrix<FT>(3,temp);

  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    // Now for each cuboid, construct the 2nd order moment about the origin.
    // assemble the transformation matrix.
    const Iso_cuboid& t = *it;

    // defined for convenience.
    // FT example = CGAL::to_double(t[0].x());
    FT x0 = t[0].x();
    FT y0 = t[0].y();
    FT z0 = t[0].z();
    FT delta[9] = {t[1].x()-x0, t[3].x()-x0, t[5].x()-x0,
                   t[1].y()-y0, t[3].y()-y0, t[5].y()-y0,
                   t[1].z()-z0, t[3].z()-z0, t[5].z()-z0};
    Matrix transformation = init_matrix<FT>(3,delta);
    FT volume = t.volume();

                // skip zero measure primitives
    if(volume == (FT)0.0)
                        continue;

    // Find the 2nd order moment for the cuboid wrt to the origin by an affine transformation.

    // Transform the standard 2nd order moment using the transformation matrix
    transformation = volume * transformation * moment * LA::transpose(transformation);

    // Translate the 2nd order moment to the minimum corner (x0,y0,z0) of the cuboid.
    FT xav0 = (delta[0] + delta[1] + delta[2])/4.0;
    FT yav0 = (delta[3] + delta[4] + delta[5])/4.0;
    FT zav0 = (delta[6] + delta[7] + delta[8])/4.0;

    // and add to covariance matrix
    covariance[0] += transformation[0][0] + volume * (2*x0*xav0 + x0*x0);
    covariance[1] += transformation[1][0] + volume * (xav0*y0 + yav0*x0 + x0*y0);
    covariance[2] += transformation[2][0] + volume * (x0*zav0 + xav0*z0 + x0*z0);
    covariance[3] += transformation[1][1] + volume * (2*y0*yav0 + y0*y0);
    covariance[4] += transformation[2][1] + volume * (yav0*z0 + y0*zav0 + z0*y0);
    covariance[5] += transformation[2][2] + volume * (2*zav0*z0 + z0*z0);

    mass += volume;
  }

  // Translate the 2nd order moment calculated about the origin to
  // the center of mass to get the covariance.
  covariance[0] += mass * (- c.x() * c.x());
  covariance[1] += mass * (- c.x() * c.y());
  covariance[2] += mass * (- c.z() * c.x());
  covariance[3] += mass * (- c.y() * c.y());
  covariance[4] += mass * (- c.z() * c.y());
  covariance[5] += mass * (- c.z() * c.z());
}

// assemble covariance matrix from a cuboid set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K& ,                    // kernel
                             const typename K::Iso_cuboid_3*,// used for indirection
                             const CGAL::Dimension_tag<2>&,
                             const DiagonalizeTraits&)
{
  typedef typename K::FT FT;
  typedef typename K::Iso_cuboid_3 Iso_cuboid;
  typedef typename CGAL::Linear_algebraCd<FT> LA;
  typedef typename LA::Matrix Matrix;

  // assemble covariance matrix as a semi-definite matrix.
  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  //Final combined covariance matrix for all cuboids and their combined mass
  FT mass = (FT)0.0;

  // assemble 2nd order moment about the origin.
  FT temp[9] = {(FT)(7.0/3.0), (FT)1.5,       (FT)1.5,
                (FT)1.5,       (FT)(7.0/3.0), (FT)1.5,
                (FT)1.5,       (FT)1.5,       (FT)(7.0/3.0)};
  Matrix moment = init_matrix<FT>(3,temp);

  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    // Now for each cuboid, construct the 2nd order moment about the origin.
    // assemble the transformation matrix.
    const Iso_cuboid& t = *it;

    // defined for convenience.
    FT x0 = t[0].x();
    FT y0 = t[0].y();
    FT z0 = t[0].z();
    FT delta[9] = {t[1].x()-x0, t[3].x()-x0, t[5].x()-x0,
                   t[1].y()-y0, t[3].y()-y0, t[5].y()-y0,
                   t[1].z()-z0, t[3].z()-z0, t[5].z()-z0};
    Matrix transformation = init_matrix<FT>(3,delta);
    FT area = std::pow(delta[0]*delta[0] + delta[3]*delta[3] +
                  delta[6]*delta[6],1/3.0)*std::pow(delta[1]*delta[1] +
                  delta[4]*delta[4] + delta[7]*delta[7],1/3.0)*2 +
                  std::pow(delta[0]*delta[0] + delta[3]*delta[3] +
                  delta[6]*delta[6],1/3.0)*std::pow(delta[2]*delta[2] +
                  delta[5]*delta[5] + delta[8]*delta[8],1/3.0)*2 +
                  std::pow(delta[1]*delta[1] + delta[4]*delta[4] +
                  delta[7]*delta[7],1/3.0)*std::pow(delta[2]*delta[2] +
                  delta[5]*delta[5] + delta[8]*delta[8],1/3.0)*2;

                // skip zero measure primitives
    if(area == (FT)0.0)
                        continue;

    // Find the 2nd order moment for the cuboid wrt to the origin by an affine transformation.

    // Transform the standard 2nd order moment using the transformation matrix
    transformation = area * transformation * moment * LA::transpose(transformation);

    // Translate the 2nd order moment to the minimum corner (x0,y0,z0) of the cuboid.
    FT xav0 = (delta[0] + delta[1] + delta[2])/4.0;
    FT yav0 = (delta[3] + delta[4] + delta[5])/4.0;
    FT zav0 = (delta[6] + delta[7] + delta[8])/4.0;

    // and add to covariance matrix
    covariance[0] += transformation[0][0] + area * (2*x0*xav0 + x0*x0);
    covariance[1] += transformation[1][0] + area * (xav0*y0 + yav0*x0 + x0*y0);
    covariance[2] += transformation[2][0] + area * (x0*zav0 + xav0*z0 + x0*z0);
    covariance[3] += transformation[1][1] + area * (2*y0*yav0 + y0*y0);
    covariance[4] += transformation[2][1] + area * (yav0*z0 + y0*zav0 + z0*y0);
    covariance[5] += transformation[2][2] + area * (2*zav0*z0 + z0*z0);

    mass += area;
  }

  // Translate the 2nd order moment calculated about the origin to
  // the center of mass to get the covariance.
  covariance[0] += mass * (-1.0 * c.x() * c.x());
  covariance[1] += mass * (-1.0 * c.x() * c.y());
  covariance[2] += mass * (-1.0 * c.z() * c.x());
  covariance[3] += mass * (-1.0 * c.y() * c.y());
  covariance[4] += mass * (-1.0 * c.z() * c.y());
  covariance[5] += mass * (-1.0 * c.z() * c.z());

}

// assemble covariance matrix from a sphere set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K&,                     // kernel
                             const typename K::Sphere_3*,  // used for indirection
                             const CGAL::Dimension_tag<3>&,
                             const DiagonalizeTraits&)
{
  typedef typename K::FT          FT;
  typedef typename K::Sphere_3  Sphere;
  typedef typename CGAL::Linear_algebraCd<FT> LA;
  typedef typename LA::Matrix Matrix;

  // assemble covariance matrix as a semi-definite matrix.
  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  //Final combined covariance matrix for all spheres and their combined mass
  FT mass = 0.0;

  // assemble 2nd order moment about the origin.
  FT temp[9] = {4.0/15.0, 0.0,      0.0,
                0.0,      4.0/15.0, 0.0,
                0.0,      0.0,      4.0/15.0};
  Matrix moment = init_matrix<FT>(3,temp);

  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    // Now for each sphere, construct the 2nd order moment about the origin.
    // assemble the transformation matrix.
    const Sphere& t = *it;

    // defined for convenience.
    FT radius = std::sqrt(t.squared_radius());
    FT delta[9] = {radius, 0.0, 0.0,
                   0.0, radius, 0.0,
                   0.0, 0.0, radius};
    Matrix transformation = init_matrix<FT>(3,delta);
    FT volume = radius * t.squared_radius();

                // skip zero measure primitives
    if(volume == (FT)0.0)
                        continue;

    // Find the 2nd order moment for the sphere wrt to the origin by an affine transformation.

    // Transform the standard 2nd order moment using the transformation matrix
    transformation = volume * transformation * moment * LA::transpose(transformation);

    volume *= FT(4.0/3.0);
    // Translate the 2nd order moment to the center of the sphere.
    FT x0 = t.center().x();
    FT y0 = t.center().y();
    FT z0 = t.center().z();

    // and add to covariance matrix
    covariance[0] += transformation[0][0] + volume * x0*x0;
    covariance[1] += transformation[1][0] + volume * x0*y0;
    covariance[2] += transformation[2][0] + volume * x0*z0;
    covariance[3] += transformation[1][1] + volume * y0*y0;
    covariance[4] += transformation[2][1] + volume * z0*y0;
    covariance[5] += transformation[2][2] + volume * z0*z0;

    mass += volume;
  }

  // Translate the 2nd order moment calculated about the origin to
  // the center of mass to get the covariance.
  covariance[0] += mass * (-1.0 * c.x() * c.x());
  covariance[1] += mass * (-1.0 * c.x() * c.y());
  covariance[2] += mass * (-1.0 * c.z() * c.x());
  covariance[3] += mass * (-1.0 * c.y() * c.y());
  covariance[4] += mass * (-1.0 * c.z() * c.y());
  covariance[5] += mass * (-1.0 * c.z() * c.z());

}
// assemble covariance matrix from a sphere set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K&,                     // kernel
                             const typename K::Sphere_3*,  // used for indirection
                             const CGAL::Dimension_tag<2>&,
                             const DiagonalizeTraits&)
{
  typedef typename K::FT          FT;
  typedef typename K::Sphere_3  Sphere;
  typedef typename CGAL::Linear_algebraCd<FT> LA;
  typedef typename LA::Matrix Matrix;

  // assemble covariance matrix as a semi-definite matrix.
  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  //Final combined covariance matrix for all spheres and their combined mass
  FT mass = 0.0;

  // assemble 2nd order moment about the origin.
  FT temp[9] = {4.0/3.0, 0.0,     0.0,
                0.0,     4.0/3.0, 0.0,
                0.0,     0.0,     4.0/3.0};
  Matrix moment = init_matrix<FT>(3,temp);

  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    // Now for each sphere, construct the 2nd order moment about the origin.
    // assemble the transformation matrix.
    const Sphere& t = *it;

    // defined for convenience.
    // FT example = CGAL::to_double(t[0].x());
    FT radius = std::sqrt(t.squared_radius());
    FT delta[9] = {radius, 0.0,    0.0,
                   0.0,    radius, 0.0,
                   0.0,    0.0,    radius};
    Matrix transformation = init_matrix<FT>(3,delta);
    FT area = t.squared_radius();

                // skip zero measure primitives
    if(area == (FT)0.0)
                        continue;

    // Find the 2nd order moment for the sphere wrt to the origin by an affine transformation.

    // Transform the standard 2nd order moment using the transformation matrix
    transformation = area * transformation * moment * LA::transpose(transformation);

    area *= FT(4.0);
    // Translate the 2nd order moment to the center of the sphere.
    FT x0 = t.center().x();
    FT y0 = t.center().y();
    FT z0 = t.center().z();

    // and add to covariance matrix
    covariance[0] += transformation[0][0] + area * x0*x0;
    covariance[1] += transformation[1][0] + area * x0*y0;
    covariance[2] += transformation[2][0] + area * x0*z0;
    covariance[3] += transformation[1][1] + area * y0*y0;
    covariance[4] += transformation[2][1] + area * z0*y0;
    covariance[5] += transformation[2][2] + area * z0*z0;

    mass += area;
  }

  // Translate the 2nd order moment calculated about the origin to
  // the center of mass to get the covariance.
  covariance[0] += mass * (-1.0 * c.x() * c.x());
  covariance[1] += mass * (-1.0 * c.x() * c.y());
  covariance[2] += mass * (-1.0 * c.z() * c.x());
  covariance[3] += mass * (-1.0 * c.y() * c.y());
  covariance[4] += mass * (-1.0 * c.z() * c.y());
  covariance[5] += mass * (-1.0 * c.z() * c.z());

}

// assemble covariance matrix from a tetrahedron set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K& ,                    // kernel
                             const typename K::Tetrahedron_3*,// used for indirection
                             const CGAL::Dimension_tag<3>&,
                             const DiagonalizeTraits&)
{
  typedef typename K::FT          FT;
  typedef typename K::Tetrahedron_3  Tetrahedron;
  typedef typename CGAL::Linear_algebraCd<FT> LA;
  typedef typename LA::Matrix Matrix;

  // assemble covariance matrix as a semi-definite matrix.
  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  //Final combined covariance matrix for all tetrahedrons and their combined mass
  FT mass = 0.0;

  // assemble 2nd order moment about the origin.
  FT temp[9] = {1.0/60.0,  1.0/120.0, 1.0/120.0,
                1.0/120.0, 1.0/60.0,  1.0/120.0,
                1.0/120.0, 1.0/120.0, 1.0/60.0};
  Matrix moment = init_matrix<FT>(3,temp);

  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    // Now for each tetrahedron, construct the 2nd order moment about the origin.
    // assemble the transformation matrix.
    const Tetrahedron& t = *it;

    // defined for convenience.
    FT x0 = t[0].x();
    FT y0 = t[0].y();
    FT z0 = t[0].z();

    FT delta[9] = {t[1].x()-x0, t[2].x()-x0, t[3].x()-x0,
                   t[1].y()-y0, t[2].y()-y0, t[3].y()-y0,
                   t[1].z()-z0, t[2].z()-z0, t[3].z()-z0};
    Matrix transformation = init_matrix<FT>(3,delta);
    FT volume = t.volume();

                // skip zero measure primitives
    if(volume == (FT)0.0)
                        continue;

    // Find the 2nd order moment for the tetrahedron wrt to the origin by an affine transformation.

    // Transform the standard 2nd order moment using the transformation matrix
    transformation = 6 * volume * transformation * moment * LA::transpose(transformation);

    // Translate the 2nd order moment to the center of the tetrahedron.
    FT xav0 = (delta[0]+delta[1]+delta[2])/4.0;
    FT yav0 = (delta[3]+delta[4]+delta[5])/4.0;
    FT zav0 = (delta[6]+delta[7]+delta[8])/4.0;

    // and add to covariance matrix
    covariance[0] += transformation[0][0] + volume * (2*x0*xav0 + x0*x0);
    covariance[1] += transformation[1][0] + volume * (xav0*y0 + yav0*x0 + x0*y0);
    covariance[2] += transformation[2][0] + volume * (x0*zav0 + xav0*z0 + x0*z0);
    covariance[3] += transformation[1][1] + volume * (2*y0*yav0 + y0*y0);
    covariance[4] += transformation[2][1] + volume * (yav0*z0 + y0*zav0 + z0*y0);
    covariance[5] += transformation[2][2] + volume * (2*zav0*z0 + z0*z0);

    mass += volume;
  }

  // Translate the 2nd order moment calculated about the origin to
  // the center of mass to get the covariance.
  covariance[0] += mass * (-1.0 * c.x() * c.x());
  covariance[1] += mass * (-1.0 * c.x() * c.y());
  covariance[2] += mass * (-1.0 * c.z() * c.x());
  covariance[3] += mass * (-1.0 * c.y() * c.y());
  covariance[4] += mass * (-1.0 * c.z() * c.y());
  covariance[5] += mass * (-1.0 * c.z() * c.z());
}

// assemble covariance matrix from a segment set
template < typename InputIterator,
           typename K,
           typename DiagonalizeTraits >
void
assemble_covariance_matrix_3(InputIterator first,
                             InputIterator beyond,
                             typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                             const typename K::Point_3& c, // centroid
                             const K& ,                    // kernel
                             const typename K::Segment_3*,// used for indirection
                             const CGAL::Dimension_tag<1>&,
                             const DiagonalizeTraits&)
{
  typedef typename K::FT          FT;
  typedef typename K::Segment_3  Segment;
  typedef typename CGAL::Linear_algebraCd<FT> LA;
  typedef typename LA::Matrix Matrix;

  // assemble covariance matrix as a semi-definite matrix.
  // Matrix numbering:
  // 0 1 2
  //   3 4
  //     5
  //Final combined covariance matrix for all segments and their combined mass
  FT mass = 0.0;

  // assemble 2nd order moment about the origin.
  FT temp[9] = {1.0, 0.5, 0.0,
                0.5, 1.0, 0.0,
                0.0, 0.0, 0.0};
  Matrix moment = (FT)(1.0/3.0) * init_matrix<FT>(3,temp);

  for(InputIterator it = first;
      it != beyond;
      it++)
  {
    // Now for each segment, construct the 2nd order moment about the origin.
    // assemble the transformation matrix.
    const Segment& t = *it;

    // defined for convenience.
    // FT example = CGAL::to_double(t[0].x());
    FT delta[9] = {t[0].x(), t[1].x(), 0.0,
       t[0].y(), t[1].y(), 0.0,
                   t[0].z(), t[1].z(), 1.0};
    Matrix transformation = init_matrix<FT>(3,delta);
    using std::sqrt;
    FT length = sqrt(t.squared_length());

                // skip zero measure primitives
    if(length == (FT)0.0)
                        continue;

    // Find the 2nd order moment for the segment wrt to the origin by an affine transformation.

    // Transform the standard 2nd order moment using the transformation matrix
    transformation = length * transformation * moment * LA::transpose(transformation);

    // and add to covariance matrix
    covariance[0] += transformation[0][0];
    covariance[1] += transformation[1][0];
    covariance[2] += transformation[2][0];
    covariance[3] += transformation[1][1];
    covariance[4] += transformation[2][1];
    covariance[5] += transformation[2][2];

    mass += length;
  }

  // Translate the 2nd order moment calculated about the origin to
  // the center of mass to get the covariance.
  covariance[0] += mass * (-1.0 * c.x() * c.x());
  covariance[1] += mass * (-1.0 * c.x() * c.y());
  covariance[2] += mass * (-1.0 * c.z() * c.x());
  covariance[3] += mass * (-1.0 * c.y() * c.y());
  covariance[4] += mass * (-1.0 * c.z() * c.y());
  covariance[5] += mass * (-1.0 * c.z() * c.z());

}


// compute the eigen values and vectors of the covariance
// matrix and deduces the best linear fitting plane.
// returns fitting quality
template < typename K, typename DiagonalizeTraits >
typename K::FT
fitting_plane_3(typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
                const typename K::Point_3& c,       // centroid
                typename K::Plane_3& plane,         // best fit plane
                const K&,                           // kernel
                const DiagonalizeTraits& )                 // Diagonalize traits
{
  typedef typename K::FT       FT;
  typedef typename K::Plane_3  Plane;
  typedef typename K::Vector_3 Vector;

  // solve for eigenvalues and eigenvectors.
  // eigen values are sorted in ascending order,
  // eigen vectors are sorted in accordance.
  typename DiagonalizeTraits::Vector eigen_values = {{ 0. , 0., 0. }};
  typename DiagonalizeTraits::Matrix eigen_vectors = {{ 0., 0., 0.,
                                                        0., 0., 0.,
                                                        0., 0., 0. }};
  DiagonalizeTraits::diagonalize_selfadjoint_covariance_matrix
    (covariance, eigen_values, eigen_vectors);

  // degenerate case
  if(eigen_values[0] == eigen_values[1] &&
     eigen_values[1] == eigen_values[2])
  {
    // assemble a default horizontal plane that goes
    // through the centroid.
    plane = Plane(c,Vector(FT(0),FT(0),FT(1)));
    return FT(0);
  }
  else // regular and line case
  {
    Vector normal(eigen_vectors[0],
                  eigen_vectors[1],
                  eigen_vectors[2]);
    plane = Plane(c,normal);

    if (eigen_values[1] == 0)
        return FT(0); // line case
    else
        return FT(1) - eigen_values[0] / eigen_values[1]; // regular case
  } // end regular case
}

// compute the eigen values and vectors of the covariance
// matrix and deduces the best linear fitting line
// (this is an internal function)
// returns fitting quality
template < typename K, typename DiagonalizeTraits >
typename K::FT
fitting_line_3(typename DiagonalizeTraits::Covariance_matrix& covariance, // covariance matrix
               const typename K::Point_3& c,       // centroid
               typename K::Line_3& line,           // best fit line
               const K&,                           // kernel
               const DiagonalizeTraits& )                 // Diagonalize traits
{
  typedef typename K::FT       FT;
  typedef typename K::Line_3   Line;
  typedef typename K::Vector_3 Vector;

  // solve for eigenvalues and eigenvectors.
  // eigen values are sorted in ascending order,
  // eigen vectors are sorted in accordance.
  typename DiagonalizeTraits::Vector eigen_values = {{ 0. , 0., 0. }};
  typename DiagonalizeTraits::Matrix eigen_vectors = {{ 0., 0., 0.,
                                                        0., 0., 0.,
                                                        0., 0., 0. }};
  DiagonalizeTraits::diagonalize_selfadjoint_covariance_matrix
    (covariance, eigen_values, eigen_vectors);

    // isotropic case (infinite number of directions)
  if(eigen_values[0] == eigen_values[1] &&
     eigen_values[0] == eigen_values[2])
  {
    // assemble a default line along x axis which goes
    // through the centroid.
    line = Line(c,Vector(FT(1),FT(0),FT(0)));
    return (FT)0.0;
  }
  else
  {
    // regular case
    Vector direction(eigen_vectors[6],eigen_vectors[7],eigen_vectors[8]);
    line = Line(c,direction);
    return (FT)1.0 - eigen_values[1] / eigen_values[2];
  }
}

} // end namespace internal

} //namespace CGAL

#ifdef CGAL_EIGEN3_ENABLED
#include <CGAL/PCA_util_Eigen.h>
#endif


#endif // CGAL_LINEAR_LEAST_SQUARES_FITTING_UTIL_H

