//=========================================================================
//  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/rgg/plugin/smtkRGGEditCoreView.h"
#include "smtk/session/rgg/plugin/ui_smtkRGGEditCoreParameters.h"

#include "smtkRGGViewHelper.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/GroupItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/VoidItem.h"

#include "smtk/io/Logger.h"

#include "smtk/model/AuxiliaryGeometry.h"
#include "smtk/model/Group.h"
#include "smtk/model/Resource.h"

#include "smtk/operation/Operation.h"

#include "smtk/session/rgg/Assembly.h"
#include "smtk/session/rgg/Core.h"
#include "smtk/session/rgg/operators/OperationHelper.h"

#include "smtk/session/rgg/json/jsonAssembly.h"
#include "smtk/session/rgg/json/jsonCore.h"

#include "smtk/session/rgg/operators/CreateModel.h"
#include "smtk/session/rgg/operators/EditCore.h"

#include "smtk/session/rgg/qt/qtDraw2DLattice.h"
#include "smtk/session/rgg/qt/rggNucCore.h"

#include "smtk/extension/qt/qtAttribute.h"
#include "smtk/extension/qt/qtBaseAttributeView.h"
#include "smtk/extension/qt/qtItem.h"
#include "smtk/extension/qt/qtUIManager.h"

#include "smtk/view/Configuration.h"

#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqPresetDialog.h"

#include <QComboBox>
#include <QMap>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>

#include <QDockWidget>

using namespace smtk::model;
using namespace smtk::extension;
using namespace smtk::session::rgg;
using json = nlohmann::json;

class smtkRGGEditCoreViewInternals : public Ui::RGGEditCoreParameters
{
public:
  smtkRGGEditCoreViewInternals()
  {
  }

  ~smtkRGGEditCoreViewInternals()
  {
    if (CurrentAtt)
    {
      delete CurrentAtt;
      CurrentAtt = nullptr;
    }
    auto mapIter = this->SMTKCoreToCMBCore.begin();
    while (mapIter != this->SMTKCoreToCMBCore.end())
    {
      if (mapIter.value())
      {
        delete mapIter.value();
      }
      mapIter++;
    }
    if (this->SchemaPlanner)
    {
      this->SchemaPlanner->setVisible(false);
    }
  }

  qtAttribute* createAttUI(smtk::attribute::AttributePtr att, QWidget* pw, qtBaseView* view)
  {
    if (att && att->numberOfItems() > 0)
    {
      smtk::view::Configuration::Component comp;
      qtAttribute* attInstance = new qtAttribute(att, comp, pw, view);
      if (attInstance && attInstance->widget())
      {
        //Without any additional info lets use a basic layout with model associations
        // if any exists
        attInstance->createBasicLayout(true);
        attInstance->widget()->setObjectName("RGGCoreEditor");
        QVBoxLayout* parentlayout = static_cast<QVBoxLayout*>(pw->layout());
        parentlayout->insertWidget(0, attInstance->widget());
      }
      return attInstance;
    }
    return nullptr;
  }

  QPointer<qtAttribute> CurrentAtt{nullptr};
  QPointer<qtDraw2DLattice> Current2DLattice{nullptr};
  QPointer<QDockWidget> SchemaPlanner;
  // Map smtk core(smtk::model::Model) to CMB core rggNucCore which is used for schema planning
  QMap<smtk::model::EntityRef, rggNucCore*> SMTKCoreToCMBCore;
  rggNucCore* CurrentCMBCore{nullptr};
  QPointer<QWidget> CurrentWidget;
  smtk::model::EntityRef CurrentSMTKCore;
};

smtkRGGEditCoreView::smtkRGGEditCoreView(const smtk::view::Information& info)
  : qtBaseAttributeView(info)
{
  this->Internals = new smtkRGGEditCoreViewInternals();
}

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

const smtk::operation::OperationPtr& smtkRGGEditCoreView::operation() const
{
  return m_viewInfo.get<smtk::operation::OperationPtr>();
}

qtBaseView* smtkRGGEditCoreView::createViewWidget(const smtk::view::Information& info)
{
  smtkRGGEditCoreView* view = new smtkRGGEditCoreView(info);
  view->buildUI();
  return view;
}

