//=========================================================================
//  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/plugin/pqAEVASurfaceFeatureToolBar.h"

#include "smtk/session/aeva/CellSelection.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"
#include "smtk/session/aeva/operators/AdjacencyFeature.h"
#include "smtk/session/aeva/operators/AllPrimitivesFeature.h"
#include "smtk/session/aeva/operators/BooleanIntersect.h"
#include "smtk/session/aeva/operators/BooleanSubtract.h"
#include "smtk/session/aeva/operators/BooleanUnite.h"
#include "smtk/session/aeva/operators/Duplicate.h"
#include "smtk/session/aeva/operators/EditFreeformAttributes.h"
#include "smtk/session/aeva/operators/GrowSelection.h"
#include "smtk/session/aeva/operators/ImprintGeometry.h"
#include "smtk/session/aeva/operators/ImprintImage.h"
#include "smtk/session/aeva/operators/LinearToQuadratic.h"
#include "smtk/session/aeva/operators/NormalFeature.h"
#include "smtk/session/aeva/operators/PointsOfPrimitivesFeature.h"
#include "smtk/session/aeva/operators/ProportionalEdit.h"
#include "smtk/session/aeva/operators/ProximityFeature.h"
#include "smtk/session/aeva/operators/ReconstructSurface.h"
#include "smtk/session/aeva/operators/SmoothSurface.h"
#include "smtk/session/aeva/operators/UnreferencedPrimitives.h"
#include "smtk/session/aeva/operators/VolumeInspect.h"
#include "smtk/session/aeva/operators/VolumeMesher.h"
#include "smtk/session/aeva/plugin/pqFeatureReaction.h"

// SMTK
#include "smtk/view/Selection.h"

#include "smtk/operation/Manager.h"
#include "smtk/operation/Observer.h"

#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h"
#include "smtk/extension/paraview/server/vtkSMSMTKWrapperProxy.h"

// VTK
#include "vtkDataSet.h"

// Qt
#include <QAction>
#include <QLabel>

static pqAEVASurfaceFeatureToolBar* s_surfaceFeatureToolBar = nullptr;

