//=========================================================================
//  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/extension/qt/qtMeshSelectionItem.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/MeshSelectionItem.h"
#include "smtk/attribute/MeshSelectionItemDefinition.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/ModelEntityItemDefinition.h"
#include "smtk/attribute/System.h"
#include "smtk/common/UUID.h"
#include "smtk/extension/qt/qtBaseView.h"
#include "smtk/extension/qt/qtCheckItemComboBox.h"
#include "smtk/extension/qt/qtUIManager.h"

#include <QAbstractItemView>
#include <QButtonGroup>
#include <QCheckBox>
#include <QComboBox>
#include <QDoubleValidator>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPointer>
#include <QSizePolicy>
#include <QStandardItemModel>
#include <QTextEdit>
#include <QToolButton>
#include <QVBoxLayout>
#include <QVariant>

using namespace smtk::attribute;
using namespace smtk::extension;

class qtMeshSelectionItemInternals
{
public:
  QPointer<QGridLayout> EntryLayout;
  QPointer<QLabel> theLabel;
  Qt::Orientation VectorItemOrient;

  QPointer<QToolButton> GrowButton;
  QPointer<QToolButton> GrowPlusButton;
  QPointer<QToolButton> GrowMinusButton;
  QPointer<QToolButton> CancelButton;
  QPointer<QToolButton> AcceptButton;
  QPointer<QButtonGroup> ButtonGroup;

  QPointer<QToolButton> EdgeButton;

  void uncheckOpButtons()
  {
    if (!this->ButtonGroup)
      return;
    bool exlusive = this->ButtonGroup->exclusive();
    this->ButtonGroup->setExclusive(false);
    if (this->GrowButton)
      this->GrowButton->setChecked(false);
    if (this->GrowPlusButton)
      this->GrowPlusButton->setChecked(false);
    if (this->GrowMinusButton)
      this->GrowMinusButton->setChecked(false);
    if (this->EdgeButton)
      this->EdgeButton->setChecked(false);
    this->ButtonGroup->setExclusive(exlusive);
  }

  void modifyOutSelection(
    const smtk::common::UUID& entid, const std::set<int> vals, MeshModifyMode opType)
  {
    if (opType == RESET)
    {
      m_outSelection[entid] = vals;
    }
    else if (opType == MERGE)
    {
      m_outSelection[entid].insert(vals.begin(), vals.end());
    }
    else if (opType == SUBTRACT)
    {
      std::set<int> diffSet;
      std::set_difference(m_outSelection[entid].begin(), m_outSelection[entid].end(), vals.begin(),
        vals.end(), std::inserter(diffSet, diffSet.end()));
      m_outSelection[entid] = diffSet;
    }
  }

  std::map<smtk::common::UUID, std::set<int> > m_outSelection;
};

qtMeshSelectionItem::qtMeshSelectionItem(smtk::attribute::ItemPtr dataObj, QWidget* p,
  qtBaseView* bview, Qt::Orientation enVectorItemOrient)
  : qtItem(dataObj, p, bview)
{
  this->Internals = new qtMeshSelectionItemInternals;
  this->IsLeafItem = true;
  this->Internals->VectorItemOrient = enVectorItemOrient;
  this->createWidget();
  if (bview)
  {
    bview->uiManager()->onMeshSelectionItemCreated(this);
  }
}

qtMeshSelectionItem::~qtMeshSelectionItem()
{
  delete this->Internals;
}

void qtMeshSelectionItem::setLabelVisible(bool visible)
{
  this->Internals->theLabel->setVisible(visible);
}

void qtMeshSelectionItem::createWidget()
{
  smtk::attribute::ItemPtr dataObj = this->getObject();
  if (!dataObj || !this->passAdvancedCheck() ||
    (this->baseView() &&
      !this->baseView()->uiManager()->passItemCategoryCheck(dataObj->definition())))
  {
    return;
  }

  this->updateItemData();
}

void qtMeshSelectionItem::updateItemData()
{
  this->updateUI();
  this->qtItem::updateItemData();
}

QToolButton* internal_createToolButton(const QString& strIconName, const QString& strToolTip,
  QWidget* pw, QBoxLayout* buttonLayout, QButtonGroup* bgroup, bool checkable,
  qtMeshSelectionItem* meshItem)
{
  QSizePolicy sizeFixedPolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  QToolButton* retButton = new QToolButton(pw);
  retButton->setToolTip(strToolTip);
  retButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
  retButton->setFixedSize(QSize(32, 32));
  retButton->setIconSize(QSize(32, 32));
  retButton->setIcon(QIcon(strIconName));
  retButton->setSizePolicy(sizeFixedPolicy);
  retButton->setCheckable(checkable);

  QObject::connect(retButton, SIGNAL(clicked()), meshItem, SLOT(onRequestMeshSelection()));
  buttonLayout->addWidget(retButton);
  bgroup->addButton(retButton);

  return retButton;
}

