//=============================================================================
// 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/ImprintImage.h"
#include "smtk/session/aeva/ImprintImage_xml.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/io/Logger.h"
#include "smtk/operation/MarkGeometry.h"

#include "vtkDataSetSurfaceFilter.h"
#include "vtkGenericImageInterpolator.h"
#include "vtkImageProbeFilter.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkUnstructuredGrid.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
{

ImprintImage::Result ImprintImage::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);

  // Get inputs
  smtk::model::EntityPtr input = this->parameters()->associations()->valueAs<smtk::model::Entity>();
  auto image = this->parameters()->findComponent("image")->value();

  // Check input geometry
  vtkSmartPointer<vtkDataObject> inputData;
  if (!(inputData = ImprintImage::storage(input)))
  {
    smtkErrorMacro(this->log(), "Input has no geometric data.");
    return result;
  }

  const std::string inputClass = inputData->GetClassName();
  if (inputClass != "vtkPolyData" && inputClass != "vtkUnstructuredGrid")
  {
    smtkErrorMacro(this->log(), "Input is neither a poly data nor an unstructured grid");
    return result;
  }

  // Check input image
  vtkSmartPointer<vtkDataObject> imageData;
  if (!image || !(imageData = ImprintImage::storage(image)))
  {
    smtkErrorMacro(this->log(), "No image available or image has no geometric data.");
    return result;
  }

  const std::string imageClass = imageData->GetClassName();
  if (imageClass != "vtkImageData")
  {
    smtkErrorMacro(this->log(), "The image is not a vtkImageData.");
    return result;
  }

  // Probing
  vtkNew<vtkImageProbeFilter> probe;
  vtkNew<vtkImageInterpolator> interpolator;
  interpolator->SetInterpolationModeToLinear();
  probe->SetSourceData(imageData);
  probe->SetInputDataObject(inputData);
  probe->SetInterpolator(interpolator);
  probe->Update();

  // Attach ImageScalars to geometry
  vtkSmartPointer<vtkDataSet> output = probe->GetOutput();
  auto* scalarArray = output->GetPointData()->GetScalars("ImageScalars");
  if (scalarArray != nullptr)
  {
    vtkDataSet::SafeDownCast(inputData)
      ->GetAttributes(vtkDataObject::AttributeTypes::POINT)
      ->SetScalars(scalarArray);
    smtkInfoMacro(this->log(),
      "image scalar range: " << output->GetScalarRange()[0] << " " << output->GetScalarRange()[1]);
  }
  else
  {
    smtkErrorMacro(this->log(), "ImageScalars attribute is not found after probing.");
    return result;
  }

  // Reassign the result to indicate success
  result->findInt("outcome")->setValue(static_cast<int>(ImprintImage::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* ImprintImage::xmlDescription() const
{
  return ImprintImage_xml;
}

}
}
}
