/*==============================================================================

  Program: 3D Slicer

  Copyright (c) Laboratory for Percutaneous Surgery (PerkLab)
  Queen's University, Kingston, ON, Canada. All Rights Reserved.

  See COPYRIGHT.txt
  or http://www.slicer.org/copyright/copyright.txt for details.

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  This file was originally developed by Csaba Pinter, PerkLab, Queen's University
  and was supported through the Applied Cancer Research Unit program of Cancer Care
  Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care

==============================================================================*/

#ifndef __qSlicerSubjectHierarchyAbstractPlugin_h
#define __qSlicerSubjectHierarchyAbstractPlugin_h

// Qt includes
#include <QColor>
#include <QMap>
#include <QObject>
#include <QStringList>
#include <QIcon>
#include <QVariant>

// MRML includes
#include <vtkMRMLSubjectHierarchyNode.h>

#include "qSlicerSubjectHierarchyModuleWidgetsExport.h"

class QStandardItem;
class QAction;
class qSlicerAbstractModuleWidget;
class vtkMRMLAbstractViewNode;

struct Q_SLICER_MODULE_SUBJECTHIERARCHY_WIDGETS_EXPORT qSlicerTerminologyMetadata
{
  qSlicerTerminologyMetadata() { this->clear(); }

  void clear()
  {
    this->Terminology.clear();
    this->DefaultTerminology.clear();
    this->Name.clear();
    this->NameAutoGenerated = false;
    this->ColorAutoGenerated = true;
    this->GeneratedColor = QColor();

    this->HasTerminology = false;
    this->HasDefaultTerminology = false;
    this->HasName = false;
    this->HasNameAutoGenerated = false;
    this->HasColorAutoGenerated = false;
    this->HasGeneratedColor = false;
  }

  void setTerminology(const QString& terminology)
  {
    this->Terminology = terminology;
    this->HasTerminology = true;
  }

  void setDefaultTerminology(const QString& defaultTerminology)
  {
    this->DefaultTerminology = defaultTerminology;
    this->HasDefaultTerminology = true;
  }

  void setName(const QString& name)
  {
    this->Name = name;
    this->HasName = true;
  }

  void setNameAutoGenerated(bool nameAutoGenerated)
  {
    this->NameAutoGenerated = nameAutoGenerated;
    this->HasNameAutoGenerated = true;
  }

  void setColorAutoGenerated(bool colorAutoGenerated)
  {
    this->ColorAutoGenerated = colorAutoGenerated;
    this->HasColorAutoGenerated = true;
  }

  void setGeneratedColor(const QColor& generatedColor)
  {
    this->GeneratedColor = generatedColor;
    this->HasGeneratedColor = true;
  }

  bool hasTerminology() const { return this->HasTerminology; }
  bool hasDefaultTerminology() const { return this->HasDefaultTerminology; }
  bool hasName() const { return this->HasName; }
  bool hasNameAutoGenerated() const { return this->HasNameAutoGenerated; }
  bool hasColorAutoGenerated() const { return this->HasColorAutoGenerated; }
  bool hasGeneratedColor() const { return this->HasGeneratedColor; }

  const QString& terminology() const { return this->Terminology; }
  const QString& defaultTerminology() const { return this->DefaultTerminology; }
  const QString& name() const { return this->Name; }
  bool nameAutoGenerated() const { return this->NameAutoGenerated; }
  bool colorAutoGenerated() const { return this->ColorAutoGenerated; }
  const QColor& generatedColor() const { return this->GeneratedColor; }

private:
  QString Terminology;
  QString DefaultTerminology;
  QString Name;
  bool NameAutoGenerated{ false };
  bool ColorAutoGenerated{ true };
  QColor GeneratedColor;

  bool HasTerminology{ false };
  bool HasDefaultTerminology{ false };
  bool HasName{ false };
  bool HasNameAutoGenerated{ false };
  bool HasColorAutoGenerated{ false };
  bool HasGeneratedColor{ false };
};

