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

#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKResource.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKResourceRepresentation.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h"
#include "smtk/extension/paraview/server/vtkSMSMTKResourceRepresentationProxy.h"
#include "smtk/extension/paraview/server/vtkSMSMTKWrapperProxy.h"
#include "smtk/extension/paraview/server/vtkSMTKResource.h"
#include "smtk/extension/paraview/server/vtkSMTKResourceRepresentation.h"
#include "smtk/extension/paraview/server/vtkSMTKResourceSource.h"
#include "smtk/extension/qt/SVGIconEngine.h"
#include "smtk/extension/qt/qtOperationView.h"
#include "smtk/extension/qt/qtUIManager.h"
#include "smtk/extension/vtk/source/vtkResourceMultiBlockSource.h"
#include "smtk/model/operators/AssignColors.h"
#include "smtk/operation/Manager.h"
#include "smtk/session/aeva/CellSelection.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/operators/EditFreeformAttributes.h"

#include "pqApplicationCore.h"
#include "pqDataRepresentation.h"
#include "pqPipelineSource.h"
#include "pqSMAdaptor.h"
#include "pqServer.h"
#include "pqServerManagerModel.h"
#include "pqSetName.h"
#include "pqTabbedMultiViewWidget.h"
#include "pqUndoStack.h"
#include "pqView.h"
#include "vtkPVCompositeRepresentation.h"
#include "vtkPVDataInformation.h"
#include "vtkPVDataRepresentation.h"
#include "vtkSMProxy.h"

#include "vtkDataObjectTreeIterator.h"
#include "vtkMultiBlockDataSet.h"

#include <QAction>
#include <QIcon>
#include <QMenu>
#include <QString>

#include <iostream>

// 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
{
vtkDataObject* findBlock(int flatIndexTarget, int& flatIndexCurrent, vtkMultiBlockDataSet* mbds)
{
  if (flatIndexCurrent == flatIndexTarget)
  {
    return mbds;
  }

  for (unsigned int ii = 0; ii < mbds->GetNumberOfBlocks(); ++ii)
  {
    ++flatIndexCurrent;
    if (flatIndexCurrent == flatIndexTarget)
    {
      return mbds->GetBlock(ii);
    }
    if (flatIndexCurrent > flatIndexTarget)
    {
      return nullptr;
    }
    vtkDataObject* child = mbds->GetBlock(ii);
    if (child)
    {
      auto* compositeChild = vtkMultiBlockDataSet::SafeDownCast(child);
      if (compositeChild)
      {
        return findBlock(flatIndexTarget, flatIndexCurrent, compositeChild);
      }
    }
  }
  return nullptr;
}

void visitBlocks(vtkMultiBlockDataSet* mbds,
  const std::set<unsigned int>& blockIndices,
  const std::function<void(unsigned int, vtkDataObject*, vtkInformation*)>& visitor)
{
  for (const auto& blockIndex : blockIndices)
  {
    int targetIndex = static_cast<int>(blockIndex);
    int tmpIndex = 0;
    auto* data = findBlock(targetIndex, tmpIndex, mbds);
    if (data)
    {
      visitor(blockIndex, data, data->GetInformation());
    }
  }
}

} // anonymous namespace

AEVAContextMenu::AEVAContextMenu() = default;

AEVAContextMenu::AEVAContextMenu(QObject* parent)
  : QObject(parent)
{
}

