//=============================================================================
// 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/io/Logger.h"

#include "smtk/session/aeva/CellSelection.h"
#include "smtk/session/aeva/operators/PointsOfPrimitivesFeature.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/attribute/StringItem.h"
#include "smtk/attribute/VoidItem.h"

#include "smtk/operation/MarkGeometry.h"

#include "smtk/view/Selection.h"

#include "vtkAppendPolyData.h"
#include "vtkCellData.h"
#include "vtkDataSetSurfaceFilter.h"
#include "vtkDoubleArray.h"
#include "vtkGlyph3D.h"
#include "vtkMath.h"
#include "vtkPointData.h"
#include "vtkPointSource.h"
#include "vtkPolyData.h"
#include "vtkPolyDataNormals.h"
#include "vtkThreshold.h"
#include "vtkVector.h"
#include "vtkVectorOperators.h"

#include <cmath>

#include "smtk/session/aeva/operators/PointsOfPrimitivesFeature_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
{

PointsOfPrimitivesFeature::Result PointsOfPrimitivesFeature::operateInternal()
{
  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);

  auto assocs = this->parameters()->associations();
  auto created = result->findComponent("created");
  auto modified = result->findComponent("modified");
  smtk::operation::MarkGeometry geomMarker(resource);
  std::vector<vtkSmartPointer<vtkPolyData> > pieces;
  smtk::model::Model anOwningModel;
  for (const auto& assoc : *assocs)
  {
    anOwningModel =
      smtk::model::Face(std::dynamic_pointer_cast<smtk::model::Entity>(assoc)).owningModel();
    auto data = PointsOfPrimitivesFeature::storage(assoc);
    if (!data)
    {
      continue;
    }
    vtkSmartPointer<vtkPolyData> surf(vtkPolyData::SafeDownCast(data));
    if (!surf)
    {
      vtkNew<vtkDataSetSurfaceFilter> extractSurface;
      extractSurface->PassThroughCellIdsOn();
      extractSurface->SetInputDataObject(data);
      extractSurface->Update();
      surf = extractSurface->GetOutput();
    }
    if (!surf)
    {
      smtkErrorMacro(this->log(), "Could not generate surface of " << assoc->name() << ".");
      continue;
    }
    if (!surf->GetPointData()->GetNormals())
    {
      vtkNew<vtkPolyDataNormals> genNormals;
      genNormals->SetInputDataObject(surf);
      genNormals->SplittingOff();
      genNormals->ConsistencyOn();
      genNormals->SplittingOff();
      genNormals->ComputePointNormalsOn(); // Without this, rendering is super-flaky.
      genNormals->Update();
      surf = genNormals->GetOutput();
    }
    vtkNew<vtkPolyData> geom;
    geom->ShallowCopy(surf);
    pieces.emplace_back(geom);
  }

  if (!pieces.empty())
  {
    vtkNew<vtkAppendPolyData> append;
    for (const auto& piece : pieces)
    {
      append->AddInputData(piece.GetPointer());
    }
    append->Update();
    // TODO: Remove this after vtkGlyph3D properly copies global IDs to output:
    auto* appAtt = append->GetOutput()->GetAttributes(vtkDataObject::POINT);
    std::string idName = "FAM";
    if (appAtt)
    {
      auto* preGIDs = appAtt->GetGlobalIds();
      if (preGIDs)
      {
        idName = preGIDs->GetName();
        auto* tmp = appAtt->GetGlobalIds();
        if (tmp)
        {
          tmp->Register(nullptr);
          appAtt->RemoveArray(tmp->GetName());
          appAtt->SetScalars(tmp);
          tmp->FastDelete();
        }
      }
    }

    vtkNew<vtkPointSource> pointSource;
    vtkNew<vtkGlyph3D> glyph;
    pointSource->SetNumberOfPoints(1);
    pointSource->SetRadius(0.0);
    glyph->SetInputConnection(append->GetOutputPort());
    glyph->SetSourceConnection(pointSource->GetOutputPort());
    glyph->FillCellDataOn();
    glyph->Update();
    vtkNew<vtkPolyData> geom;
    geom->ShallowCopy(glyph->GetOutput());

    // What were global IDs on points have become global cell IDs (cells are VTK_VERTEX).
    // But this is not what aeva needs, which is global point IDs to be on points rather
    // than cells. Move the array from cell-data to point-data and remove it from the cells:
    geom->GetPointData()->SetGlobalIds(geom->GetCellData()->GetGlobalIds());

    // TODO: Remove me once vtkGlyph3D passes global IDs:
    if (!geom->GetPointData()->GetGlobalIds())
    {
      geom->GetPointData()->SetGlobalIds(geom->GetCellData()->GetArray(idName.c_str()));
    }

    geom->GetCellData()->SetGlobalIds(nullptr);

    // Do not pass along any normal vectors.
    geom->GetPointData()->RemoveArray("Normals");
    geom->GetCellData()->RemoveArray("Normals");

    smtk::view::Selection::Ptr selection;
    if (auto managers = this->managers())
    {
      selection = managers->get<smtk::view::Selection::Ptr>();
    }
    auto resultNameItem = this->parameters()->findString("save result as");
    if (selection && !resultNameItem->isEnabled())
    {
      auto cellSelection = CellSelection::instance();
      if (cellSelection)
      {
        cellSelection->replaceData(geom);
        modified->appendValue(cellSelection);
      }
      else
      {
        auto cellSelection = CellSelection::create(resource, geom, this->manager());
        session->addStorage(cellSelection->id(), geom);
        created->appendValue(cellSelection);
        geomMarker.markModified(cellSelection);
      }
      std::set<smtk::model::EntityPtr> selected;
      selected.insert(cellSelection);
      int selectedValue = selection->selectionValueFromLabel("selected");
      selection->modifySelection(selected,
        "points of primitives feature",
        selectedValue,
        smtk::view::SelectionAction::UNFILTERED_REPLACE,
        /* bitwise */ true,
        /* notify */ false);
    }
    else
    {
      std::string resultName = "points from primitives";
      if (resultNameItem->isEnabled() && resultNameItem->isSet() &&
        !resultNameItem->value().empty())
      {
        resultName = resultNameItem->value();
      }
      auto face = resource->addFace();
      face.setName(resultName);
      anOwningModel.addCell(face);
      auto fcomp = face.entityRecord();
      session->addStorage(fcomp->id(), geom);
      created->appendValue(fcomp);
      geomMarker.markModified(fcomp);
    }
    result->findInt("outcome")->setValue(
      static_cast<int>(PointsOfPrimitivesFeature::Outcome::SUCCEEDED));
  }
  else
  {
    smtkErrorMacro(this->log(), "No output.");
  }

  return result;
}

const char* PointsOfPrimitivesFeature::xmlDescription() const
{
  return PointsOfPrimitivesFeature_xml;
}

}
}
}
