//=========================================================================
//  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/EditFreeformAttributesView.h"
#include "smtk/session/aeva/operators/EditFreeformAttributes.h"
#include "smtk/session/aeva/plugin/ui_EditFreeformAttributesView.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/extension/qt/qtAttribute.h"
#include "smtk/extension/qt/qtUIManager.h"
#include "smtk/operation/Manager.h"
#include "smtk/view/Configuration.h"
#include "smtk/view/Selection.h"
#include "smtk/view/SelectionObserver.h"

#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqPresetDialog.h"
#include "pqRenderView.h"
#include "pqServer.h"
#include "pqSettings.h"

#include <QCheckBox>
#include <QColorDialog>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPainter>
#include <QPixmap>
#include <QPointer>
#include <QPushButton>
#include <QScrollArea>
#include <QSpacerItem>
#include <QTableWidget>
#include <QVBoxLayout>

// 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 namespace smtk::extension;

class EditFreeformAttributesView::Internals : public Ui::EditFreeformAttributesView
{
public:
  Internals(const std::shared_ptr<smtk::operation::Operation>& op)
    : m_setUp(false)
    , m_currentOp(std::dynamic_pointer_cast<smtk::session::aeva::EditFreeformAttributes>(op))
  {
    m_attributeValueTable = nullptr;
  }

  ~Internals() = default;

  void selectionModified(const std::string& /*source*/,
    const std::shared_ptr<smtk::view::Selection>& selection)
  {
    m_objects.clear();
    selection->currentSelectionByValue(m_objects, "selected", false);
    this->updateProperties();
  }

  void updateProperties()
  {
    struct Key
    {
      std::string name;
      std::string type;

      bool operator<(const Key& other) const
      {
        return this->name < other.name || (this->name == other.name && this->type < other.type);
      }
    };
    using Entry = struct
    {
      QVariant value;
      std::size_t count;
    };
    if (!m_setUp || !m_attributeValueTable)
    {
      return;
    }
    if (m_objects.size() == 1)
    {
      m_editorLabel->setText(QString("Editing freeform attributes for %1")
                               .arg(QString::fromStdString(m_objects.begin()->get()->name())));
    }
    else
    {
      m_editorLabel->setText(
        QString("Editing freeform attributes for %1 objects").arg(m_objects.size()));
    }
    m_attributeValueTable->clearContents();
    std::map<Key, Entry> props;
    std::size_t expected = 0;
    for (const auto& object : m_objects)
    {
      if (object)
      {
        ++expected;
        const auto& objectProps = object->properties();
        {
          const auto& intProps = objectProps.get<long>();
          for (const auto& propName : intProps.keys())
          {
            Key key{ propName, "integer" };
            auto it = props.find(key);
            if (it == props.end())
            {
              props[key] = Entry{ QVariant::fromValue(intProps.at(propName)), 1 };
            }
            else
            {
              ++props[key].count;
              QVariant val = QVariant::fromValue(intProps.at(propName));
              if (val != it->second.value)
              {
                props[key].value = QString("<multiple values>");
              }
            }
          }
        }
        {
          const auto& fpProps = objectProps.get<double>();
          for (const auto& propName : fpProps.keys())
          {
            Key key{ propName, "floating-point" };
            auto it = props.find(key);
            if (it == props.end())
            {
              props[key] = Entry{ QVariant::fromValue(fpProps.at(propName)), 1 };
            }
            else
            {
              ++props[key].count;
              QVariant val = QVariant::fromValue(fpProps.at(propName));
              if (val != it->second.value)
              {
                props[key].value = QString("<multiple values>");
              }
            }
          }
        }
        {
          const auto& strProps = objectProps.get<std::string>();
          for (const auto& propName : strProps.keys())
          {
            Key key{ propName, "string" };
            auto it = props.find(key);
            if (it == props.end())
            {
              props[key] = Entry{ QString::fromStdString(strProps.at(propName)), 1 };
            }
            else
            {
              ++props[key].count;
              QVariant val = QString::fromStdString(strProps.at(propName));
              if (val != it->second.value)
              {
                props[key].value = QString("<multiple values>");
              }
            }
          }
        }
      }
    }
    m_attributeValueTable->setRowCount(props.size());
    int row = 0;
    for (auto& prop : props)
    {
      m_attributeValueTable->setItem(
        row, 0, new QTableWidgetItem(QString::fromStdString(prop.first.name)));
      m_attributeValueTable->setItem(
        row, 1, new QTableWidgetItem(QString::fromStdString(prop.first.type)));
      if (prop.second.count != expected)
      {
        prop.second.value = QString("<partial>");
      }
      m_attributeValueTable->setItem(row, 2, new QTableWidgetItem(prop.second.value.toString()));
      ++row;
    }
  }