void qtMeshSelectionItem::addMeshOpButtons()
{
  /*
  this->Internals->EntityAssociationItem =
    qtAttribute::createModelEntityItem(
        item, this->Widget, this->baseView(),
        this->Internals->VectorItemOrient);
  this->Internals->EntityAssociationItem->setLabelVisible(false);
*/

  QButtonGroup* bgroup = new QButtonGroup(this->Widget);
  QBoxLayout* buttonLayout = new QHBoxLayout;
  buttonLayout->setMargin(0);
  buttonLayout->setSpacing(3);
  const MeshSelectionItemDefinition* itemDef =
    dynamic_cast<const MeshSelectionItemDefinition*>(this->getObject()->definition().get());
  smtk::model::BitFlags masked = itemDef->membershipMask();
  if (masked == smtk::model::FACE)
  {
    bgroup->setExclusive(true);
    // grow button
    this->Internals->GrowButton = internal_createToolButton(":/icons/attribute/growcell32.png",
      "Grow Selection on Associated Entities", this->Widget, buttonLayout, bgroup, true, this);

    // grow plus button
    this->Internals->GrowPlusButton = internal_createToolButton(":/icons/attribute/growplus32.png",
      "Grow and Append Selection on Selected Entities", this->Widget, buttonLayout, bgroup, true,
      this);

    // grow minus button
    this->Internals->GrowMinusButton = internal_createToolButton(
      ":/icons/attribute/growminus32.png", "Grow and Remove Selection on Selected Entities",
      this->Widget, buttonLayout, bgroup, true, this);

    // cancel button
    this->Internals->CancelButton = internal_createToolButton(":/icons/attribute/cancel32.png",
      "Candel Grow Selection Mode", this->Widget, buttonLayout, bgroup, false, this);

    this->Internals->AcceptButton = internal_createToolButton(":/icons/attribute/growaccept24.png",
      "Create Face Group With Current Grow Selection", this->Widget, buttonLayout, bgroup, false,
      this);
  }
  else if (masked == (smtk::model::EDGE | smtk::model::VERTEX) || masked == smtk::model::EDGE ||
    masked == smtk::model::VERTEX)
  {
    bgroup->setExclusive(false);
    this->Internals->EdgeButton = internal_createToolButton(":/icons/attribute/edgesplit.png",
      "Split or merge edge", this->Widget, buttonLayout, bgroup, true, this);
  }

  this->Internals->EntryLayout->addLayout(buttonLayout, 0, 1);
  this->Internals->ButtonGroup = bgroup;
}

