//=========================================================================
//  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/attribute/Attribute.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"

#include "smtk/session/rgg/Core.h"
#include "smtk/session/rgg/Pin.h"
#include "smtk/session/rgg/Registrar.h"
#include "smtk/session/rgg/Resource.h"
#include "smtk/session/rgg/Session.h"

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

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

#include "smtk/model/EntityRef.h"
#include "smtk/model/Group.h"
#include "smtk/model/Model.h"

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

int TestCreateModifyRGGPin(int argc, char* argv[])
{
  // Create a resource manager
  smtk::resource::Manager::Ptr resourceManager = smtk::resource::Manager::create();

  // Register rgg resources to the resource manager
  {
    smtk::session::rgg::Registrar::registerTo(resourceManager);
  }

  // Create an operation manager
  smtk::operation::Manager::Ptr operationManager = smtk::operation::Manager::create();

  // Register rgg operators to the operation manager
  {
    smtk::session::rgg::Registrar::registerTo(operationManager);
  }

  // Register the resource manager to the operation manager (newly created
  // resources will be automatically registered to the resource manager).
  operationManager->registerResourceManager(resourceManager);

  smtk::model::Model model;
  smtk::resource::ResourcePtr resource;
  {
    auto createModelOp = smtk::session::rgg::CreateModel::create();
    if (!createModelOp)
    {
      std::cerr << "No create model operator\n";
      return 1;
    }

    // Create a hex core
    createModelOp->parameters()->findString("name")->setValue("rgg test core");
    createModelOp->parameters()->findString("geometry type")->setDiscreteIndex(0);
    createModelOp->parameters()->findDouble("z origin")->setValue(10);
    createModelOp->parameters()->findDouble("height")->setValue(20);
    createModelOp->parameters()->findDouble("duct thickness")->setValue(3);
    createModelOp->parameters()->findInt("hex lattice size")->setValue(5);

    auto createModelOpResult = createModelOp->operate();
    if (createModelOpResult->findInt("outcome")->value() !=
      static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
    {
      std::cerr << "create model operator failed\n";
      return 1;
    }

    // Use a shared pointer to track the created resource so that it's valid out side of this scope
    resource =
      createModelOpResult->findResource("resource")->value();

    model =
      createModelOpResult->findComponent("created")->valueAs<smtk::model::Entity>();
    if (!model.isValid())
    {
      std::cerr << "create model operator constructed an invalid model\n";
      return 1;
    }

    smtk::model::Group coreGroup = createModelOpResult->findComponent("created")->
        valueAs<smtk::model::Entity>(1);
    if (!coreGroup.isValid())
    {
      std::cerr << "create model operator constructed an invliad core group\n";
      return 1;
    }
    if (!coreGroup.hasStringProperty(Core::propDescription))
    {
      std::cerr << "The created core does not have json representation string property\n";
      return 1;
    }
    Core core = json::parse(coreGroup.stringProperty(Core::propDescription)[0]);
    Core::UuidToSchema layout;
    layout[smtk::common::UUID("1c183aa8-e3bc-430b-a292-fd84c79836be")] = {std::make_pair(1,1)};
    core.setLayout(layout);

    Core targetCore;
    targetCore.setLayout(layout);
    targetCore.setName("rgg test core");
    targetCore.setGeomType(Core::GeomType::Hex);
    targetCore.setZOrigin(10);
    targetCore.setHeight(20);
    targetCore.setDuctThickness(3);
    targetCore.setLatticeSize(5);
    assert(core == targetCore);
    std::cout << "RGG core is serialized properly" <<std::endl;
  }

  // Test create a pin
  auto editPinOp = smtk::session::rgg::EditPin::create();
  if (!editPinOp)
  {
    std::cerr << "No \"Edit Pin\" operator\n";
    return 1;
  }
  // Create the targetPin
  Pin targetPin = Pin("rgg test pin", "rggPin0");
  json targetPinJson = targetPin;
  std::string targetPinStr = targetPinJson.dump();

  editPinOp->parameters()->associate(model.component());

  editPinOp->parameters()
    ->findString("pin representation")
    ->setValue(targetPinStr);

  auto createPinResult = editPinOp->operate();
  if (createPinResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "\"Edit Pin\" operator failed to create a pin\n";
    return 1;
  }

  auto numberOfCreatedItem = createPinResult->findComponent("created")->numberOfValues();
  assert(numberOfCreatedItem == 2);

  smtk::model::AuxiliaryGeometry resultPinAux = createPinResult->findComponent("created")
      ->valueAs<smtk::model::Entity>();
  assert(resultPinAux.name() == "rgg test pin");
  assert(resultPinAux.stringProperty("rggType")[0] == Pin::typeDescription);
  assert(resultPinAux.stringProperty("label")[0] == "rggPin0");

  if (!resultPinAux.hasStringProperty(Pin::propDescription))
  {
    std::cerr << "The created pin does not have json representation string property\n";
    return 1;
  }

  Pin createPinOpResultPin = json::parse(resultPinAux.stringProperty(Pin::propDescription)[0]);
  assert (createPinOpResultPin == targetPin);
  std::cout << "Edit Pin operator passes to create a pin\n";

  // Test modify a pin
  editPinOp->parameters()->removeAllAssociations();
  editPinOp->parameters()->associate(resultPinAux.component());

  // Tweak the target pin
  targetPin.setName("pin1");
  targetPin.setLabel(Pin::generateUniqueLabel());
  targetPin.setCellMaterialIndex(3);
  std::vector<double> color = {0.5, 0.5, 0.5, 1.0};
  targetPin.setColor(color);
  targetPin.setCutAway(true);
  targetPin.setZOrigin(10);
  auto& currentPieces = targetPin.pieces();
  currentPieces.push_back({Pin::Piece()});

  auto& currentLayers = targetPin.layerMaterials();
  currentLayers.push_back({Pin::LayerMaterial()});

  nlohmann::json modifiedPinJson = targetPin;
  std::cout << modifiedPinJson.dump(2) <<std::endl;
  std::string modifiedPinString = modifiedPinJson.dump();
  editPinOp->parameters()->findString("pin representation")->setValue(modifiedPinString);

  auto editPinResult = editPinOp->operate();
  if (editPinResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "\"Edit Pin\" operator failed to edit a pin\n";
    return 1;
  }

  assert(editPinResult->
      findComponent("created")->numberOfValues() == 6);

  // Pin itself and the core
  assert(editPinResult->
      findComponent("modified")->numberOfValues() == 2);

  assert(editPinResult->
      findComponent("expunged")->numberOfValues() == 1);

  smtk::model::AuxiliaryGeometry editedPinAux = editPinResult->
      findComponent("modified")->valueAs<smtk::model::Entity>();

  if (!editedPinAux.hasStringProperty(Pin::propDescription))
  {
    std::cerr << "The edited pin does not have json representation string property\n";
    return 1;
  }
  assert(editedPinAux.name() == "pin1");
  assert(editedPinAux.stringProperty("label")[0] == targetPin.label());

  Pin editPinOpResultPin = json::parse(editedPinAux.stringProperty(Pin::propDescription)[0]);

  assert(editPinOpResultPin == targetPin);
  std::cout << "Edit Pin operator passes to edit a pin\n";

  return 0;
}