class pqAEVASurfaceFeatureToolBar::pqInternal
{
  pqInternal(pqAEVASurfaceFeatureToolBar* toolbar)
  {
    // Primitive-selection operations
    // ------------------------------
    auto* grow = new QAction("Grow to edges", toolbar);
    grow->setObjectName("GrowSelectionReaction");
    toolbar->addAction(grow);
    m_growSelectionReaction = new pqFeatureReaction<smtk::session::aeva::GrowSelection>(grow);

    auto* nfr = new QAction("Select by surface normal", toolbar);
    nfr->setObjectName("NormalFeatureReaction");
    toolbar->addAction(nfr);
    m_normalFeatureReaction = new pqFeatureReaction<smtk::session::aeva::NormalFeature>(nfr);

    auto* pfr = new QAction("Select by proximity", toolbar);
    pfr->setObjectName("ProximityFeatureReaction");
    toolbar->addAction(pfr);
    m_proximityFeatureReaction = new pqFeatureReaction<smtk::session::aeva::ProximityFeature>(pfr);

    auto* afr = new QAction("Select by adjacency", toolbar);
    afr->setObjectName("AdjacencyFeatureReaction");
    toolbar->addAction(afr);
    m_adjacencyFeatureReaction = new pqFeatureReaction<smtk::session::aeva::AdjacencyFeature>(afr);

    auto* acfr = new QAction("Select all surface primitives", toolbar);
    acfr->setObjectName("AllPrimitivesFeatureReaction");
    toolbar->addAction(acfr);
    m_allPrimitivesFeatureReaction =
      new pqFeatureReaction<smtk::session::aeva::AllPrimitivesFeature>(acfr, true);

    auto* pcfr = new QAction("Select the nodes of all selected primitives", toolbar);
    pcfr->setObjectName("PointsOfPrimitivesFeatureReaction");
    toolbar->addAction(pcfr);
    m_pointsOfPrimitivesFeature =
      new pqFeatureReaction<smtk::session::aeva::PointsOfPrimitivesFeature>(pcfr, true);

    auto* dupr = new QAction("Duplicate cells", toolbar);
    dupr->setObjectName("DuplicateReaction");
    toolbar->addAction(dupr);
    m_duplicateReaction = new pqFeatureReaction<smtk::session::aeva::Duplicate>(dupr, true);

    m_cellSelectionSizeLabel = new QLabel("0 primitives selected", toolbar);
    toolbar->addWidget(m_cellSelectionSizeLabel);

    // Side-set operations
    // -------------------
    toolbar->addSeparator();

    auto* bir = new QAction("Intersect side sets", toolbar);
    bir->setObjectName("IntersectReaction");
    toolbar->addAction(bir);
    m_booleanIntersectReaction = new pqFeatureReaction<smtk::session::aeva::BooleanIntersect>(bir);

    auto* bsr = new QAction("Subtract side sets", toolbar);
    bsr->setObjectName("SubtractReaction");
    toolbar->addAction(bsr);
    m_booleanSubtractReaction = new pqFeatureReaction<smtk::session::aeva::BooleanSubtract>(bsr);

    auto* bur = new QAction("Unite side sets", toolbar);
    bur->setObjectName("UniteReaction");
    toolbar->addAction(bur);
    m_booleanUniteReaction = new pqFeatureReaction<smtk::session::aeva::BooleanUnite>(bur);

    auto* urp = new QAction("Unreferenced primitives", toolbar);
    urp->setObjectName("UnreferencedPrimitives");
    toolbar->addAction(urp);
    m_unreferencedPrimitivesReaction =
      new pqFeatureReaction<smtk::session::aeva::UnreferencedPrimitives>(urp);

    auto* ffa = new QAction("Edit freeform attributes", toolbar);
    ffa->setObjectName("EditFreeformAttributes");
    toolbar->addAction(ffa);
    m_editAttributesReaction =
      new pqFeatureReaction<smtk::session::aeva::EditFreeformAttributes>(ffa);

    // Primary geometry operations
    // ---------------------------
    toolbar->addSeparator();

    auto* src = new QAction("Reconstruct surface", toolbar);
    src->setObjectName("ReconstructSurfaceReaction");
    toolbar->addAction(src);
    m_reconstructSurfaceReaction =
      new pqFeatureReaction<smtk::session::aeva::ReconstructSurface>(src);

    auto* vlm = new QAction("Volume mesh", toolbar);
    vlm->setObjectName("VolumeMesh");
    toolbar->addAction(vlm);
    m_volumeMeshReaction = new pqFeatureReaction<smtk::session::aeva::VolumeMesher>(vlm);

    auto* sms = new QAction("Smooth surface", toolbar);
    sms->setObjectName("SmoothSurfaceReaction");
    toolbar->addAction(sms);
    m_smoothSurfaceReaction = new pqFeatureReaction<smtk::session::aeva::SmoothSurface>(sms);

    auto* qpr = new QAction("Quadratic promotion", toolbar);
    qpr->setObjectName("QuadraticPromotionReaction");
    toolbar->addAction(qpr);
    m_quadraticPromotionReaction =
      new pqFeatureReaction<smtk::session::aeva::LinearToQuadratic>(qpr);

    auto* pefr = new QAction("Proportional Edit", toolbar);
    pefr->setObjectName("ProportionalEditReaction");
    toolbar->addAction(pefr);
    m_proportionalEditReaction = new pqFeatureReaction<smtk::session::aeva::ProportionalEdit>(pefr);

    auto* img = new QAction("Imprint Geometry", toolbar);
    img->setObjectName("ImprintGeometryReaction");
    toolbar->addAction(img);
    m_imprintGeometryReaction = new pqFeatureReaction<smtk::session::aeva::ImprintGeometry>(img);

    auto* imi = new QAction("Imprint Image", toolbar);
    img->setObjectName("ImprintImageReaction");
    toolbar->addAction(imi);
    m_imprintImageReaction = new pqFeatureReaction<smtk::session::aeva::ImprintImage>(imi);

    // Measurement and inspection widgets
    // ----------------------------------
    toolbar->addSeparator();

    auto* viw = new QAction("Volume inspector", toolbar);
    viw->setObjectName("VolumeInspector");
    toolbar->addAction(viw);
    m_volumeInspectorReaction = new pqFeatureReaction<smtk::session::aeva::VolumeInspect>(viw);
  }