///    In Widgets, not Plugins because the paths and library paths need to be exported to extensions
/// \brief Abstract plugin for handling Subject Hierarchy items
///
/// This class provides an interface and some default implementations for the common operations on
/// subject hierarchy items. To exercise the default implementations, a Default plugin \sa qSlicerSubjectHierarchyDefaultPlugin
/// has to be created.
///
/// Note about confidence values (\sa canAddNodeToSubjectHierarchy, \sa canReparentItemInsideSubjectHierarchy,
///   \sa canOwnSubjectHierarchyItem - in case of the latter two the node mentioned below is the data node associated to the item):
/// The confidence value is a floating point number between 0.0 and 1.0. Meaning of some typical values:
/// 0.0 = The plugin cannot handle the node in question at all
/// 0.3 = It is likely that other plugins will be able to handle the node in question better (typical value for plugins for generic types, such as Volumes)
/// 0.5 = The plugin has equal chance to handle this node as others (an example can be color table node)
/// 0.7 = The plugin is likely be the only one that can handle the node in question, but there is a chance that other plugins can do that too
/// 1.0 = The node in question can only be handled by the plugin (by node type or identifier attribute)
///
/// Plugin subclass naming convention:
/// - Plugins providing roles: qSubjectHierarchy[Role name, noun]Plugin
/// - Plugins providing functions: qSubjectHierarchy[Function name, verb]Plugin
///
class Q_SLICER_MODULE_SUBJECTHIERARCHY_WIDGETS_EXPORT qSlicerSubjectHierarchyAbstractPlugin : public QObject
{
  Q_OBJECT
  Q_ENUMS(ActionSectionType)

  /// This property stores the name of the plugin
  /// Cannot be empty.
  /// \sa name()
  Q_PROPERTY(QString name READ name WRITE setName)
  Q_PROPERTY(QString helpText READ helpText)

public:
  typedef QObject Superclass;
  qSlicerSubjectHierarchyAbstractPlugin(QObject* parent = nullptr);
  ~qSlicerSubjectHierarchyAbstractPlugin() override;

  // Role-related virtual methods
  // If the subclass plugin does not offer a role, these do not need to be overridden
public:
  /// Determines if the actual plugin can handle a subject hierarchy item. The plugin with
  /// the highest confidence number will "own" the item in the subject hierarchy (set icon, tooltip,
  /// set context menu etc.)
  /// \param item Item to handle in the subject hierarchy tree
  /// \return Floating point confidence number between 0 and 1, where 0 means that the plugin cannot handle the
  ///   item, and 1 means that the plugin is the only one that can handle the item (by node type or identifier attribute)
  Q_INVOKABLE virtual double canOwnSubjectHierarchyItem(vtkIdType itemID) const;

  /// Get role that the plugin assigns to the subject hierarchy item.
  ///   Each plugin should provide only one role.
  Q_INVOKABLE virtual const QString roleForPlugin() const;

  /// Get help text for plugin to be added in subject hierarchy module widget help box
  virtual const QString helpText() const;

  /// Get icon of an owned subject hierarchy item
  /// \return Icon to set, empty icon if nothing to set
  Q_INVOKABLE virtual QIcon icon(vtkIdType itemID);

  /// Get visibility icon for a visibility state
  Q_INVOKABLE virtual QIcon visibilityIcon(int visible);

  /// Get the visibility icon for an item that is nominally visible or hidden
  /// but whose effective visibility is overridden because a parent item is hidden.
  /// The default implementation calls applyParentHiddenEffect() on the result of
  /// visibilityIcon(), which renders the icon at reduced opacity and overlays a
  /// small upward-pointing arrow in the bottom-right corner.
  Q_INVOKABLE virtual QIcon visibilityIconWithParentHidden(int visible);

  /// Returns true if the module can edit properties of this item using editProperties.
  Q_INVOKABLE virtual bool canEditProperties(vtkIdType itemID);

