//=============================================================================
// 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/UnreferencedPrimitives.h"
#include "vtk/aeva/ext/vtkGlobalIdBooleans.h"

#include "smtk/model/Edge.h"
#include "smtk/model/Face.h"
#include "smtk/model/Model.h"
#include "smtk/model/Vertex.h"
#include "smtk/model/Volume.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/operation/MarkGeometry.h"

#ifdef SMTK_ENABLE_PARAVIEW_SUPPORT
#include "pqApplicationCore.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h"
#endif

#include "smtk/view/Selection.h"

#include "vtkAppendFilter.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/UnreferencedPrimitives_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
{

vtkSmartPointer<vtkDataObject> VerticesOfData(vtkSmartPointer<vtkDataObject>& data)
{
  auto resultData = vtkSmartPointer<vtkPolyData>::New();
  vtkNew<vtkPointSource> pointSource;
  vtkNew<vtkGlyph3D> glyph;
  pointSource->SetNumberOfPoints(1);
  pointSource->SetRadius(0.0);
  glyph->SetInputDataObject(data);
  glyph->SetSourceConnection(pointSource->GetOutputPort());
  glyph->FillCellDataOn();
  glyph->Update();
  resultData->ShallowCopy(glyph->GetOutput());
  return resultData;
}

}

namespace smtk
{
namespace session
{
namespace aeva
{

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

  auto modelComp =
    std::dynamic_pointer_cast<smtk::model::Entity>(this->parameters()->associations()->value());
  if (!modelComp || !modelComp->isModel())
  {
    return result;
  }
  int primitiveDim = this->parameters()->findInt("primitive type")->value();
  if (primitiveDim < 0 || primitiveDim > 3)
  {
    return result;
  }

  auto resource = dynamic_pointer_cast<smtk::session::aeva::Resource>(modelComp->resource());
  if (!resource)
  {
    return result;
  }
  auto session = resource->session();
  auto created = result->findComponent("created");
  smtk::operation::MarkGeometry geomMarker(resource);
  auto cells = smtk::model::Model(modelComp).cells();
  smtk::model::EntityRefs primaries;
  // Collect all of the model's primary geometry:
  for (auto& cell : cells)
  {
    auto cellRec = cell.entityRecord();
    auto data = Operation::storage(cellRec);
    if (!session->isPrimary(*cellRec))
    {
      continue;
    }
    if (data && (primitiveDim == 0 || cell.dimension() == primitiveDim))
    {
      primaries.insert(cell);
    }
    smtk::model::EntityRefs bdys = cell.lowerDimensionalBoundaries(-1);
    for (const auto& bdy : bdys)
    {
      if ((primitiveDim == 0 || bdy.dimension() == primitiveDim) &&
        session->isPrimary(*bdy.entityRecord()))
      {
        primaries.insert(bdy);
      }
    }
  }
  std::set<smtk::model::Entity::Ptr> createdSideSets;
  for (const auto& primary : primaries)
  {
    auto primaryComp = primary.entityRecord();
    auto primaryData = Operation::storage(primaryComp);
    if (!primaryData)
    {
      continue;
    }
    std::set<smtk::model::Entity*> sideSets;
    session->sideSets(primaryComp.get(), sideSets);
    vtkSmartPointer<vtkDataObject> resultData;
    if (sideSets.empty())
    {
      if (primitiveDim > 0)
      {
        resultData = primaryData->NewInstance();
        resultData->ShallowCopy(primaryData);
      }
      else
      {
        // Before feeding to global-id boolean, turn the primary
        // geometry into vertex cells.
        resultData = VerticesOfData(primaryData);
        // std::cout << vtkPolyData::SafeDownCast(resultData)->GetNumberOfVerts() << " verts\n";
      }
    }
    else
    {
      vtkNew<vtkGlobalIdBooleans> diff;
      vtkNew<vtkAppendFilter> append;
      for (const auto& sideSet : sideSets)
      {
        auto sideSetData = Operation::storage(sideSet);
        if (sideSetData && sideSet->dimension() == primitiveDim)
        {
          append->AddInputData(sideSetData);
        }
      }
      append->Update();
      diff->SetWorkpiece(primitiveDim > 0 ? primaryData : VerticesOfData(primaryData));
      diff->SetTool(append->GetOutputDataObject(0));
      diff->SetOperationToDifference();
      diff->Update();
      resultData = diff->GetOutputDataObject(0);
    }
    std::ostringstream name;
    name << "unreferenced ";
    smtk::model::EntityRef unrefCell;
    switch (primitiveDim)
    {
      case 0:
        name << "nodes";
        unrefCell = resource->addVertex();
        break;
      case 1:
        name << "edges";
        unrefCell = resource->addEdge();
        break;
      case 2:
        name << "areas";
        unrefCell = resource->addFace();
        break;
      case 3:
        name << "regions";
        unrefCell = resource->addVolume();
        break;
      default:
        name << "stuff";
        unrefCell = resource->addAuxiliaryGeometry();
        break;
    }
    name << " of " << primary.name();
    unrefCell.setName(name.str());
    primary.owningModel().addCell(unrefCell);
    auto unrefComp = unrefCell.entityRecord();
    session->addStorage(unrefComp->id(), resultData);
    session->setPrimary(*unrefComp, false);
    created->appendValue(unrefComp);
    geomMarker.markModified(unrefComp);
    createdSideSets.insert(unrefComp);
  }

#ifdef SMTK_ENABLE_PARAVIEW_SUPPORT
  auto* app = pqApplicationCore::instance();
  auto* behavior = app ? pqSMTKBehavior::instance() : nullptr;
  auto* wrapper = behavior ? behavior->builtinOrActiveWrapper() : nullptr;
  if (wrapper)
  {
    auto selection = wrapper->smtkSelection();
    int selectedValue = selection->selectionValueFromLabel("selected");
    selection->modifySelection(createdSideSets,
      "unreferenced primitives feature",
      selectedValue,
      smtk::view::SelectionAction::UNFILTERED_REPLACE,
      /* bitwise */ true,
      /* notify */ false);
  }
#endif

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

const char* UnreferencedPrimitives::xmlDescription() const
{
  return UnreferencedPrimitives_xml;
}

}
}
}