  ~pqInternal() = default;

protected:
  friend class pqAEVASurfaceFeatureToolBar;

  pqFeatureReaction<smtk::session::aeva::GrowSelection>* m_growSelectionReaction;
  pqFeatureReaction<smtk::session::aeva::AllPrimitivesFeature>* m_allPrimitivesFeatureReaction;
  pqFeatureReaction<smtk::session::aeva::AdjacencyFeature>* m_adjacencyFeatureReaction;
  pqFeatureReaction<smtk::session::aeva::BooleanIntersect>* m_booleanIntersectReaction;
  pqFeatureReaction<smtk::session::aeva::BooleanSubtract>* m_booleanSubtractReaction;
  pqFeatureReaction<smtk::session::aeva::BooleanUnite>* m_booleanUniteReaction;
  pqFeatureReaction<smtk::session::aeva::Duplicate>* m_duplicateReaction;
  pqFeatureReaction<smtk::session::aeva::EditFreeformAttributes>* m_editAttributesReaction;
  pqFeatureReaction<smtk::session::aeva::LinearToQuadratic>* m_quadraticPromotionReaction;
  pqFeatureReaction<smtk::session::aeva::NormalFeature>* m_normalFeatureReaction;
  pqFeatureReaction<smtk::session::aeva::PointsOfPrimitivesFeature>* m_pointsOfPrimitivesFeature;
  pqFeatureReaction<smtk::session::aeva::ProportionalEdit>* m_proportionalEditReaction;
  pqFeatureReaction<smtk::session::aeva::ImprintGeometry>* m_imprintGeometryReaction;
  pqFeatureReaction<smtk::session::aeva::ImprintImage>* m_imprintImageReaction;
  pqFeatureReaction<smtk::session::aeva::ProximityFeature>* m_proximityFeatureReaction;
  pqFeatureReaction<smtk::session::aeva::ReconstructSurface>* m_reconstructSurfaceReaction;
  pqFeatureReaction<smtk::session::aeva::SmoothSurface>* m_smoothSurfaceReaction;
  pqFeatureReaction<smtk::session::aeva::UnreferencedPrimitives>* m_unreferencedPrimitivesReaction;
  pqFeatureReaction<smtk::session::aeva::VolumeInspect>* m_volumeInspectorReaction;
  pqFeatureReaction<smtk::session::aeva::VolumeMesher>* m_volumeMeshReaction;
  QLabel* m_cellSelectionSizeLabel;
  smtk::view::SelectionObservers::Key m_selnObserver;
  smtk::operation::Observers::Key m_opObserver;
};

pqAEVASurfaceFeatureToolBar::pqAEVASurfaceFeatureToolBar(QWidget* parent)
  : Superclass(parent)
{
  m_p = new pqInternal(this);
  this->setObjectName("SurfaceFeatures");

  auto* behavior = pqSMTKBehavior::instance();
  QObject::connect(behavior,
    SIGNAL(addedManagerOnServer(pqSMTKWrapper*, pqServer*)),
    this,
    SLOT(observeWrapper(pqSMTKWrapper*, pqServer*)));
  QObject::connect(behavior,
    SIGNAL(removingManagerFromServer(pqSMTKWrapper*, pqServer*)),
    this,
    SLOT(unobserveWrapper(pqSMTKWrapper*, pqServer*)));
  // Initialize with current wrapper(s), if any:
  behavior->visitResourceManagersOnServers([this](pqSMTKWrapper* wrapper, pqServer* server) {
    this->observeWrapper(wrapper, server);
    return false; // terminate early
  });

  if (!s_surfaceFeatureToolBar)
  {
    s_surfaceFeatureToolBar = this;
  }
}

pqAEVASurfaceFeatureToolBar::~pqAEVASurfaceFeatureToolBar()
{
  delete m_p;
  if (s_surfaceFeatureToolBar == this)
  {
    s_surfaceFeatureToolBar = nullptr;
  }
}