  /// Open module belonging to item and set inputs in opened module
  Q_INVOKABLE virtual void editProperties(vtkIdType itemID);

  /// Generate displayed name for the owned subject hierarchy item corresponding to its role.
  /// The default implementation returns the associated data node's name if any, otherwise the item name
  Q_INVOKABLE virtual QString displayedItemName(vtkIdType itemID) const;

  /// Generate tooltip for a owned subject hierarchy item
  Q_INVOKABLE virtual QString tooltip(vtkIdType itemID) const;

  /// Set display visibility of an owned subject hierarchy item
  Q_INVOKABLE virtual void setDisplayVisibility(vtkIdType itemID, int visible);

  /// Get display visibility of an owned subject hierarchy item
  /// \return Display visibility (0: hidden, 1: shown, 2: partially shown)
  Q_INVOKABLE virtual int getDisplayVisibility(vtkIdType itemID) const;

  /// Set display color of an owned subject hierarchy item
  /// \param color Display color to set
  /// \param terminologyMetadata Strongly typed terminology metadata
  Q_INVOKABLE virtual void setDisplayColor( //
    vtkIdType itemID,
    QColor color,
    const qSlicerTerminologyMetadata& terminologyMetadata = qSlicerTerminologyMetadata());

  /// Compatibility overload for map-based callers.
  Q_INVOKABLE void setDisplayColor(vtkIdType itemID, QColor color, QMap<int, QVariant> terminologyMetadata);

  /// Get display color of an owned subject hierarchy item
  /// \param terminologyMetadata Output strongly typed terminology metadata
  Q_INVOKABLE virtual QColor getDisplayColor(vtkIdType itemID, qSlicerTerminologyMetadata& terminologyMetadata) const;

  /// Compatibility overload for map-based callers.
  Q_INVOKABLE QColor getDisplayColor(vtkIdType itemID, QMap<int, QVariant>& terminologyMetadata) const;

  /// Convenience overload when terminology metadata is not needed by the caller.
  Q_INVOKABLE QColor getDisplayColor(vtkIdType itemID) const;

  // Function related virtual methods
public:
  /// Get item context menu item actions to add to tree view.
  /// These item context menu actions can be shown in the implementations of \sa showContextMenuActionsForItem
  Q_INVOKABLE virtual QList<QAction*> itemContextMenuActions() const;

  /// Get scene context menu item actions to add to tree view. Also provides actions for right-click on empty area
  /// These context menu actions are shown for the scene in the implementations of \sa showContextMenuActionsForItem
  /// Separate method is needed for the scene, as its actions are set to the
  /// tree by a different method \sa itemContextMenuActions
  Q_INVOKABLE virtual QList<QAction*> sceneContextMenuActions() const;

  /// Show context menu actions valid for a given subject hierarchy item.
  /// \param itemID Subject Hierarchy item to show the context menu items for
  Q_INVOKABLE virtual void showContextMenuActionsForItem(vtkIdType itemID) { Q_UNUSED(itemID); };

  /// Get visibility context menu item actions to add to tree view.
  /// These item visibility context menu actions can be shown in the implementations of \sa showVisibilityContextMenuActionsForItem
  Q_INVOKABLE virtual QList<QAction*> visibilityContextMenuActions() const;

  /// Show visibility context menu actions valid for a given subject hierarchy item.
  /// \param itemID Subject Hierarchy item to show the visibility context menu items for
  Q_INVOKABLE virtual void showVisibilityContextMenuActionsForItem(vtkIdType itemID) { Q_UNUSED(itemID); };

  /// Get transform context menu item actions to add to tree view.
  /// These item transform context menu actions can be shown in the
  /// implementations of \sa showTransformContextMenuActionsForItem
  Q_INVOKABLE virtual QList<QAction*> transformContextMenuActions() const;