  void copySelectedRowToEditor()
  {
    const auto& indices = m_attributeValueTable->selectionModel()->selectedIndexes();
    if (indices.size() != 3) // NB: 3 == number of columns per row.
    {
      return;
    }
    QModelIndex index = indices.front();
    if (!index.isValid())
    {
      return;
    }
    // Only a single row was selected. Copy data into editor
    m_attributeNameEdit->setText(index.data().toString());
    QString typeString = index.siblingAtColumn(1).data().toString();
    if (typeString == "string")
    {
      m_attributeTypeCombo->setCurrentIndex(0);
    }
    else if (typeString == "floating-point")
    {
      m_attributeTypeCombo->setCurrentIndex(1);
    }
    else if (typeString == "integer")
    {
      m_attributeTypeCombo->setCurrentIndex(2);
    }
    m_attributeValuesEdit->setText(index.siblingAtColumn(1).data().toString());
    m_attributeValuesEdit->setText(index.siblingAtColumn(2).data().toString());
  }

  void editAttribute(const std::string& attributeName, bool erase = false)
  {
    // Check that inputs are valid
    QString attributeValues = m_attributeValuesEdit->text();
    QVariant val = attributeValues;
    if (attributeName.empty())
    {
      smtkErrorMacro(smtk::io::Logger::instance(), "You must specify an attribute name.");
      return;
    }
    auto op = m_currentOp;
    if (!op)
    {
      smtkErrorMacro(smtk::io::Logger::instance(), "Could not create an edit operation.");
      return;
    }
    auto assoc = op->parameters()->associations();
    assoc->setValues(m_objects.begin(), m_objects.end(), 0);
    op->parameters()->findString("name")->setValue(attributeName);
    auto stringItem = op->parameters()->findString("string value");
    auto doubleItem = op->parameters()->findDouble("float value");
    auto intItem = op->parameters()->findInt("integer value");
    if (erase)
    {
      stringItem->setNumberOfValues(0);
      doubleItem->setNumberOfValues(0);
      intItem->setNumberOfValues(0);
    }
    else
    {
      int attributeType = m_attributeTypeCombo->currentIndex(); // 0 = string, 1 = fp, 2 = int
      switch (attributeType)
      {
        default:
        case 0:
          stringItem->setNumberOfValues(1);
          stringItem->setValue(val.toString().toStdString());
          doubleItem->setNumberOfValues(0);
          intItem->setNumberOfValues(0);
          break;
        case 1:
          stringItem->setNumberOfValues(0);
          doubleItem->setNumberOfValues(1);
          doubleItem->setValue(val.toDouble());
          intItem->setNumberOfValues(0);
          break;
        case 2:
          stringItem->setNumberOfValues(0);
          doubleItem->setNumberOfValues(0);
          intItem->setNumberOfValues(1);
          intItem->setValue(val.toInt());
          break;
      }
    }
    auto result = op->operate();
    (void)result; // TODO: Verify
    this->updateProperties();
  }

  bool m_setUp;
  QPointer<QHBoxLayout> m_editorLayout;
  std::shared_ptr<smtk::session::aeva::EditFreeformAttributes> m_currentOp;
  smtk::view::SelectionObservers::Key m_selectionObserver;
  std::set<std::shared_ptr<smtk::resource::PersistentObject> > m_objects;
};

EditFreeformAttributesView::EditFreeformAttributesView(const smtk::view::Information& info)
  : qtBaseAttributeView(info)
  , m_p(new Internals(info.get<smtk::operation::Operation::Ptr>()))
{
  if (auto uiManager = info.get<qtUIManager*>())
  {
    const auto& managers = uiManager->managers();
    const auto& selection = managers.get<smtk::view::Selection::Ptr>();
    m_p->m_selectionObserver = selection->observers().insert(
      [this](const std::string& src, std::shared_ptr<smtk::view::Selection> const& sel) {
        m_p->selectionModified(src, sel);
      },
      std::numeric_limits<smtk::view::SelectionObservers::Priority>::lowest(),
      /* initialize immediately */ true,
      "update freeform attribute editor");
  }
}

EditFreeformAttributesView::~EditFreeformAttributesView()
{
  delete m_p;
}

bool EditFreeformAttributesView::displayItem(smtk::attribute::ItemPtr item) const
{
  if (item && item->name() == "colors")
  {
    return false;
  }
  return this->qtBaseAttributeView::displayItem(item);
}