pqAEVASurfaceFeatureToolBar* pqAEVASurfaceFeatureToolBar::instance()
{
  return s_surfaceFeatureToolBar;
}

void pqAEVASurfaceFeatureToolBar::activateDeselect()
{
  auto* behavior = pqSMTKBehavior::instance();
  // For all resource managers on all servers, empty the selection:
  behavior->visitResourceManagersOnServers([](pqSMTKWrapper* wrapper, pqServer* /*server*/) {
    if (wrapper)
    {
      std::set<smtk::resource::PersistentObject::Ptr> empty;
      wrapper->smtkSelection()->modifySelection(
        empty, "deselect", 0, smtk::view::SelectionAction::UNFILTERED_REPLACE, false, false);
    }
    return true; // continue
  });
}

void pqAEVASurfaceFeatureToolBar::activateDuplicate()
{
  // press the duplicate toolbar button.
  m_p->m_duplicateReaction->onTriggered();
}

void pqAEVASurfaceFeatureToolBar::observeWrapper(pqSMTKWrapper* wrapper, pqServer* /*server*/)
{
  m_p->m_selnObserver = wrapper->smtkSelection()->observers().insert(
    [this](const std::string& source, smtk::view::Selection::Ptr const& seln) {
      this->onSelectionChanged(source, seln);
    },
    std::numeric_limits<smtk::view::Selection::Observers::Priority>::lowest(),
    /* invoke observer on current selection */ true,
    "pqAEVASurfaceFeatureToolBar: Show primitive count on selection.");
  m_p->m_opObserver = wrapper->smtkOperationManager()->observers().insert(
    [this](const smtk::operation::Operation& /*op*/,
      smtk::operation::EventType event,
      smtk::operation::Operation::Result const &
      /*result*/) -> int {
      if (event == smtk::operation::EventType::DID_OPERATE)
      {
        // TODO: If this is too much, we could inspect Result to see if CellSelection was created/modified.
        this->onPrimitivesSelected(smtk::session::aeva::CellSelection::instance().get());
      }
      return 0;
    },
    std::numeric_limits<smtk::operation::Observers::Priority>::lowest(),
    /* invoke observer on current selection */ false,
    "pqAEVASurfaceFeatureToolBar: Show primitive count on selection modification.");
}

void pqAEVASurfaceFeatureToolBar::unobserveWrapper(pqSMTKWrapper* /*wrapper*/, pqServer* /*server*/)
{
  m_p->m_selnObserver = smtk::view::SelectionObservers::Key();
  m_p->m_opObserver = smtk::operation::Observers::Key();
  this->onPrimitivesSelected(nullptr);
}

void pqAEVASurfaceFeatureToolBar::onSelectionChanged(const std::string& /*selnSource*/,
  const std::shared_ptr<smtk::view::Selection>& seln)
{
  if (seln)
  {
    for (const auto& entry : seln->currentSelection())
    {
      if (entry.second)
      {
        auto* prims = dynamic_cast<smtk::session::aeva::CellSelection*>(entry.first.get());
        if (prims)
        {
          this->onPrimitivesSelected(prims);
          return;
        }
      }
    }
  }
  this->onPrimitivesSelected(nullptr);
}

void pqAEVASurfaceFeatureToolBar::onPrimitivesSelected(smtk::session::aeva::CellSelection* cellSeln)
{
  std::ostringstream label;
  if (cellSeln)
  {
    auto* rsrc = dynamic_cast<smtk::session::aeva::Resource*>(cellSeln->resource().get());
    if (rsrc)
    {
      vtkSmartPointer<vtkDataObject> obj = rsrc->session()->findStorage(cellSeln->id());
      auto* dataset = vtkDataSet::SafeDownCast(obj);
      if (dataset)
      {
        label << dataset->GetNumberOfCells() << " primitives selected";
      }
      else if (obj)
      {
        label << obj->GetClassName() << " selected";
      }
    }
  }
  if (label.str().empty())
  {
    label << "0 primitives selected";
  }
  m_p->m_cellSelectionSizeLabel->setText(label.str().c_str());
}