void qtMeshSelectionItem::updateUI()
{
  //smtk::attribute::ItemPtr dataObj = this->getObject();
  smtk::attribute::MeshSelectionItemPtr dataObj =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!dataObj || !this->passAdvancedCheck() ||
    (this->baseView() &&
      !this->baseView()->uiManager()->passItemCategoryCheck(dataObj->definition())))
  {
    return;
  }

  this->Widget = new QFrame(this->parentWidget());
  this->Internals->EntryLayout = new QGridLayout(this->Widget);
  this->Internals->EntryLayout->setMargin(0);
  this->Internals->EntryLayout->setSpacing(0);
  this->Internals->EntryLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);

  QSizePolicy sizeFixedPolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

  QHBoxLayout* labelLayout = new QHBoxLayout();
  labelLayout->setMargin(0);
  labelLayout->setSpacing(0);
  labelLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
  int padding = 0;
  if (dataObj->isOptional())
  {
    QCheckBox* optionalCheck = new QCheckBox(this->parentWidget());
    optionalCheck->setChecked(dataObj->isEnabled());
    optionalCheck->setText(" ");
    optionalCheck->setSizePolicy(sizeFixedPolicy);
    padding = optionalCheck->iconSize().width() + 3; // 6 is for layout spacing
    QObject::connect(optionalCheck, SIGNAL(stateChanged(int)), this, SLOT(setOutputOptional(int)));
    labelLayout->addWidget(optionalCheck);
  }
  smtk::attribute::MeshSelectionItemPtr item = dynamic_pointer_cast<MeshSelectionItem>(dataObj);
  const MeshSelectionItemDefinition* itemDef =
    dynamic_cast<const MeshSelectionItemDefinition*>(dataObj->definition().get());

  QString labelText;
  if (!item->label().empty())
  {
    labelText = item->label().c_str();
  }
  else
  {
    labelText = item->name().c_str();
  }
  QLabel* label = new QLabel(labelText, this->Widget);
  label->setSizePolicy(sizeFixedPolicy);
  if (this->baseView())
  {
    label->setFixedWidth(this->baseView()->fixedLabelWidth() - padding);
  }
  label->setWordWrap(true);
  label->setAlignment(Qt::AlignLeft | Qt::AlignTop);

  //  qtOverlayFilter *filter = new qtOverlayFilter(this);
  //  label->installEventFilter(filter);

  // add in BriefDescription as tooltip if available
  const std::string strBriefDescription = itemDef->briefDescription();
  if (!strBriefDescription.empty())
  {
    label->setToolTip(strBriefDescription.c_str());
  }

  if (itemDef->advanceLevel() && this->baseView())
  {
    label->setFont(this->baseView()->uiManager()->advancedFont());
  }
  labelLayout->addWidget(label);
  this->Internals->theLabel = label;

  this->addMeshOpButtons();

  this->Internals->EntryLayout->addLayout(labelLayout, 0, 0);
  //  layout->addWidget(this->Internals->EntryFrame, 0, 1);
  if (this->parentWidget() && this->parentWidget()->layout())
  {
    this->parentWidget()->layout()->addWidget(this->Widget);
  }
  if (dataObj->isOptional())
  {
    this->setOutputOptional(dataObj->isEnabled() ? 1 : 0);
  }
}

void qtMeshSelectionItem::setOutputOptional(int state)
{
  smtk::attribute::MeshSelectionItemPtr item =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!item)
  {
    return;
  }
  bool enable = state ? true : false;
  //  this->Internals->EntryFrame->setEnabled(enable);
  if (enable != this->getObject()->isEnabled())
  {
    this->getObject()->setIsEnabled(enable);
    if (this->baseView())
      this->baseView()->valueChanged(this->getObject());
  }
}

void qtMeshSelectionItem::clearSelection()
{
  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!meshSelectionItem)
  {
    return;
  }
  meshSelectionItem->reset();
}

void qtMeshSelectionItem::resetSelectionState(bool emitSignal)
{
  this->Internals->uncheckOpButtons();
  this->Internals->m_outSelection.clear();
  this->clearSelection();

  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (meshSelectionItem)
  {
    meshSelectionItem->setModifyMode(NONE);
  }

  if (emitSignal)
  {
    smtk::attribute::ModelEntityItem::Ptr modelEntities = this->refModelEntityItem();
    emit this->requestMeshSelection(modelEntities);
  }
}

void qtMeshSelectionItem::updateInputSelection(
  const std::map<smtk::common::UUID, std::set<int> >& selectionValues)
{
  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!meshSelectionItem)
  {
    return;
  }

  // clear cached mesh selection list
  this->clearSelection();

  int totalVals = 0;
  smtk::attribute::MeshSelectionItem::const_sel_map_it mapIt;
  for (mapIt = selectionValues.begin(); mapIt != selectionValues.end(); ++mapIt)
    totalVals += static_cast<int>(mapIt->second.size());

  MeshModifyMode opType = meshSelectionItem->modifyMode();
  for (mapIt = selectionValues.begin(); mapIt != selectionValues.end(); ++mapIt)
  {
    switch (opType)
    {
      case ACCEPT:
        meshSelectionItem->setValues(mapIt->first, mapIt->second);
        break;
      case RESET:
      case MERGE:
      case SUBTRACT:
        if (meshSelectionItem->isCtrlKeyDown() || totalVals > 1)
        {
          this->Internals->modifyOutSelection(mapIt->first, mapIt->second, opType);
          meshSelectionItem->setValues(mapIt->first, this->Internals->m_outSelection[mapIt->first]);
        }
        else if (totalVals == 1)
          meshSelectionItem->setValues(mapIt->first, mapIt->second);
        break;
      case NONE:
        break;
      default:
        std::cerr << "ERROR: Unrecognized MeshUpdateMode: " << opType << std::endl;
        break;
    }
  }

  if (opType == ACCEPT && this->Internals->EdgeButton && !this->Internals->EdgeButton->isChecked())
  {
    this->Internals->uncheckOpButtons();
    this->Internals->m_outSelection.clear();
  }
  else if (opType == NONE)
  {
    this->Internals->uncheckOpButtons();
    this->Internals->m_outSelection.clear();
    meshSelectionItem->reset();
  }
}