  /// Show transform context menu actions valid for a given subject hierarchy
  /// item. \param itemID Subject Hierarchy item to show the transform context menu items for
  Q_INVOKABLE virtual void showTransformContextMenuActionsForItem(vtkIdType itemID) { Q_UNUSED(itemID); };

  /// Get view context menu item actions that are available when right-clicking an object in the views.
  /// These item context menu actions can be shown in the implementations of \sa showViewContextMenuActionsForItem
  /// Note: The actions need object names set so that they can be included in the white list
  Q_INVOKABLE virtual QList<QAction*> viewContextMenuActions() const;

  /// Show context menu actions valid for a given subject hierarchy item to be shown in the view.
  /// \param itemID Subject Hierarchy item to show the context menu items for
  /// \param eventData Supplementary data for the item that may be considered for the menu (sub-item ID, attribute, etc.)
  Q_INVOKABLE virtual void showViewContextMenuActionsForItem(vtkIdType itemID, QVariantMap eventData)
  {
    Q_UNUSED(itemID);
    Q_UNUSED(eventData);
  };

  // Parenting related virtual methods with default implementation
public:
  /// Determines if a data node can be placed in the hierarchy using the actual plugin,
  /// and gets a confidence value for a certain MRML node (usually the type and possibly attributes are checked).
  /// Most plugins do not perform steps additional to the default, so the default implementation returns a 0
  /// confidence value, which can be overridden in plugins that do handle special cases.
  /// \param node Node to be added to the hierarchy
  /// \param parentItemID Prospective parent of the node to add.
  ///   Default value is invalid. In that case the parent will be ignored, the confidence numbers are got based on the to-be child node alone.
  /// \return Floating point confidence number between 0 and 1, where 0 means that the plugin cannot handle the
  ///   node, and 1 means that the plugin is the only one that can handle the node (by node type or identifier attribute)
  Q_INVOKABLE virtual double canAddNodeToSubjectHierarchy(vtkMRMLNode* node, vtkIdType parentItemID = vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID) const;

  /// Add a node to subject hierarchy under a specified parent. This is basically a convenience function to
  /// call \sa vtkMRMLSubjectHierarchyNode::CreateItem
  /// \param node Node to add to subject hierarchy
  /// \param parentItemID Parent item of the added node
  /// \return True if added successfully, false otherwise
  Q_INVOKABLE virtual bool addNodeToSubjectHierarchy(vtkMRMLNode* node, vtkIdType parentItemID);

  /// Determines if a subject hierarchy item can be reparented in the hierarchy using the current plugin,
  /// and gets a confidence value for the reparented item.
  /// Most plugins do not perform steps additional to the default, so the default implementation returns a 0
  /// confidence value, which can be overridden in plugins that do handle special cases.
  /// \param itemID Item to be reparented in the hierarchy
  /// \param parentItemID Prospective parent of the item to reparent.
  /// \return Floating point confidence number between 0 and 1, where 0 means that the plugin cannot handle the
  ///   item, and 1 means that the plugin is the only one that can handle the item
  Q_INVOKABLE virtual double canReparentItemInsideSubjectHierarchy(vtkIdType itemID, vtkIdType parentItemID) const;

  /// Reparent an item that was already in the subject hierarchy under a new parent.
  /// \return True if reparented successfully, false otherwise
  Q_INVOKABLE virtual bool reparentItemInsideSubjectHierarchy(vtkIdType itemID, vtkIdType parentItemID);

  /// Show an item in a selected view.
  /// List of all other item IDs that will be shown in this request is also provided, as it may help
  /// in determining the optimal view setup. For example, if multiple volume nodes will be shown
  /// then the first node may be displayed as background and the second as foreground.
  Q_INVOKABLE virtual bool showItemInView(vtkIdType itemID, vtkMRMLAbstractViewNode* viewNode, vtkIdList* allItemsToShow);

  // Utility functions
public:
  /// Determines if the item is owned by this plugin
  Q_INVOKABLE bool isThisPluginOwnerOfItem(vtkIdType itemID) const;

