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

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center (DKFZ)
All rights reserved.

Use of this source code is governed by a 3-clause BSD license that can be
found in the LICENSE file.

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

// mitk gui qt common plugin
#include <QmitkSynchronizedWidgetConnector.h>

#include <mitkException.h>

bool NodeListsEqual(const QmitkSynchronizedWidgetConnector::NodeList& selection1, const QmitkSynchronizedWidgetConnector::NodeList& selection2)
{
  if (selection1.size() != selection2.size())
  {
    return false;
  }

  // lambda to compare node pointer inside both lists
  auto lambda = [](mitk::DataNode::Pointer lhs, mitk::DataNode::Pointer rhs)
  {
    return lhs == rhs;
  };

  return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin(), selection2.end(), lambda);
}

QmitkSynchronizedWidgetConnector::QmitkSynchronizedWidgetConnector()
  : m_SelectAll(true)
  , m_ConnectionCounter(0)
{

}

void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget)
{
  connect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged,
    this, &QmitkSynchronizedWidgetConnector::ChangeSelection);

  connect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged,
    nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection);

  connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged,
    this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode);

  connect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged,
    nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll);

  connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::NodeVisibilityChanged,
    this, &QmitkSynchronizedWidgetConnector::NodeVisibilityChanged);

  connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::NodeVisibilityChanged,
    this, &QmitkSynchronizedWidgetConnector::OnNodeVisibilityChanged);

  connect(this, &QmitkSynchronizedWidgetConnector::NodeVisibilityChanged,
    nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetNodeVisibility);

  connect(nodeSelectionWidget->GetStorageModel(), &QmitkRenderWindowDataNodeTableModel::NodesLayerMoved,
    this, &QmitkSynchronizedWidgetConnector::NodesLayerMoved);

  connect(this, &QmitkSynchronizedWidgetConnector::NodesLayerMoved,
    nodeSelectionWidget->GetStorageModel(), &QmitkRenderWindowDataNodeTableModel::moveNodesLayer);

  connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization,
    this, &QmitkSynchronizedWidgetConnector::DeregisterWidget);

  m_ConnectionCounter++;
}

void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget)
{
  disconnect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged,
    this, &QmitkSynchronizedWidgetConnector::ChangeSelection);

  disconnect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged,
    nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection);

  disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged,
    this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode);

  disconnect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged,
    nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll);

  disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::NodeVisibilityChanged,
    this, &QmitkSynchronizedWidgetConnector::NodeVisibilityChanged);

  disconnect(this, &QmitkSynchronizedWidgetConnector::NodeVisibilityChanged,
    nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetNodeVisibility);

  disconnect(nodeSelectionWidget->GetStorageModel(), &QmitkRenderWindowDataNodeTableModel::NodesLayerMoved,
    this, &QmitkSynchronizedWidgetConnector::NodesLayerMoved);

  disconnect(this, &QmitkSynchronizedWidgetConnector::NodesLayerMoved,
    nodeSelectionWidget->GetStorageModel(), &QmitkRenderWindowDataNodeTableModel::moveNodesLayer);

  disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization,
    this, &QmitkSynchronizedWidgetConnector::DeregisterWidget);

  this->DeregisterWidget();
}

void QmitkSynchronizedWidgetConnector::SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const
{
  // We need to explicitly differentiate when "Select All" is active, since the internal selection
  // might not contain all nodes. When no selection widget is synchronized, the m_InternalSelection
  // won't be updated.
  if (m_SelectAll)
  {
    nodeSelectionWidget->SelectAll();
  }
  else
  {
    nodeSelectionWidget->SetCurrentSelection(m_InternalSelection);
  }

  nodeSelectionWidget->SetSelectAll(m_SelectAll);

  // Need to explicitly set visibility true, since selected but invisible nodes in the sync group don't trigger selection changes
  for (auto& node : nodeSelectionWidget->GetSelectedNodes())
  {
    nodeSelectionWidget->SetNodeVisibility(node, true);
  }

  for (auto& node : this->m_InternalInvisibles)
  {
    nodeSelectionWidget->SetNodeVisibility(node, false);
  }
}

QmitkSynchronizedWidgetConnector::NodeList QmitkSynchronizedWidgetConnector::GetNodeSelection() const
{
  return m_InternalSelection;
}

void QmitkSynchronizedWidgetConnector::SeedFromMember(const NodeList& seedSelection, const mitk::BaseRenderer* seedRenderer)
{
  if (nullptr == seedRenderer)
  {
    mitkThrow() << "QmitkSynchronizedWidgetConnector::SeedFromMember: seedRenderer must not be null.";
  }

  m_InternalSelection = seedSelection;

  m_InternalInvisibles.clear();
  for (const auto& node : seedSelection)
  {
    if (node.IsNull())
    {
      continue;
    }
    if (!node->IsVisible(seedRenderer))
    {
      m_InternalInvisibles.insert(node.GetPointer());
    }
  }

  // Note: m_SelectAll is intentionally left untouched - it is group-scoped,
  // set from the layout document's `groups` dict in ApplyLayout, and not
  // derived from any single cell.
}

bool QmitkSynchronizedWidgetConnector::GetSelectionMode() const
{
  return m_SelectAll;
}

void QmitkSynchronizedWidgetConnector::ChangeSelection(NodeList nodes)
{
  if (!NodeListsEqual(m_InternalSelection, nodes))
  {
    m_InternalSelection = nodes;
    emit NodeSelectionChanged(m_InternalSelection);
  }
}

void QmitkSynchronizedWidgetConnector::ChangeSelectionMode(bool selectAll)
{
  if (m_SelectAll!= selectAll)
  {
    m_SelectAll = selectAll;
    emit SelectionModeChanged(m_SelectAll);
  }
}

void QmitkSynchronizedWidgetConnector::OnNodeVisibilityChanged(mitk::DataNode::Pointer node, bool visibility)
{
  if (!visibility)
    this->m_InternalInvisibles.insert(node.GetPointer());
  else
    this->m_InternalInvisibles.erase(node.GetPointer());
}

void QmitkSynchronizedWidgetConnector::DeregisterWidget()
{
  m_ConnectionCounter--;

  // When no more widgets are synchronized anymore, turn on SelectAll to avoid losing
  // nodes that are added until synchronization of a widget is turned on again.
  if (m_ConnectionCounter == 0)
  {
    m_SelectAll = true;
  }
}