smtk::attribute::ModelEntityItemPtr qtMeshSelectionItem::refModelEntityItem()
{
  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!meshSelectionItem)
  {
    return smtk::attribute::ModelEntityItemPtr();
  }
  const MeshSelectionItemDefinition* itemDef =
    dynamic_cast<const MeshSelectionItemDefinition*>(meshSelectionItem->definition().get());
  smtk::attribute::AttributePtr att = meshSelectionItem->attribute();
  return att->findModelEntity(itemDef->refModelEntityName());
}

void qtMeshSelectionItem::setUsingCtrlKey(bool val)
{
  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!meshSelectionItem)
  {
    return;
  }
  meshSelectionItem->setCtrlKeyDown(val);
}

bool qtMeshSelectionItem::usingCtrlKey()
{
  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!meshSelectionItem)
  {
    return false;
  }
  return meshSelectionItem->isCtrlKeyDown();
}

void qtMeshSelectionItem::onRequestMeshSelection()
{
  QToolButton* const cButton = qobject_cast<QToolButton*>(QObject::sender());
  if (!cButton)
  {
    return;
  }
  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!meshSelectionItem)
  {
    return;
  }

  this->setUsingCtrlKey(false);

  MeshModifyMode opType;
  if (cButton == this->Internals->AcceptButton)
    opType = ACCEPT;
  else if (cButton == this->Internals->GrowButton)
    opType = RESET;
  else if (cButton == this->Internals->GrowPlusButton)
    opType = MERGE;
  else if (cButton == this->Internals->GrowMinusButton)
    opType = SUBTRACT;
  else if (cButton == this->Internals->CancelButton)
    opType = NONE;
  else if (cButton == this->Internals->EdgeButton)
  {
    if (cButton->isChecked())
      opType = ACCEPT;
    else
      opType = NONE;
  }
  else
  {
    std::cerr << "ERROR: Unrecognized button click " << cButton->objectName().toStdString()
              << std::endl;
    return;
  }

  meshSelectionItem->setModifyMode(opType);
  if (cButton != this->Internals->EdgeButton && (opType == ACCEPT || opType == NONE))
    this->Internals->uncheckOpButtons();

  smtk::attribute::ModelEntityItem::Ptr modelEntities = this->refModelEntityItem();
  if (modelEntities)
    emit this->requestMeshSelection(modelEntities);
}

void qtMeshSelectionItem::syncWithCachedSelection(
  const smtk::attribute::MeshSelectionItemPtr& resultSelectionItem,
  std::map<smtk::common::UUID, std::set<int> >& outSelectionValues)
{
  smtk::attribute::MeshSelectionItemPtr meshSelectionItem =
    dynamic_pointer_cast<MeshSelectionItem>(this->getObject());
  if (!meshSelectionItem)
  {
    return;
  }

  smtk::attribute::MeshSelectionItem::const_sel_map_it mapIt;
  MeshModifyMode opType = meshSelectionItem->modifyMode();
  if (opType == RESET || opType == ACCEPT || opType == NONE)
    this->Internals->m_outSelection.clear();

  for (mapIt = resultSelectionItem->begin(); mapIt != resultSelectionItem->end(); ++mapIt)
  {
    switch (opType)
    {
      case ACCEPT:
      case NONE:
        break;
      case RESET:
        this->Internals->modifyOutSelection(mapIt->first, mapIt->second, opType);
        break;
      case MERGE:
      case SUBTRACT:
        if (meshSelectionItem->numberOfValues() == 1)
        // this result is from grow, so we need to update the cached selection with it
        {
          this->Internals->modifyOutSelection(mapIt->first, mapIt->second, opType);
        }
        // else, // the cached selection should already be updated
        break;
      default:
        std::cerr << "ERROR: Unrecognized MeshUpdateMode: " << opType << std::endl;
        break;
    }
  }
  // copy cached selection out
  if (opType == RESET || opType == MERGE || opType == SUBTRACT)
  {
    for (mapIt = this->Internals->m_outSelection.begin();
         mapIt != this->Internals->m_outSelection.end(); ++mapIt)
      outSelectionValues[mapIt->first] = this->Internals->m_outSelection[mapIt->first];
  }
}
