//=============================================================================
// 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/session/aeva/operators/ChartGeneration.h"
#include "smtk/io/Logger.h"

#include "smtk/model/Face.h"

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

#include "smtk/operation/MarkGeometry.h"

#include "vtk/aeva/ext/vtkDistanceToFeature.h"
#include "vtk/aeva/ext/vtkGrowCharts.h"
#include "vtk/aeva/ext/vtkNewFeatureEdges.h"
#include "vtkCellData.h"
#include "vtkDataSet.h"
#include "vtkDataSetSurfaceFilter.h"
#include "vtkIdTypeArray.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkUnstructuredGrid.h"

#include "smtk/session/aeva/ChartGeneration_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
{
ChartGeneration::Result ChartGeneration::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 for generating charts
  smtk::model::EntityPtr input = this->parameters()->associations()->valueAs<smtk::model::Entity>();
  if (!input->isFace() || !session->isPrimary(*input))
  {
    smtkErrorMacro(this->log(), "Input must be a primary surface.");
    return result;
  }

  // Access feature angle
  smtk::attribute::DoubleItemPtr featureAngleItem = this->parameters()->findDouble("feature angle");
  double featureAngle = featureAngleItem->value();

  // Get input data
  vtkSmartPointer<vtkDataObject> inputData;
  if (!(inputData = ChartGeneration::storage(input)))
  {
    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);
  vtkSmartPointer<vtkUnstructuredGrid> inputUG = vtkUnstructuredGrid::SafeDownCast(inputData);

  // In case the geometric data is an unstructured grid
  if (!inputPD && inputUG)
  {
    vtkNew<vtkDataSetSurfaceFilter> extractSurface;
    extractSurface->SetInputDataObject(inputUG);
    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;
  }

  // Generate chart ids as point data
  vtkNew<vtkNewFeatureEdges> featureEdge;
  featureEdge->SetInputDataObject(inputPD);
  featureEdge->SetFeatureAngle(featureAngle);
  featureEdge->Update();

  vtkNew<vtkDistanceToFeature> distanceToFeature;
  distanceToFeature->SetInputDataObject(0, inputPD);
  distanceToFeature->SetInputDataObject(1, featureEdge->GetOutput());
  distanceToFeature->Update();

  vtkNew<vtkGrowCharts> growCharts;
  growCharts->SetInputDataObject(0, distanceToFeature->GetOutput());
  growCharts->Update();

  // Assign chart ids to input surface
  vtkDataSet::SafeDownCast(inputData)->GetCellData()->SetScalars(
    growCharts->GetOutput()->GetCellData()->GetScalars("chart id"));

  // 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);

  result->findInt("outcome")->setValue(static_cast<int>(ChartGeneration::Outcome::SUCCEEDED));
  return result;
}

const char* ChartGeneration::xmlDescription() const
{
  return ChartGeneration_xml;
}

}
}
}
