/*=========================================================================

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPolyData.h"
#include "vtkImageData.h"
#include "itkImage.h"
#include "itkModelConvolutionImageFilter.h"
#include "itkPhantomImageSource.h"
#include "itkImageFileWriter.h"
#include "itkLaplacianRecursiveGaussianImageFilter.h"
#include "vtkMetaImageReader.h"
#include "vtkPolyDataConnectivityFilter.h"
#include "vtkSmartPointer.h"
#include "vtkFeatureEdges.h"
#include "vtkXMLPolyDataWriter.h"
#include "vtkMassProperties.h"
#include "vtkPoints.h"
#include "vtkMarchingCubes.h"
#include "vtkMath.h"
#include <fstream>
#include <cmath> 
#include <iostream>

void Compute( int argc, char *argv[], double r )
{
  typedef itk::Image< double, 3 > RealImageType;
  typedef itk::Vector< double, 3 > VectorType;
  typedef itk::SphereImageSource< RealImageType, RealImageType > SphereSourceType;
  typedef itk::ModelConvolutionImageFilter< RealImageType > ConvolutionFilterType;
  typedef itk::LaplacianRecursiveGaussianImageFilter< RealImageType > LaplacianFilterType;
  typedef itk::ImageFileWriter< RealImageType > RealImageWriterType;

  std::cout << "Radius = " << r << std::endl;

  double radius = r;

  std::ofstream of( argv[1], std::ios::app );
  const double spacing[3] = { atof(argv[2]), atof(argv[3]), atof(argv[4]) };
  const double sigma[3] = { atof(argv[5]), atof(argv[5]), atof(argv[5]) };

  if (radius < (6 * spacing[0])) radius = 10*spacing[0];
  
  RealImageType::SizeType size;
  size[0] = 4 * radius / spacing[0];
  size[1] = 4 * radius / spacing[1];
  size[2] = 4 * radius / spacing[2];
  RealImageType::IndexType index;
  index[0] = 0;
  index[1] = 0;
  index[2] = 0;
  index.Fill(0);

  RealImageType::PointType origin, sphereCenter;
  origin[0] = -2.0*radius;
  origin[1] = -2.0*radius;
  origin[2] = -2.0*radius;
  sphereCenter.Fill(0);

  RealImageType::RegionType region( index, size );

  RealImageType::Pointer syntheticSphereImage = RealImageType::New();
  syntheticSphereImage->SetRegions(region);
  syntheticSphereImage->SetOrigin(origin);
  syntheticSphereImage->SetSpacing(spacing);
  syntheticSphereImage->Allocate();
  syntheticSphereImage->FillBuffer(50);

  // Generate a synthetic model of a sphere
  SphereSourceType::Pointer teflonSphereSource = SphereSourceType::New();
  teflonSphereSource->SetQuantization( 32 );
  teflonSphereSource->SetTemplateImage( syntheticSphereImage );
  teflonSphereSource->SetRadius( r );
  teflonSphereSource->SetForeground( 980 );
  teflonSphereSource->SetBackground( 50 );
  teflonSphereSource->SetCenter( sphereCenter );
  teflonSphereSource->SetRegion(syntheticSphereImage->GetBufferedRegion());
  teflonSphereSource->Update();


  ConvolutionFilterType::Pointer conv = ConvolutionFilterType::New();
  conv->SetUseIIRFilter(true);
  conv->SetSigma(sigma);
  conv->SetInput(teflonSphereSource->GetOutput());
  conv->Update();

  RealImageWriterType::Pointer realImageWriter = RealImageWriterType::New();
  realImageWriter->SetInput(conv->GetOutput());
  realImageWriter->SetFileName("ConvolvedImage.mha");
  realImageWriter->SetUseCompression(true);
  //realImageWriter->Update();

  LaplacianFilterType::Pointer laplacian = LaplacianFilterType::New();
  laplacian->SetInput(conv->GetOutput());
  laplacian->SetSigma(sigma[0]);
  laplacian->Update();

  realImageWriter->SetInput(laplacian->GetOutput());
  realImageWriter->SetFileName("Laplacian.mha");
  realImageWriter->SetUseCompression(true);
  realImageWriter->Update();  

  vtkSmartPointer < vtkMetaImageReader > imageReader = 
    vtkSmartPointer< vtkMetaImageReader >::New();
  imageReader->SetFileName("Laplacian.mha");
  imageReader->Update();

  vtkSmartPointer< vtkMarchingCubes > mc =
    vtkSmartPointer< vtkMarchingCubes >::New();
  mc->SetInput(imageReader->GetOutput());
  mc->SetValue(0, 0);
  mc->ComputeNormalsOff();
  mc->ComputeScalarsOff();
  mc->Update();

  std::cout << "  Marching cubes done on laplacian image with isovalue " << mc->GetValue(0) << std::endl;
  std::cout << "    Number of triangles = " << mc->GetOutput()->GetNumberOfCells() << std::endl;


  // find the point closest to (radius, 0, 0);
  vtkPolyData *pd = mc->GetOutput();

  vtkSmartPointer< vtkPolyDataConnectivityFilter > connectivity =
    vtkSmartPointer< vtkPolyDataConnectivityFilter >::New();
  connectivity->SetInput(mc->GetOutput());
  connectivity->SetClosestPoint(radius,0,0);
  connectivity->SetExtractionModeToClosestPointRegion();
  connectivity->Update();

  std::cout << "  Number of triangles in the ZC of interest = " << 
    connectivity->GetOutput()->GetNumberOfCells() << std::endl;

  vtkSmartPointer< vtkFeatureEdges > featureEdges
    = vtkSmartPointer< vtkFeatureEdges >::New();
  featureEdges->SetInput(connectivity->GetOutput());
  featureEdges->BoundaryEdgesOn();
  featureEdges->FeatureEdgesOff();
  featureEdges->NonManifoldEdgesOn();
  featureEdges->Update();
  std::cout << "   Boundary edges = " << featureEdges->GetOutput()->GetNumberOfCells() << std::endl;

  vtkSmartPointer< vtkMassProperties > massProperties =
    vtkSmartPointer< vtkMassProperties >::New();
  massProperties->SetInput(connectivity->GetOutput());
  const double volume = massProperties->GetVolume();
  std::cout << "  NSI = " << massProperties->GetNormalizedShapeIndex() << std::endl;
  std::cout << "  Volume = " << volume << std::endl;
  const double idealVolume = (4.0/3.0 * 3.14159 * r* r* r);
  std::cout << "  IdealVol = " << idealVolume << std::endl;


  // Get the min, max and avg distance measure on the laplacian ZC of interest
  vtkPoints *points = connectivity->GetOutput()->GetPoints();
  const vtkIdType nPoints = points->GetNumberOfPoints();

  double x[3], minDist2 = VTK_DOUBLE_MAX, maxDist2 = VTK_DOUBLE_MIN, d, dSum = 0;
  for (vtkIdType id = 0; id < nPoints; ++id)
    {
    points->GetPoint(id,x);
    d = vtkMath::Norm(x);
    if (d < minDist2) minDist2 = d;
    if (d > maxDist2) maxDist2 = d;
    dSum += d;
    }

  const double dMean = dSum / (double)nPoints;

  std::cout << "  Average distance = " << dMean << std::endl;
  std::cout << "  Min distance = " << minDist2 << std::endl;
  std::cout << "  Max distance = " << maxDist2 << std::endl;


  vtkSmartPointer< vtkXMLPolyDataWriter > meshWriter = 
    vtkSmartPointer< vtkXMLPolyDataWriter >::New();
  meshWriter->SetInput(connectivity->GetOutput());
  meshWriter->SetFileName("laplacianMesh.vtp");
  //meshWriter->Update();

  const double outputRadius = std::pow((volume * 3.0 / (4.0 * 3.14159)), 1.0/3.0);

  of << r << " " 
     << outputRadius << " " 
     << volume << " " 
     << (volume-idealVolume) << " "
     << dMean << " " 
     << (dMean - r) << " " 
     << minDist2 << " " 
     << (minDist2 - r) << " " 
     << maxDist2 << " " 
     << (maxDist2 - r) << " " 
     << massProperties->GetNormalizedShapeIndex() << std::endl;
}

int main( int argc, char *argv[] )
{
  std::ofstream of( argv[1], std::ios::out );
  
  of << "R" << " " 
     << "ComputedRadius" << " " 
     << "Volume" << " " 
     << "(volume-idealVolume)" << " "
     << "dMean" << " " 
     << "(dMean-R)" << " " 
     << "minDist" << " " 
     << "(minDist-R)" << " " 
     << "maxDist" << " " 
     << "(maxDist-R)" << " " 
     << "NSI" << std::endl;
  for (double radius = 0.2; radius < 20; radius += 0.1)
    {
    Compute(argc, argv, radius);
    }

  return EXIT_SUCCESS;
}