bool AEVAContextMenu::contextMenu(QMenu* menu,
  pqView* viewContext,
  const QPoint& viewPoint,
  pqRepresentation* dataContext,
  const QList<unsigned int>& dataBlockContext) const
{
  (void)viewContext;
  (void)viewPoint;

  auto* rep = dynamic_cast<pqDataRepresentation*>(dataContext);
  this->PickedRep = rep;
  auto* inp = rep ? rep->getInput() : nullptr;

  auto* res = dynamic_cast<pqSMTKResource*>(inp);
  if (!res ||
    !(this->Resource =
        std::dynamic_pointer_cast<smtk::session::aeva::Resource>(res->getResource())))
  {
    // Only apply this menu to aeva resources.
    return false;
  }

  // Get the representation internals
  vtkSMProxy* compositeRepProxy = rep->getProxy();
  auto* c2rep = vtkSMSMTKResourceRepresentationProxy::SafeDownCast(compositeRepProxy);
  this->SMTKRep = nullptr;
  if (c2rep)
  {
    pqServerManagerModel* smmodel = pqApplicationCore::instance()->getServerManagerModel();
    pqServer* server = smmodel->findServer(c2rep->GetSession());
    QList<pqSMTKResourceRepresentation*> proxies =
      smmodel->findItems<pqSMTKResourceRepresentation*>(server);
    foreach (pqSMTKResourceRepresentation* pqproxy, proxies)
    {
      if (pqproxy->getProxy() == c2rep)
      {
        this->SMTKRep = pqproxy;
        break;
      }
    }
    // vtkSMProxy* realRepPxy = c2rep->GetResourceRepresentationSubProxy();
    // this->SMTKRep = dynamic_cast<pqSMTKResourceRepresentation*>(pxy);
  }
  auto* compositeRep =
    vtkPVCompositeRepresentation::SafeDownCast(compositeRepProxy->GetClientSideObject());
  vtkPVDataRepresentation* dataRep =
    compositeRep ? compositeRep->GetActiveRepresentation() : nullptr;
  auto* smtkRep = vtkSMTKResourceRepresentation::SafeDownCast(dataRep);
  this->ResourceRep = smtkRep;

  // Get the source of the representation's data
  pqPipelineSource* input = rep->getInput();
  auto* source = vtkSMTKResourceSource::SafeDownCast(input->getProxy()->GetClientSideObject());
  auto* sourceGen =
    vtkResourceMultiBlockSource::SafeDownCast(source->GetVTKResource()->GetConverter());

  // Only apply ourselves to SMTK representations
  if (!smtkRep)
  {
    return false;
  }

  // Save this for QAction-based callbacks to use.
  this->CompositeRep = compositeRep;
  this->ContextItems.clear();

  std::set<unsigned int> blockIndices(dataBlockContext.begin(), dataBlockContext.end());
  auto cs = smtk::session::aeva::CellSelection::instance();
  visitBlocks(sourceGen->GetOutput(),
    blockIndices,
    [this, &cs](unsigned int /*blockIndex*/, vtkDataObject* /*data*/, vtkInformation* metadata) {
      auto uuid = vtkResourceMultiBlockSource::GetDataObjectUUID(metadata);
      auto comp = this->Resource->find(uuid);
      if (!comp && cs && uuid == cs->id())
      {
        comp = cs;
      }
      if (comp)
      {
        this->ContextItems.push_back(comp);
      }
    });

  QAction* headerAction = nullptr;
  if (this->ContextItems.empty())
  {
    headerAction = menu->addAction(QString::fromStdString(this->Resource->name()));
  }
  else
  {
    if (this->ContextItems.size() == 1)
    {
      headerAction = menu->addAction(QString::fromStdString(this->ContextItems.front()->name()));
    }
    else
    {
      headerAction =
        menu->addAction(QString("%1 + %2 more")
                          .arg(QString::fromStdString(this->ContextItems.front()->name()))
                          .arg(this->ContextItems.size() - 1));
    }
  }
  if (headerAction)
  {
    headerAction->setEnabled(false);
  }

  QAction* hideAction = menu->addAction(QString("Hide"));
  hideAction->setObjectName("HideContext");
  QObject::connect(hideAction, &QAction::triggered, this, &AEVAContextMenu::hide);

  QAction* hideOthersAction = menu->addAction(QString("Hide others"));
  hideOthersAction->setObjectName("HideOthersContext");
  QObject::connect(hideOthersAction, &QAction::triggered, this, &AEVAContextMenu::hideOthers);

  menu->addSeparator();

  auto* editColorAction = this->addOperation<smtk::model::AssignColors>(menu, "Set color...");
  (void)editColorAction;

  // Disabled for now until we figure out why the SMTK selection is
  // modified just before the context menu is being built (most likely
  // due to the pick-event used to identify the picked objects).
  // QAction* editFreeformAttributesAction =
  //   this->addOperation<smtk::session::aeva::EditFreeformAttributes>(menu, "Freeform attributes...");

  menu->addSeparator();

  QMenu* reprMenu = menu->addMenu("Representation") << pqSetName("Representation");

  // populate the representation types menu.
  QList<QVariant> rTypes = pqSMAdaptor::getEnumerationPropertyDomain(
    dataContext->getProxy()->GetProperty("Representation"));
  QVariant curRType =
    pqSMAdaptor::getEnumerationProperty(dataContext->getProxy()->GetProperty("Representation"));
  foreach (QVariant rtype, rTypes)
  {
    QAction* raction = reprMenu->addAction(rtype.toString());
    raction->setCheckable(true);
    raction->setChecked(rtype == curRType);
  }

  menu->addSeparator();

  QObject::connect(reprMenu, &QMenu::triggered, this, &AEVAContextMenu::changeRepresentationType);

  menu->addSeparator();

  // Even when nothing was picked, show the "link camera" and
  // possibly the frame decoration menu items.
  menu->addAction("Link Camera...", viewContext, SLOT(linkToOtherView()));

  if (auto* tmvwidget = qobject_cast<pqTabbedMultiViewWidget*>(
        pqApplicationCore::instance()->manager("MULTIVIEW_WIDGET")))
  {
    auto* actn = menu->addAction("Show Frame Decorations");
    actn->setCheckable(true);
    actn->setChecked(tmvwidget->decorationsVisibility());
    QObject::connect(
      actn, &QAction::triggered, tmvwidget, &pqTabbedMultiViewWidget::setDecorationsVisibility);
  }

  /*
  auto wrapper = pqSMTKBehavior::instance()->wrapperProxy();
  wrapper->GetSelection()->modifySelection(
    this->ContextItems, "AEVAContextMenu", 1, smtk::view::SelectionAction::UNFILTERED_REPLACE, true, true);
    */

  return true;
}

