//=============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt 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 "smtk/io/Logger.h"

#include "smtk/session/aeva/CellSelection.h"
#include "smtk/session/aeva/operators/SmoothSurface.h"

#include "smtk/model/Face.h"
#include "smtk/model/Model.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/VoidItem.h"

#include "smtk/geometry/queries/BoundingBox.h"

#include "smtk/operation/MarkGeometry.h"

#include "smtk/resource/Component.h"

#include <vtkDataArray.h>
#include <vtkDataObject.h>
#include <vtkDataSetSurfaceFilter.h>
#include <vtkExtractSurface.h>
#include <vtkPolyData.h>
#include <vtkSmoothPolyDataFilter.h>
#include <vtkWindowedSincPolyDataFilter.h>

#include "smtk/session/aeva/SmoothSurface_xml.h"

// On Windows MSVC 2015+, something is included that defines
// a macro named ERROR to be 0. This causes smtkErrorMacro()
// to expand into garbage (because smtk::io::Logger::ERROR
// gets expanded to smtk::io::Logger::0).
#ifdef ERROR
#undef ERROR
#endif

namespace smtk
{
namespace session
{
namespace aeva
{

SmoothSurface::Result SmoothSurface::operateInternal()
{
  // Access the associated resource and session for the operation
  smtk::session::aeva::Resource::Ptr resource;
  smtk::session::aeva::Session::Ptr session;
  auto result = this->createResult(smtk::operation::Operation::Outcome::FAILED);
  this->prepareResourceAndSession(result, resource, session);

  // Access the input surface to smooth
  smtk::model::EntityPtr input = this->parameters()->associations()->valueAs<smtk::model::Entity>();

  // Access the smoothing filter type
  smtk::attribute::IntItemPtr smoothingMethodItem = this->parameters()->findInt("smoothing method");
  int smoothingMethod = smoothingMethodItem->value();

  // Access the number of iterations to perform
  smtk::attribute::IntItemPtr nIterationsItem = this->parameters()->findInt("number of iterations");
  int nIterations = nIterationsItem->value();

  // Access the passband value for the windowed sinc filter
  smtk::attribute::DoubleItemPtr passbandItem = this->parameters()->findDouble("passband");
  double passband = (passbandItem ? passbandItem->value() : 0.1);

  // Access the flag to normalize coordinates
  smtk::attribute::VoidItemPtr normalizeCoordinatesItem =
    this->parameters()->findVoid("normalize coordinates");
  bool normalizeCoordinates =
    (normalizeCoordinatesItem ? normalizeCoordinatesItem->isEnabled() : false);

  // Access the convergence criterion
  smtk::attribute::DoubleItemPtr convergenceCriterionItem =
    this->parameters()->findDouble("convergence criterion");
  double convergenceCriterion = (convergenceCriterionItem ? convergenceCriterionItem->value() : 0);

  // Access the relaxation factor
  smtk::attribute::DoubleItemPtr relaxationFactorItem =
    this->parameters()->findDouble("relaxation factor");
  double relaxationFactor = (relaxationFactorItem ? relaxationFactorItem->value() : 0.1);

  // Access the feature angle value
  smtk::attribute::DoubleItemPtr featureAngleItem = this->parameters()->findDouble("feature angle");
  bool useFeatureAngle = (featureAngleItem ? featureAngleItem->isEnabled() : false);
  double featureAngle = (useFeatureAngle ? featureAngleItem->value() : 45.);

  // Access the edge angle value
  smtk::attribute::DoubleItemPtr edgeAngleItem = this->parameters()->findDouble("edge angle");
  double edgeAngle = edgeAngleItem->value();

  // Access the flag to perform boundary smoothing
  smtk::attribute::VoidItemPtr boundarySmoothingItem =
    this->parameters()->findVoid("boundary smoothing");
  bool boundarySmoothing = (boundarySmoothingItem ? boundarySmoothingItem->isEnabled() : false);

  // Access the flag to perform non-manifold smoothing
  smtk::attribute::VoidItemPtr nonmanifoldSmoothingItem =
    this->parameters()->findVoid("non-manifold smoothing");
  bool nonmanifoldSmoothing =
    (nonmanifoldSmoothingItem ? nonmanifoldSmoothingItem->isEnabled() : false);

  vtkSmartPointer<vtkDataObject> inputData;
  if (!(inputData = session->findStorage(input->id())))
  {
    smtkErrorMacro(this->log(), "Input has no geometric data.");
    return result;
  }

  // Access the geometric data corresponding to the input face
  vtkSmartPointer<vtkPolyData> inputPD = vtkPolyData::SafeDownCast(inputData);

  // If the geometric data is not a polydata...
  if (!inputPD)
  {
    //...extract its surface as polydata.
    vtkNew<vtkDataSetSurfaceFilter> extractSurface;
    extractSurface->PassThroughCellIdsOn();
    extractSurface->SetInputDataObject(inputData);
    extractSurface->Update();
    inputPD = extractSurface->GetOutput();
  }
  // If we still have no input polydata, there's not much we can do.
  if (!inputPD)
  {
    smtkErrorMacro(smtk::io::Logger::instance(), "Could not generate surface polydata.");
    return result;
  }

  vtkSmartPointer<vtkPolyData> outputPD;
  if (smoothingMethod == 0) //Taubin smoothing
  {
    // Run the windowed sinc filter on the face's geometry
    vtkNew<vtkWindowedSincPolyDataFilter> windowedSinc;
    windowedSinc->SetInputDataObject(inputPD);
    windowedSinc->SetNumberOfIterations(nIterations);
    windowedSinc->SetPassBand(passband);
    windowedSinc->SetNormalizeCoordinates(normalizeCoordinates);
    windowedSinc->SetFeatureEdgeSmoothing(useFeatureAngle);
    windowedSinc->SetFeatureAngle(featureAngle);
    windowedSinc->SetEdgeAngle(edgeAngle);
    windowedSinc->SetBoundarySmoothing(boundarySmoothing);
    windowedSinc->SetNonManifoldSmoothing(nonmanifoldSmoothing);
    windowedSinc->Update();

    outputPD = windowedSinc->GetOutput();
  }
  else if (smoothingMethod == 1) //Laplacian smoothing
  {
    // Run the laplacian smoothing filter on the face's geometry
    vtkNew<vtkSmoothPolyDataFilter> laplacianSmooth;
    laplacianSmooth->SetInputDataObject(inputPD);
    laplacianSmooth->SetNumberOfIterations(nIterations);
    laplacianSmooth->SetRelaxationFactor(relaxationFactor);
    laplacianSmooth->SetConvergence(convergenceCriterion);
    laplacianSmooth->SetFeatureEdgeSmoothing(useFeatureAngle);
    laplacianSmooth->SetFeatureAngle(featureAngle);
    laplacianSmooth->SetEdgeAngle(edgeAngle);
    laplacianSmooth->SetBoundarySmoothing(boundarySmoothing);
    laplacianSmooth->Update();

    outputPD = laplacianSmooth->GetOutput();
  }
  else
  {
    smtkInfoMacro(smtk::io::Logger::instance(), "Could not get smoothing method");
    return result;
  }

  // Assign the geometric data for the input face
  session->addStorage(input->id(), outputPD);

  // Reassign the result to indicate success
  result->findInt("outcome")->setValue(static_cast<int>(SmoothSurface::Outcome::SUCCEEDED));

  // Mark the input face to update its representative geometry
  smtk::operation::MarkGeometry markGeometry(resource);
  markGeometry.markModified(input);

  // Add the input face to the operation result's "modified" item
  smtk::attribute::ComponentItem::Ptr modified = result->findComponent("modified");
  modified->appendValue(input);

  return result;
}

const char* SmoothSurface::xmlDescription() const
{
  return SmoothSurface_xml;
}
}
}
}
