//=========================================================================
//  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/pqSMTKVolumeInspectionItemWidget.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKResource.h"
#include "smtk/extension/paraview/widgets/pqSMTKAttributeItemWidgetP.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"
#include "smtk/session/aeva/plugin/pqVolumeInspectionPropertyWidget.h"

#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/GroupItem.h"

#include "smtk/io/Logger.h"

#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqDataRepresentation.h"
#include "pqImplicitPlanePropertyWidget.h"
#include "pqObjectBuilder.h"
#include "pqPipelineSource.h"
#include "pqServer.h"
#include "pqSpherePropertyWidget.h"
#include "vtkDataArray.h"
#include "vtkDataObject.h"
#include "vtkDataSetAttributes.h"
#include "vtkMath.h"
#include "vtkPVXMLElement.h"
#include "vtkSMNewWidgetRepresentationProxy.h"
#include "vtkSMPropertyGroup.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxy.h"
#include "vtkSMProxyProperty.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMTransferFunctionManager.h"
#include "vtkVector.h"
#include "vtkVectorOperators.h"

#include <memory>

// 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

using qtItem = smtk::extension::qtItem;
using qtAttributeItemInfo = smtk::extension::qtAttributeItemInfo;

pqSMTKVolumeInspectionItemWidget::pqSMTKVolumeInspectionItemWidget(
  const smtk::extension::qtAttributeItemInfo& info,
  Qt::Orientation orient)
  : pqSMTKAttributeItemWidget(info, orient)
{
  this->createWidget();
}

pqSMTKVolumeInspectionItemWidget::~pqSMTKVolumeInspectionItemWidget() = default;

qtItem* pqSMTKVolumeInspectionItemWidget::createVolumeInspectionItemWidget(
  const qtAttributeItemInfo& info)
{
  return new pqSMTKVolumeInspectionItemWidget(info);
}

bool pqSMTKVolumeInspectionItemWidget::createProxyAndWidget(vtkSMProxy*& proxy,
  pqInteractivePropertyWidget*& widget)
{
  std::vector<smtk::attribute::DoubleItemPtr> items;
  bool haveItems = this->fetchVolumeInspectionItems(items);
  if (!haveItems)
  {
    smtkErrorMacro(smtk::io::Logger::instance(), "Could not find items for widget.");
    return false;
  }

  // I. Create the ParaView widget and a proxy for its representation.
  pqApplicationCore* paraViewApp = pqApplicationCore::instance();
  pqServer* server = paraViewApp->getActiveServer();
  pqObjectBuilder* builder = paraViewApp->getObjectBuilder();

  proxy =
    builder->createProxy("representations", "VolumeInspectionWidgetRepresentation", server, "");
  if (!proxy)
  {
    return false;
  }
  auto* inspectorWidget = new pqVolumeInspectionPropertyWidget(proxy, proxy->GetPropertyGroup(0));
  widget = inspectorWidget;

  // II. Initialize the properties.
  m_p->m_pvwidget = widget;
  this->updateWidgetFromItem();
  auto* widgetProxy = widget->widgetProxy();
  widgetProxy->UpdateVTKObjects();

  return widget != nullptr;
}

void pqSMTKVolumeInspectionItemWidget::updateItemFromWidgetInternal()
{
  vtkSMNewWidgetRepresentationProxy* widget = m_p->m_pvwidget->widgetProxy();
  std::vector<smtk::attribute::DoubleItemPtr> items;
  if (!this->fetchVolumeInspectionItems(items))
  {
    smtkErrorMacro(smtk::io::Logger::instance(),
      "Item widget has an update but the item(s) do not exist or are not sized properly.");
    return;
  }

  // Values held by widget
  vtkVector3d origin;
  vtkSMPropertyHelper originHelper(widget, "OriginPoint");
  originHelper.Get(origin.GetData(), 3);

  // Current values held in items
  vtkVector3d curOrigin;
  curOrigin = vtkVector3d(*items[0]->begin());

  double dist = sqrt(vtkMath::Distance2BetweenPoints(origin.GetData(), curOrigin.GetData()));
  bool didChange = false;
  if (dist > std::numeric_limits<double>::epsilon())
  {
    items[0]->setValues(origin.GetData(), origin.GetData() + 3);
    didChange = true;
  }

  if (didChange)
  {
    emit modified();
  }
}

