//=========================================================================
//  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/ComponentItem.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/Assembly.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/EditAssembly.h"
#include "smtk/session/rgg/operators/EditDuct.h"
#include "smtk/session/rgg/operators/EditPin.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/json/jsonDuct.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 TestCreateModifyRGGAssembly(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);
    assert(coreGroup.name() == "rgg test core");
    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;
  }

  smtk::model::AuxiliaryGeometry resultPinAux = createPinResult->findComponent("created")
      ->valueAs<smtk::model::Entity>();

  // Test create a duct
  auto editDuctOp = smtk::session::rgg::EditDuct::create();
  if(!editDuctOp)
  {
    std::cerr << "No \"Edit Duct\" operator\n";
    return 1;
  }
  // Create targetDuct
  Duct targetDuct = Duct("rgg test duct", false);
  Duct::Segment initSeg;
  initSeg.baseZ = 10.0;
  initSeg.height = 20.0;
  initSeg.layers.push_back(std::make_tuple(0, 1.0, 1.0));
  targetDuct.setSegments({initSeg});
  json targetDuctJson = targetDuct;
  std::string targetDuctStr = targetDuctJson.dump();

  editDuctOp->parameters()->associate(model.component());
  editDuctOp->parameters()->findString("duct representation")->setValue(targetDuctStr);

  auto createDuctResult = editDuctOp->operate();

  if (createDuctResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "\"Edit Duct\" operator failed to create a duct\n";
    return 1;
  }

  assert(createDuctResult->findComponent("created")->numberOfValues() == 2);

  smtk::model::AuxiliaryGeometry resultDuctAux =
      createDuctResult->findComponent("created")->valueAs<smtk::model::Entity>();
  if (!resultDuctAux.isValid())
  {
    std::cerr << "Edit duct opertor constructed an invalid duct\n";
    return 1;
  }
  assert(resultDuctAux.auxiliaryGeometries().size()==1);

  // Test Create an assembly
  auto editAssyOp = smtk::session::rgg::EditAssembly::create();
  if (!editAssyOp)
  {
    std::cerr << "NO \"Edit Assembly\" operator\n";
    return 1;
  }

  // Create targetAssembly
  Assembly targetAssy = Assembly("rgg test assembly", "rggAssy0");
  targetAssy.setAssociatedDuct(resultDuctAux.entity());
  json targetAssyJson = targetAssy;
  std::string targetAssyStr = targetAssyJson.dump();

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

  editAssyOp->parameters()->findString("assembly representation")->setValue(targetAssyStr);

  editAssyOp->parameters()->findComponent("associated duct")->setValue(resultDuctAux.component());

  auto createAssyOpResult = editAssyOp->operate();
  if (createAssyOpResult->findInt("outcome")->value() !=
      static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "\"Create Assembly\" operator failed\n";
    return -1;
  }
  auto numberOfCreatedItem = createAssyOpResult->findComponent("created")->numberOfValues();
  // Unit duct prototype, duct instance used by assembly and assembly group
  assert(numberOfCreatedItem == 3);

  smtk::model::Group resultAssyGroup = createAssyOpResult->findComponent("created")->
      valueAs<smtk::model::Entity>(createAssyOpResult->findComponent("created")->numberOfValues()-1);

  assert(resultAssyGroup.stringProperty("rggType")[0] == Assembly::typeDescription);
  assert(resultAssyGroup.name() == targetAssy.name());
  assert(resultAssyGroup.stringProperty("label")[0] == targetAssy.label());

  if (!resultAssyGroup.hasStringProperty(Assembly::propDescription))
  {
    std::cerr << "The created assembly does not have json representation string property\n";
    return 1;
  }
  auto instances = model.resource()->findEntitiesOfType(INSTANCE_ENTITY);
  assert(instances.size() == 1);

  Assembly createdAssyOpResultAssy = json::parse(
        resultAssyGroup.stringProperty(Assembly::propDescription)[0]);
  assert(createdAssyOpResultAssy == targetAssy);
  std::cout << "Create Assembly operator passes\n";

  // Test modify an asembly
  editAssyOp->parameters()->removeAllAssociations();
  editAssyOp->parameters()->associate(resultAssyGroup.component());
  editAssyOp->parameters()->findComponent("associated duct")->setValue(resultDuctAux.component());
  std::cout << "Edit Assy: association name="<<editAssyOp->parameters()->associations()->name()
            <<"  valid?" << editAssyOp->parameters()->associations()->isValid()
            <<" group valid?" << resultAssyGroup.isValid() << std::endl;

  // Tweak the target assembly
  targetAssy.setName("assy1");
  targetAssy.setLabel(Assembly::generateUniqueLabel());
  targetAssy.setColor({1,1,1,1});
  Assembly::UuidToSchema layout;
  layout[resultPinAux.entity().toString()] = {std::make_pair(1,1)};
  targetAssy.setLayout(layout);
  Assembly::UuidToCoordinates uTC;
  uTC[resultPinAux.entity()] = {std::make_tuple(0,0,0)};
  targetAssy.setEntityToCoordinates(uTC);
  targetAssy.setAssociatedDuct(resultDuctAux.entity());
  targetAssy.setCenterPin(false);
  targetAssy.setPitch(1, 1);
  targetAssy.setLatticeSize(5, 5);
  OperationHelper::calculateEntityCoordsInAssembly(targetAssy, true, model.resource());

  json modifiedAssyJson = targetAssy;
  std::string modifiedAssyStr = modifiedAssyJson.dump();
  editAssyOp->parameters()->findString("assembly representation")->setValue(modifiedAssyStr);

  auto editAssyOpResult = editAssyOp->operate();
  if (editAssyOpResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "\"Edit Assembly\" operator failed\n";
    return 1;
  }
  numberOfCreatedItem = editAssyOpResult->findComponent("created")->numberOfValues();
  // one instance&&unit prototype for duct and one instance&&unit prototype for the pin
  assert(numberOfCreatedItem == 4);

  auto numberOfModifiedItem = editAssyOpResult->findComponent("modified")->numberOfValues();
  assert(numberOfModifiedItem == 4); // pin, duct, group and model

  auto numberOfTessCItem = editAssyOpResult->findComponent("tess_changed")->numberOfValues();
  assert(numberOfTessCItem == 2); // pin, duct instances

  auto numberOfExpungedItem = editAssyOpResult->findComponent("expunged")->numberOfValues();
  assert(numberOfExpungedItem == 2); // one instance&&unit prototype of duct

  smtk::model::Group editedAssyGroup = editAssyOpResult->
      findComponent("modified")->valueAs<smtk::model::Entity>(
        editAssyOpResult->findComponent("modified")->numberOfValues()-1);

  if (!editedAssyGroup.hasStringProperty(Assembly::propDescription))
  {
    std::cerr << "ERROR: The edited Assembly does not have json representation string property\n";
    return 1;
  }
  assert(editedAssyGroup.name() == "assy1");
  assert(editedAssyGroup.stringProperty("label")[0] == targetAssy.label());
  instances = model.resource()->findEntitiesOfType(INSTANCE_ENTITY);
  assert(instances.size() == 2); // Pin instance + duct instance

  Assembly editAssyOpResultAssy = json::parse(
        editedAssyGroup.stringProperty(Assembly::propDescription)[0]);

  assert(editAssyOpResultAssy == targetAssy);
  std::cout << "Edit Assembly operator passes\n";

  return 0;
}
