//=============================================================================
// 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
  auto image = this->parameters()->findComponent("image")->value();

  // 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 data is not formatted as such.");
    return result;
  }

  // Find the best input array to use.
  // If passed a volume cell, its name should be the scalar array
  // (unless the user has renamed it).
  auto expectedName = image->name();
  std::string actualName;
  auto* pd = imageData->GetAttributes(vtkDataObject::POINT);
  int aa = -1; // Index of actual array in field data.
  auto* expectedArray = pd->GetArray(expectedName.c_str(), aa);
  if (!expectedArray)
  {
    auto* ps = pd->GetScalars();
    if (ps)
    {
      std::array<int, vtkDataSetAttributes::NUM_ATTRIBUTES> indices;
      pd->GetAttributeIndices(indices.data());
      aa = indices[vtkDataSetAttributes::SCALARS];
      expectedArray = ps;
      expectedName = ps->GetName();
    }
  }
  if (aa < 0 || !expectedArray)
  {
    smtkErrorMacro(this->log(), "No scalar point-data array found on image.");
    return result;
  }
  smtkInfoMacro(this->log(),
    "\n  " << expectedName << " range on input image \"" << image->name()
           << "\": " << expectedArray->GetRange()[0] << " " << expectedArray->GetRange()[1]);

  vtkNew<vtkImageProbeFilter> probe;
  vtkNew<vtkImageInterpolator> interpolator;
  interpolator->SetInterpolationModeToLinear();
  probe->SetSourceData(imageData);
  probe->SetInterpolator(interpolator);

  std::ostringstream info;

  auto targets = this->parameters()->associations();
  std::size_t numberOfTargets = targets->numberOfValues();
  auto modified = result->findComponent("modified");
  for (int ii = 0; ii < numberOfTargets; ++ii)
  {
    smtk::model::EntityPtr input = targets->valueAs<smtk::model::Entity>(ii);

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

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

    // Probing
    probe->SetInputDataObject(inputData);
    probe->Update();

    // Attach ImageScalars to geometry
    vtkSmartPointer<vtkDataSet> output = probe->GetOutput();
    auto* scalarArray = output->GetPointData()->GetScalars("ImageScalars");
    if (scalarArray != nullptr)
    {
      // Copy the filter output into a new array with the proper name
      // as we re-use the filter in this loop and do not want the same
      // array on every target geometry.
      auto* scalarCopy =
        vtkDataArray::SafeDownCast(vtkAbstractArray::CreateArray(scalarArray->GetDataType()));
      scalarCopy->DeepCopy(scalarArray);
      scalarCopy->SetName(expectedName.c_str());
      vtkDataSet::SafeDownCast(inputData)
        ->GetAttributes(vtkDataObject::AttributeTypes::POINT)
        ->SetScalars(scalarCopy);
      info << "\n  " << scalarCopy->GetName() << " range on " << input->name() << ": "
           << output->GetScalarRange()[0] << " " << output->GetScalarRange()[1];
    }
    else
    {
      smtkErrorMacro(this->log(), "ImageScalars attribute is not found after probing.");
      return result;
    }

    // Mark the input face to update its representative geometry
    smtk::operation::MarkGeometry markGeometry(
      std::dynamic_pointer_cast<smtk::geometry::Resource>(input->resource()));
    markGeometry.markModified(input);

    // Add the input face to the operation result's "modified" item
    modified->appendValue(input);
  }

  if (!info.str().empty())
  {
    smtkInfoMacro(this->log(), info.str());
  }

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

  return result;
}

const char* ImprintImage::xmlDescription() const
{
  return ImprintImage_xml;
}

}
}
}