  /// Switch to module with given name
  /// \return Widget representation of the module if found, nullptr otherwise
  Q_INVOKABLE static qSlicerAbstractModuleWidget* switchToModule(QString moduleName);

  /// Apply the "parent hidden" visual effect to a visibility icon: render it at
  /// reduced opacity and overlay a small upward-pointing arrow in the bottom-right
  /// corner to indicate that the item's own visibility setting is irrelevant because
  /// a parent item is hidden.
  /// Plugins can call this helper when implementing visibilityIconWithParentHidden().
  static QIcon applyParentHiddenEffect(const QIcon& icon);

public:
  /// Get the name of the plugin
  virtual QString name() const;

  /// Set the name of the plugin
  /// NOTE: name must be defined in constructor in C++ plugins, this can only be used in python scripted ones
  virtual void setName(QString name);

  enum ActionSectionType
  {
    SectionTop = -400,         ///< Use this section to make items appear at the top of the menu.
    SectionInteraction = -300, ///< Actions for changing the current interaction mode (in view context menu).
    SectionComponent = -200,   ///< Actions for the selected node component (e.g., control point within a node).
    SectionNode = -100,        ///< Actions for the selected node.
    SectionDefault = 0,        ///< By default (if no position is defined) actions will appear in this section.
    SectionFolder = 100,       ///< Actions for the selected folder.
    SectionBottom = 200,       ///< Use this section to make items appear at the bottom of the menu.
  };

  /// Set the action position within a subject hierarchy menu by setting section and weight.
  /// \param section specifies section where the menu item will appear.
  ///   SectionDefault, SectionNode, SectionFolder, ... constants can be used.
  ///   A separator is displayed between each menu section.
  /// \param weight specifies the position of the action within the section.
  ///   Lighter actions float up (actions that have lower weight appear higher in the menu).
  ///   Valid range is -49 to 49.
  /// \param weightAdjustment specifies additional weight that allows inserting an action right above or below
  ///   another action. Valid range is -49.0 to 49.0 (non-integer values are allowed).
  Q_INVOKABLE static void setActionPosition(QAction* action, int section, int weight = 0, double weightAdjustment = 0.0);

  // @{
  /// Helper functions for getting/setting color in displayable data node.
  /// It is useful for implementing getDisplayColor and setDisplayColor methods.
  Q_INVOKABLE void setColorAndTerminologyToDisplayableNode(vtkIdType itemID,
                                                           QColor color,
                                                           const qSlicerTerminologyMetadata& terminologyMetadata,
                                                           bool useSelectedColor,
                                                           bool disableScalarVisibility);
  Q_INVOKABLE QColor colorAndTerminologyFromDisplayableNode(vtkIdType itemID, qSlicerTerminologyMetadata& terminologyMetadata, bool useSelectedColor) const;
  // @}

signals:
  /// Signal requesting expanding of the subject hierarchy tree item belonging to an item
  void requestExpandItem(vtkIdType itemID);

  /// Signal requesting invalidating the filter model for the tree view
  /// (e.g. when an item is added or removed by the plugin)
  void requestInvalidateFilter() const;

protected:
  /// Hide all context menu actions offered by the plugin.
  /// This method must be called as a first step in \sa showContextMenuActionsForItem
  /// before showing the actions that apply to the current situation. Calling this method
  /// prevents programming errors made in case plugin actions change.
  void hideAllContextMenuActions() const;

protected:
  /// Name of the plugin
  QString m_Name;

private:
  qSlicerSubjectHierarchyAbstractPlugin(const qSlicerSubjectHierarchyAbstractPlugin&); // Not implemented
  void operator=(const qSlicerSubjectHierarchyAbstractPlugin&);                        // Not implemented
  friend class qMRMLSubjectHierarchyTreeView;
  friend class qSlicerSubjectHierarchyPluginLogic;
};

#endif