void pqSMTKVolumeInspectionItemWidget::updateWidgetFromItemInternal()
{
  vtkSMNewWidgetRepresentationProxy* widget = m_p->m_pvwidget->widgetProxy();
  std::vector<smtk::attribute::DoubleItemPtr> items;
  if (!this->fetchVolumeInspectionItems(items))
  {
    smtkErrorMacro(smtk::io::Logger::instance(),
      "Item signaled an update but the item(s) do not exist or are not sized properly.");
    return;
  }

  // Unlike updateItemFromWidget, we don't care if we cause ParaView an unnecessary update;
  // we might cause an extra render but we won't accidentally mark a resource as modified.
  // Since there's no need to compare new values to old, this is simpler than updateItemFromWidget:
  vtkVector3d origin(&(*items[0]->begin()));
  vtkSMPropertyHelper(widget, "OriginPoint").Set(origin.GetData(), 3);

  // Also pass the image input to the widget rep
  auto assoc = this->item()->attribute()->associations();
  if (assoc)
  {
    smtk::model::EntityPtr input = assoc->valueAs<smtk::model::Entity>();
    if (input)
    {
      auto resource = input->resource();
      auto uuid = input->id();
      auto* behavior = pqSMTKBehavior::instance();
      pqSMTKResource* pvResource = behavior->getPVResource(resource);
      vtkSMSourceProxy* resourceProxy = pvResource ? pvResource->getSourceProxy() : nullptr;
      if (!pvResource || !resourceProxy)
      {
        smtkErrorMacro(smtk::io::Logger::instance(), "Could not fetch image proxy.");
        return;
      }
      vtkSMPropertyHelper(widget, "Image").Set(resourceProxy);
      vtkSMPropertyHelper(widget, "ImageID").Set(uuid.toString().c_str());
      auto imageResource = std::dynamic_pointer_cast<smtk::session::aeva::Resource>(resource);
      if (imageResource)
      {
        auto data = imageResource->session()->findStorage(uuid);
        auto* scalars = data ? data->GetAttributes(vtkDataObject::POINT)->GetScalars() : nullptr;
        if (scalars)
        {
          vtkSMSessionProxyManager* pxm = pqActiveObjects::instance().proxyManager();
          auto* lkupProxy =
            this->TransferFunctions->GetColorTransferFunction(scalars->GetName(), pxm);
          vtkSMPropertyHelper(widget, "LookupTable").Set(lkupProxy);
        }
      }
    }
  }
}

bool pqSMTKVolumeInspectionItemWidget::fetchVolumeInspectionItems(
  std::vector<smtk::attribute::DoubleItemPtr>& items)
{
  items.clear();

  // Check to see if item is a group containing items of double-vector items.
  auto groupItem = m_itemInfo.itemAs<smtk::attribute::GroupItem>();
  if (!groupItem || groupItem->numberOfGroups() < 1 || groupItem->numberOfItemsPerGroup() < 1)
  {
    smtkErrorMacro(
      smtk::io::Logger::instance(), "Expected a group item with 1 group of 1 or more items.");
    return false;
  }

  // Find items in the group based on names in the configuration info:
  std::string originItemName;
  if (!m_itemInfo.component().attribute("OriginPoint", originItemName))
  {
    originItemName = "OriginPoint";
  }

  auto originItem = groupItem->findAs<smtk::attribute::DoubleItem>(originItemName);

  if (originItem && originItem->numberOfValues() == 3)
  {
    items.push_back(originItem);
    return true;
  }

  return false;
}