void smtkRGGEditCoreView::valueChanged(smtk::attribute::ItemPtr /*optype*/)
{
  this->requestOperation(this->operation());
}

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

void smtkRGGEditCoreView::attributeModified()
{
  // Always enable apply button here
}

void smtkRGGEditCoreView::onAttItemModified(smtk::extension::qtItem* item)
{
  smtk::attribute::ItemPtr itemPtr = item->item();
  // only changing core would update the edit core panel
  if ((itemPtr->name() == "Core") &&
    itemPtr->type() == smtk::attribute::Item::Type::ReferenceType)
  {
    this->updateEditCorePanel();
  }
}

void smtkRGGEditCoreView::apply()
{
  // Due to the limitation of PV 3DGlyphMapper that it cannot create instances
  // based on an instance, I choose to decompose an assembly to pins and a duct.
  // In other words, a rgg core also consists of pin instances and duct instances.

  // Extract some assembly information first
  smtk::attribute::AttributePtr ecAtt = this->operation()->parameters();

  smtk::model::EntityRefArray coreArray =
    ecAtt->associatedModelEntities<smtk::model::EntityRefArray>();
  smtk::model::Group coreGroup;
  smtk::model::Model model;
  if (coreArray.size() > 0)
  {
    coreGroup = coreArray[0].as<smtk::model::Group>();
    model = coreGroup.owningModel();
  }
  else
  {
    smtkErrorMacro(smtk::io::Logger::instance(), "An invalid core is provided to the op. Stop"
                                                 "the operation.");
    return;
  }

  ResourcePtr resource = model.resource();
  Core core = json::parse(coreGroup.stringProperty(Core::propDescription)[0]);
  bool isHex = (core.geomType() == Core::GeomType::Hex);

  core.setName(this->Internals->nameLineEdit->text().toStdString());

  core.setHeight(this->Internals->heightLineEdit->text().toDouble());

  core.setZOrigin(this->Internals->zOriginLineEdit->text().toDouble());

  if (isHex)
  {
    core.setDuctThickness(this->Internals->ductXThicknessSpinBox->value());
  }
  else
  {
    core.setDuctThickness(this->Internals->ductXThicknessSpinBox->value(),
            this->Internals->ductYThicknessSpinBox->value());
  }

  // Ignore geometry type item since it can only be decided at
  // the creation time
  smtk::attribute::IntItemPtr latticeSizeI = ecAtt->findInt("lattice size");
  std::pair<int, int> latticeSize;
  latticeSize.first = this->Internals->latticeXSpinBox->value();
  if (isHex)
  { // Geometry type is hex.
    latticeSize.second = this->Internals->latticeXSpinBox->value();
  }
  else
  { // Geometry type is rect
    latticeSize.second = this->Internals->latticeYSpinBox->value();
  }
  core.setLatticeSize(latticeSize.first, latticeSize.second);

  // Assembly and its corresponding layouts
  Core::UuidToSchema assyToLayout;
  // Call apply function on qtDraw2DLattice to update CurrentCMBCore
  if (this->Internals->Current2DLattice)
  {
    this->Internals->Current2DLattice->apply();
  }

  // FIXME: This map might be a performance bottleneck if we are considering
  // millions of assemblies
  std::shared_ptr<qtLattice> coreLattice = this->Internals->CurrentCMBCore->lattice();
  std::pair<size_t, size_t> dimension = coreLattice->GetDimensions();
  for (size_t i = 0; i < dimension.first; i++)
  {
    size_t jMax = isHex ? ((i == 0) ? 1 : dimension.second * i) : dimension.second;
    for (size_t j = 0; j < jMax; j++)
    {
      smtk::model::EntityRef assyEntity = coreLattice->GetCell(i, j).getPart();
      if (!assyEntity.hasStringProperty(Assembly::propDescription))
      {
        continue;
      }
      Assembly assy = json::parse(assyEntity.stringProperty(Assembly::propDescription)[0]);
      // Skip invalid and empty assy cell(defined in qtLattice class)
      if (!assyEntity.isValid() ||
        (assy.name() == "Empty Cell" && assy.label() == "XX"))
      {
        continue;
      }

      if (assyToLayout.find(assyEntity.entity()) != assyToLayout.end())
      {
        assyToLayout[assyEntity.entity()].push_back(std::make_pair(i,j));
      }
      else
      {
        std::vector<std::pair<int, int>> layout;
        layout.push_back(std::make_pair(i,j));
        assyToLayout.emplace(assyEntity.entity(), layout);
      }
    }
  }
  core.setLayout(assyToLayout);

  if (!OperationHelper::calculateEntityCoordsInCore(core, resource))
  {
    smtkErrorMacro(smtk::io::Logger::instance(),
                   "Fail to calculate entity coordinates for the core");
  }

  json coreJson = core;
  std::string coreStr = coreJson.dump();
  ecAtt->findString("core representation")->setValue(coreStr);

  this->requestOperation(this->operation());
}

