//=============================================================================
// 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/TextureAtlas.h"
#include "smtk/io/Logger.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/operation/MarkGeometry.h"

#include "vtkCellData.h"
#include "vtkDataObject.h"
#include "vtkLSCMFilter.h"
#include "vtkPolyData.h"
#include "vtkTexturePackingFilter.h"

#include "smtk/session/aeva/operators/TextureAtlas_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
{
TextureAtlas::Result TextureAtlas::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);

  smtk::model::EntityPtr input = this->parameters()->associations()->valueAs<smtk::model::Entity>();

  // Access input parameters
  smtk::attribute::DoubleItemPtr texelSizeItem = this->parameters()->findDouble("texel size");
  double texelSize = texelSizeItem->value();

  smtk::attribute::DoubleItemPtr textureAtlasWidthItem =
    this->parameters()->findDouble("texture atlas width");
  int textureAtlasWidth = std::ceil(textureAtlasWidthItem->value() / (double)texelSize);

  smtk::attribute::DoubleItemPtr BoundarySpacingItem =
    this->parameters()->findDouble("boundary spacing");
  int BoundarySpacing = std::ceil(BoundarySpacingItem->value() / (double)texelSize);

  // Get input data
  auto data = TextureAtlas::storage(input);
  if (!data)
  {
    smtkErrorMacro(this->log(), "Input has no geometric data.");
    return result;
  }

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

  // If the geometric data is not a polydata...
  if (!inputPD)
  {
    smtkErrorMacro(this->log(), "Input is not a vtkPolyData.");
    return result;
  }

  // Check chart ids
  if (!inputPD->GetCellData()->GetScalars("chart id"))
  {
    smtkErrorMacro(smtk::io::Logger::instance(), "Could not find chart ids in cell data.");
    return result;
  }

  // Compute lscm
  vtkNew<vtkLSCMFilter> lscm;
  lscm->SetInputDataObject(inputPD);
  lscm->SetInputArrayToProcess(vtkLSCMFilter::CHART_ID, 0, 0, vtkDataObject::CELL, "chart id");
  lscm->Update();
  auto* partitionedSurface = lscm->GetOutput(vtkLSCMFilter::ATLAS);
  auto* partitionedBoundary = lscm->GetOutput(vtkLSCMFilter::BOUNDARY);
  auto* partitionedSegments = lscm->GetOutput(vtkLSCMFilter::SEGMENTS);

  // Texture packing
  vtkNew<vtkTexturePackingFilter> packingFilter;
  packingFilter->SetInputDataObject(vtkTexturePackingFilter::ATLAS, partitionedSurface);
  packingFilter->SetInputDataObject(vtkTexturePackingFilter::BOUNDARY, partitionedBoundary);
  packingFilter->SetTexelSize(texelSize);
  packingFilter->SetTextureMapWidth(textureAtlasWidth);
  packingFilter->SetBoundaryTexel(BoundarySpacing);
  packingFilter->Update();
  auto* textureAtlas = packingFilter->GetOutputDataObject(vtkTexturePackingFilter::UNIATLAS);

  auto created = result->findComponent("created");
  smtk::operation::MarkGeometry markGeometry(resource);

  // Add texture atlas to resource
  auto face = resource->addFace();
  std::string prefix = "texture atlas of ";
  face.setName(prefix + input->name());
  input->owningModel()->referenceAs<smtk::model::Model>().addCell(face);
  auto fcomp = face.entityRecord();
  session->addStorage(fcomp->id(), textureAtlas);
  session->setPrimary(*fcomp, false);
  created->appendValue(fcomp);
  markGeometry.markModified(fcomp);

  // Add segments to resource
  for (int i = 0; i < partitionedSegments->GetNumberOfPartitions(); ++i)
  {
    auto* curPD = vtkPolyData::SafeDownCast(partitionedSegments->GetPartition(i));
    auto curChartId = (int)curPD->GetCellData()->GetScalars("chart id")->GetTuple1(0);

    // Add segments to resource
    auto segFace = resource->addFace();
    std::string segName = "chart " + std::to_string(curChartId) + " of " + input->name();
    segFace.setName(segName);
    input->owningModel()->referenceAs<smtk::model::Model>().addCell(segFace);
    auto segComp = segFace.entityRecord();
    session->addStorage(segComp->id(), curPD);
    session->setPrimary(*segComp, false);
    created->appendValue(segComp);
    markGeometry.markModified(segComp);
  }

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

const char* TextureAtlas::xmlDescription() const
{
  return TextureAtlas_xml;
}

}
}
}