void AEVAContextMenu::hide()
{
  if (this->ResourceRep && !this->ContextItems.empty())
  {
    // std::cout << "Hide " << this->ContextItems.size() << " components (or resource if empty)\n";
    for (const auto& component : this->ContextItems)
    {
      this->ResourceRep->SetEntityVisibility(component, false);
      this->SMTKRep->componentVisibilityChanged(component, false);
    }
    this->PickedRep->renderViewEventually();
  }
}

void AEVAContextMenu::hideOthers()
{
  if (this->Resource && this->ResourceRep && !this->ContextItems.empty())
  {
    bool didHide = false;
    smtk::resource::Component::Visitor hider = [this, &didHide](
                                                 const smtk::resource::ComponentPtr& comp) {
      if (std::find(this->ContextItems.begin(), this->ContextItems.end(), comp) ==
        this->ContextItems.end())
      {
        this->ResourceRep->SetEntityVisibility(comp, false);
        this->SMTKRep->componentVisibilityChanged(comp, false);
        didHide = true;
      }
    };
    this->Resource->visit(hider);
    if (didHide)
    {
      this->PickedRep->renderViewEventually();
    }
  }
}

void AEVAContextMenu::changeRepresentationType(QAction* action)
{
  pqDataRepresentation* repr = this->PickedRep;
  if (repr)
  {
    BEGIN_UNDO_SET("Representation Type Changed");
    pqSMAdaptor::setEnumerationProperty(
      repr->getProxy()->GetProperty("Representation"), action->text());
    repr->getProxy()->UpdateVTKObjects();
    repr->renderViewEventually();
    END_UNDO_SET();
  }
}

QAction* AEVAContextMenu::addOperation(const std::string& opType,
  QMenu* menu,
  const std::string& label) const
{
  using smtk::extension::SVGIconEngine;

  auto* wrapper = pqSMTKBehavior::instance()->wrapperProxy();
  auto viewManager = wrapper->GetManagers().get<smtk::view::Manager::Ptr>();
  std::string iconSVG = viewManager->operationIcons().createIcon(opType, "#ffffff");
  QIcon operationIcon(new SVGIconEngine(iconSVG));
  auto* opAction = new QAction(QString::fromStdString(label));
  opAction->setIcon(operationIcon);
  opAction->setObjectName(QString::fromStdString(opType));
  menu->addAction(opAction);
  QObject::connect(opAction, &QAction::triggered, [this, &label, opType]() {
    auto* wrapper = pqSMTKBehavior::instance()->wrapperProxy();
    auto operationManager = wrapper->GetManagers().get<smtk::operation::Manager::Ptr>();
    smtk::operation::Operation::Ptr op;
    if (!operationManager || !(op = operationManager->create(opType)))
    {
      smtkErrorMacro(smtk::io::Logger::instance(), "Could not create operation.");
      return;
    }
    this->operationDialog(op);
  });
  return opAction;
}

void AEVAContextMenu::operationDialog(
  const std::shared_ptr<smtk::operation::Operation>& operation) const
{
  if (!operation)
  {
    return;
  }
  auto assoc = operation->parameters()->associations();
  assoc->setNumberOfValues(this->ContextItems.size());
  assoc->setValues(this->ContextItems.begin(), this->ContextItems.end());
  if (operation->ableToOperate())
  {
    auto manager = operation->manager();
    if (manager)
    {
      manager->launchers()(operation);
    }
    else
    {
      operation->operate();
    }
  }

  // Can't run without more information... pop up the operation's parameters for editing.
  QSharedPointer<QDialog> createDialog = QSharedPointer<QDialog>(new QDialog());
  createDialog->setObjectName("ContextMenuOperationDialog");
  createDialog->setWindowTitle(
    QString::fromStdString(operation->parameters()->definition()->label()));
  createDialog->setLayout(new QVBoxLayout(createDialog.data()));

  auto* wrapper = pqSMTKBehavior::instance()->wrapperProxy();
  QSharedPointer<smtk::extension::qtUIManager> uiManager =
    QSharedPointer<smtk::extension::qtUIManager>(new smtk::extension::qtUIManager(
      operation, wrapper->GetResourceManager(), wrapper->GetViewManager()));
  uiManager->setOperationManager(wrapper->GetOperationManager());
  uiManager->setSelection(wrapper->GetSelection());

  smtk::view::ConfigurationPtr view = uiManager->findOrCreateOperationView();
  auto* opView = dynamic_cast<smtk::extension::qtOperationView*>(
    uiManager->setSMTKView(view, createDialog.data()));
  (void)opView;
  createDialog->setModal(false);
  createDialog->exec();
}