void smtkRGGEditCoreView::launchSchemaPlanner()
{
  if (!this->Internals->SchemaPlanner)
  {
    QWidget* dockP = nullptr;
    Q_FOREACH (QWidget* widget, QApplication::topLevelWidgets())
    {
      if (widget->inherits("QMainWindow"))
      {
        dockP = widget;
        break;
      }
    }
    this->Internals->SchemaPlanner = new QDockWidget(dockP);
    this->Internals->SchemaPlanner->setObjectName("rggCoreSchemaPlanner");
    this->Internals->SchemaPlanner->setWindowTitle("Core Schema Planner");
    auto sp = this->Internals->SchemaPlanner;
    sp->setFloating(true);
    sp->setWidget(this->Internals->Current2DLattice);
    sp->raise();
    sp->show();
    this->Internals->Current2DLattice->rebuild();
  }
  else
  {
    this->Internals->SchemaPlanner->raise();
    this->Internals->SchemaPlanner->show();
  }
}

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

  if (this->Internals->CurrentAtt)
  {
    delete this->Internals->CurrentAtt;
  }

  int i = view->details().findChild("AttributeTypes");
  if (i < 0)
  {
    return;
  }
  smtk::view::Configuration::Component& comp = view->details().child(i);
  std::string eaName;
  for (std::size_t ci = 0; ci < comp.numberOfChildren(); ++ci)
  {
    smtk::view::Configuration::Component& attComp = comp.child(ci);
    if (attComp.name() != "Att")
    {
      continue;
    }
    std::string optype;
    if (attComp.attribute("Type", optype) && !optype.empty())
    {
      if (optype == "edit core")
      {
        eaName = optype;
        break;
      }
    }
  }
  if (eaName.empty())
  {
    return;
  }

  smtk::attribute::AttributePtr att = this->operation()->parameters();
  this->Internals->CurrentAtt = this->Internals->createAttUI(att, this->Widget, this);
  if (this->Internals->CurrentAtt)
  {
    QObject::connect(this->Internals->CurrentAtt, &qtAttribute::modified, this,
      &smtkRGGEditCoreView::attributeModified);
    QObject::connect(this->Internals->CurrentAtt, &qtAttribute::itemModified, this,
      &smtkRGGEditCoreView::onAttItemModified);

    this->updateEditCorePanel();
  }
}

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

  QVBoxLayout* parentLayout = dynamic_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());
  QVBoxLayout* layout = new QVBoxLayout(this->Widget);
  layout->setMargin(0);
  this->Widget->setLayout(layout);
  this->Widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);

  QWidget* tempWidget = new QWidget(this->parentWidget());
  this->Internals->CurrentWidget = tempWidget;
  this->Internals->setupUi(tempWidget);
  tempWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
  layout->addWidget(tempWidget, 1);

  QObject::connect(this->Internals->launchSchemaPlannerButton, &QPushButton::clicked, this,
    &smtkRGGEditCoreView::launchSchemaPlanner);

  // Show help when the info button is clicked. //
  QObject::connect(
    this->Internals->infoButton, &QPushButton::released, this, &smtkRGGEditCoreView::onInfo);

  QObject::connect(
    this->Internals->applyButton, &QPushButton::released, this, &smtkRGGEditCoreView::apply);

  this->updateUI();
}

