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

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/VoidItem.h"
#include "smtk/io/Logger.h"
#include "smtk/model/Volume.h"
#include "smtk/operation/MarkGeometry.h"
#include "vtk/aeva/ext/vtkImageNarrowBand.h"

#include "vtkImageData.h"
#include "vtkMultiThreader.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkUnstructuredGrid.h"

#include "vtkNIFTIImageWriter.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
{

ImprintGeometry::Result ImprintGeometry::operateInternal()
{
  smtk::attribute::ReferenceItemPtr assocs = this->parameters()->associations();

  smtk::operation::Operation::Result result =
    this->createResult(smtk::operation::Operation::Outcome::FAILED);

  if (assocs->numberOfValues() < 2)
  {
    smtkErrorMacro(this->log(),
      "ImprintGeometry operation requires 2 as associations, an image and mesh association");
    return result;
  }

  smtk::model::Model parentModel1 =
    smtk::model::EntityRef(assocs->valueAs<smtk::model::Entity>(0)).owningModel();
  smtk::model::Model parentModel2 =
    smtk::model::EntityRef(assocs->valueAs<smtk::model::Entity>(1)).owningModel();
  smtk::model::Model parentModel =
    parentModel1; // Will take the value of 1 or 2 depending on which contains vtkImageData

  // Identify which association is an image and which is vtkUnstructuredGrid or vtkPolyData
  vtkSmartPointer<vtkDataObject> data1 = Operation::getData(assocs->value(0));
  vtkSmartPointer<vtkDataObject> data2 = Operation::getData(assocs->value(1));
  if (!data1 || !data2)
  {
    smtkErrorMacro(this->log(), "ImprintGeometry: Unable to retrieve geometry for input data");
    return result;
  }

  const std::string class1 = data1->GetClassName();
  const std::string class2 = data2->GetClassName();

  vtkSmartPointer<vtkImageData> input1;
  vtkSmartPointer<vtkDataObject> input2;
  if (class1 == "vtkImageData" && (class2 == "vtkPolyData" || class2 == "vtkUnstructuredGrid"))
  {
    input1 = vtkImageData::SafeDownCast(data1);
    input2 = data2;
  }
  else if (class2 == "vtkImageData" && (class1 == "vtkPolyData" || class1 == "vtkUnstructuredGrid"))
  {
    input1 = vtkImageData::SafeDownCast(data2);
    parentModel = parentModel2; // If data2 is the image, then switch the parent model
    input2 = data1;
  }
  else
  {
    smtkErrorMacro(this->log(),
      "ImprintGeometry: wrong data types, must use imagedata + polydata/unstructuredgrid");
    return result;
  }
  // we want multi-threading, but ParaView sets to 1 by default. Override
  int threadMax = vtkMultiThreader::GetGlobalMaximumNumberOfThreads();
  vtkMultiThreader::SetGlobalMaximumNumberOfThreads(
    vtkMultiThreader::GetGlobalStaticMaximumNumberOfThreads());

  vtkNew<vtkImageNarrowBand> narrowBand;
  narrowBand->SetReferenceImage(input1);
  narrowBand->SetInputData(0, input2);
  narrowBand->SetBandWidth(this->parameters()->findDouble("bandwidth")->value(0));
  bool imprintDistance = this->parameters()->findVoid("imprintDistance")->isEnabled();
  narrowBand->SetUseBinary(!imprintDistance);
  narrowBand->Update();

  // Create a volume within the parentModel
  auto modelComp = parentModel.component();
  modelComp->properties().get<std::string>()["aeva_datatype"] = "image";
  auto volume = parentModel.resource()->addVolume();
  parentModel.addCell(volume);
  auto createdItems = result->findComponent("created");
  createdItems->appendValue(volume.component());

  parentModel.resource()->as<smtk::session::aeva::Resource>()->session()->addStorage(
    volume.entity(), narrowBand->GetOutput());
  if (narrowBand->GetOutput()->GetPointData()->GetScalars())
  {
    volume.setName(narrowBand->GetOutput()->GetPointData()->GetScalars()->GetName());
  }
  operation::MarkGeometry(parentModel.resource()).markModified(volume.component());

  vtkMultiThreader::SetGlobalMaximumNumberOfThreads(threadMax);

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

const char* ImprintGeometry::xmlDescription() const
{
  return ImprintGeometry_xml;
}

}
}
}