bool EditFreeformAttributesView::validateInformation(const smtk::view::Information& info)
{
  return qtOperationView::validateInformation(info);
}

qtBaseView* EditFreeformAttributesView::createViewWidget(const smtk::view::Information& info)
{
  EditFreeformAttributesView* view;
  if (!EditFreeformAttributesView::validateInformation(info))
  {
    return nullptr;
  }
  view = new EditFreeformAttributesView(info);
  view->buildUI();
  return view;
}

void EditFreeformAttributesView::createWidget()
{
  smtk::view::ConfigurationPtr view = this->getObject();
  if (!view)
  {
    return;
  }

  auto* parentlayout = static_cast<QVBoxLayout*>(this->parentWidget()->layout());

  // Delete any pre-existing widget
  if (this->Widget)
  {
    if (parentlayout)
    {
      parentlayout->removeWidget(this->Widget);
    }
    delete this->Widget;
  }

  // Create a new frame and lay it out
  this->Widget = new QFrame(this->parentWidget());
  this->Widget->setObjectName("EditFreeformAttributesView");
  auto* layout = new QVBoxLayout(this->Widget);
  layout->setMargin(0);
  this->Widget->setLayout(layout);
  this->Widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);

  m_p->m_editorLayout = new QHBoxLayout;
  this->updateUI();

  auto* wtmp = new QWidget;
  m_p->setupUi(wtmp);
  m_p->m_setUp = true;
  layout->addWidget(wtmp);

  QObject::connect(m_p->m_attributeValueTable,
    SIGNAL(itemSelectionChanged()),
    this,
    SLOT(tableSelectionChanged()));
  QObject::connect(m_p->m_addButton, SIGNAL(released()), this, SLOT(addOrReplaceAttribute()));
  QObject::connect(m_p->m_removeButton, SIGNAL(released()), this, SLOT(removeSelectedAttribute()));

  // Show help when the info button is clicked.
  // QObject::connect(m_p->InfoBtn, SIGNAL(released()), this, SLOT(onInfo()));
}

void EditFreeformAttributesView::onShowCategory()
{
  this->updateUI();
}

void EditFreeformAttributesView::updateUI()
{
  smtk::view::ConfigurationPtr view = this->getObject();
  if (!view || !this->Widget)
  {
    return;
  }

  int i = view->details().findChild("AttributeTypes");
  if (i < 0)
  {
    return;
  }
  smtk::view::Configuration::Component& comp = view->details().child(i);
  std::string defName;
  for (std::size_t ci = 0; ci < comp.numberOfChildren(); ++ci)
  {
    smtk::view::Configuration::Component& attComp = comp.child(ci);
    //std::cout << "  component " << attComp.name() << "\n";
    if (attComp.name() != "Att")
    {
      continue;
    }
    std::string optype;
    if (attComp.attribute("Type", optype) && !optype.empty())
    {
      //std::cout << "    component type " << optype << "\n";
      if (optype == "edit freeform attributes")
      {
        //defName = optype;
        defName = "smtk::session::aeva::EditFreeformAttributes";
        break;
      }
    }
  }
  if (defName.empty())
  {
    return;
  }

  // expecting only 1 instance of the op?
  smtk::attribute::AttributePtr att = m_p->m_currentOp->parameters();
}

void EditFreeformAttributesView::requestOperation(const smtk::operation::OperationPtr& op)
{
  if (!op || !op->parameters())
  {
    return;
  }
  op->operate();
}

void EditFreeformAttributesView::tableSelectionChanged()
{
  m_p->copySelectedRowToEditor();
}

void EditFreeformAttributesView::addOrReplaceAttribute()
{
  std::string attributeName = m_p->m_attributeNameEdit->text().toStdString();
  m_p->editAttribute(attributeName, false);
}

void EditFreeformAttributesView::removeSelectedAttribute()
{
  const auto& indices = m_p->m_attributeValueTable->selectionModel()->selectedIndexes();
  for (const auto& index : indices)
  {
    if (index.column() == 0)
    {
      std::string attributeName = index.data().toString().toStdString();
      m_p->editAttribute(attributeName, true);
    }
  }
}

void EditFreeformAttributesView::valueChanged(smtk::attribute::ItemPtr valItem)
{
  (void)valItem;
  std::cout << "Item " << valItem->name() << " type " << valItem->type()
            << " changed; running op.\n";
  this->requestOperation(m_p->m_currentOp);
}

void EditFreeformAttributesView::requestModelEntityAssociation()
{
  this->updateUI();
}

void EditFreeformAttributesView::setInfoToBeDisplayed()
{
  m_infoDialog->displayInfo(this->getObject());
}