void smtkRGGEditCoreView::updateEditCorePanel()
{
  smtk::attribute::AttributePtr att = this->Internals->CurrentAtt->attribute();
  smtk::model::EntityRefArray ents = att->associatedModelEntities<smtk::model::EntityRefArray>();

  auto populateThePanel = [this, &ents]() {
    // Populate the panel
    smtk::model::Model model = ents[0].owningModel();
    smtk::model::EntityRef coreGroup = ents[0];
    Core core = json::parse(coreGroup.stringProperty(Core::propDescription)[0]);

    this->Internals->nameLineEdit->setText(QString::fromStdString(core.name()));

    this->Internals->zOriginLineEdit->setText(QString::number(core.zOrigin()));
    this->Internals->heightLineEdit->setText(QString::number(core.height()));

    this->Internals->ductXThicknessSpinBox->setValue(core.ductThickness().first);
    this->Internals->ductYThicknessSpinBox->setValue(core.ductThickness().second);

    bool isHex = (core.geomType() == Core::GeomType::Hex);
    // Lattice
    this->Internals->latticeYLabel->setHidden(isHex);
    this->Internals->latticeYSpinBox->setHidden(isHex);
    this->Internals->latticeXLabel->setHidden(isHex);
    // Duct thickness
    this->Internals->ductXThicknessLabel->setHidden(isHex);
    this->Internals->ductYThicknessLabel->setHidden(isHex);
    this->Internals->ductYThicknessSpinBox->setHidden(isHex);

    // By default the rect model is 4x4 and hex is 1x1.
    this->Internals->latticeXSpinBox->setValue(core.latticeSize().first);
    this->Internals->latticeYSpinBox->setValue(core.latticeSize().second);
  };

  // Need a valid core
  bool isEnabled(true);
  if ((ents.size() == 0) || (!ents[0].hasStringProperty("rggType")) ||
    (ents[0].stringProperty("rggType")[0] != smtk::session::rgg::Core::typeDescription))
  { // Its type is not rgg core
    isEnabled = false;
    this->Internals->CurrentSMTKCore = smtk::model::EntityRef(); // Invalid the current smtk core
  }
  else
  {
    if (this->Internals->CurrentSMTKCore == ents[0])
    { // If it's the same, do not reset the schema planner
      // since it might surprise the user
      // We still populate the panel in case if the user has imported a core via some operations.
      populateThePanel();
      return;
    }
    this->Internals->CurrentSMTKCore = ents[0]; // Update the current smtk core
  }

  if (this->Internals)
  {
    this->Internals->CurrentWidget->setEnabled(isEnabled);
  }

  if (isEnabled)
  {
    if (!this->Internals->Current2DLattice)
    {
      this->Internals->Current2DLattice = new qtDraw2DLattice(ents[0], this->Internals->coreWidget);
      this->Internals->Current2DLattice->setFrameShape(QFrame::NoFrame);
      // Modify layers/ x and y value would update the schema planner
      QObject::connect(this->Internals->latticeXSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
        this->Internals->Current2DLattice, &qtDraw2DLattice::setLatticeXorLayers);
      QObject::connect(this->Internals->latticeYSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
        this->Internals->Current2DLattice, &qtDraw2DLattice::setLatticeY);
      // Reset schema
      QObject::connect(this->Internals->resetSchemaPlannerButton, &QPushButton::clicked,
         this->Internals->Current2DLattice, &qtDraw2DLattice::reset);
      this->Internals->Current2DLattice->setVisible(false);
    }

    // Create/update current rggNucCore
    if (this->Internals->SMTKCoreToCMBCore.contains(ents[0]))
    {
      this->Internals->CurrentCMBCore = this->Internals->SMTKCoreToCMBCore.value(ents[0]);
    }
    else
    {
      rggNucCore* assy = new rggNucCore(ents[0]);
      this->Internals->SMTKCoreToCMBCore[ents[0]] = assy;
      this->Internals->CurrentCMBCore = assy;
    }
    this->Internals->Current2DLattice->setLattice(this->Internals->CurrentCMBCore);
    populateThePanel();
  }
}

void smtkRGGEditCoreView::setInfoToBeDisplayed()
{
  this->m_infoDialog->displayInfo(this->configuration());
}
