//=========================================================================
//  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/attribute/VoidItem.h"

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

#include "smtk/session/rgg/Core.h"

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

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

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

#include <tuple>

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

int TestCreateModifyRGGDuct(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 rect core
    createModelOp->parameters()->findString("name")->setValue("rgg test core");
    createModelOp->parameters()->findString("geometry type")->setDiscreteIndex(1);
    createModelOp->parameters()->findDouble("z origin")->setValue(10);
    createModelOp->parameters()->findDouble("height")->setValue(20);
    createModelOp->parameters()->findDouble("duct thickness X")->setValue(3);
    createModelOp->parameters()->findDouble("duct thickness Y")->setValue(4);
    createModelOp->parameters()->findInt("rect lattice size")->setValue(0, 5);
    createModelOp->parameters()->findInt("rect lattice size")->setValue(1, 6);

    auto createModelOpResult = createModelOp->operate();

    // Use a shared pointer to track the created resource so that it's valid outside
    // 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 invalid 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 targetCore;
    targetCore.setName("rgg test core");
    targetCore.setGeomType(Core::GeomType::Rect);
    targetCore.setZOrigin(10);
    targetCore.setHeight(20);
    targetCore.setDuctThickness(3, 4);
    targetCore.setLatticeSize(5, 6);
    assert(targetCore == core);
    std::cout << "RGG core is serialized properly" <<std::endl;
  }

  // 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.stringProperty("rggType")[0] == Duct::typeDescription);

  if (!resultDuctAux.hasStringProperty(Duct::propDescription))
  {
    std::cerr << "The newly created Duct does not have propDescription";
    return 1;
  }
  Duct resultDuct = json::parse(resultDuctAux.stringProperty(Duct::propDescription)[0]);
  assert(resultDuct == targetDuct);
  std::cout << "Edit Duct operator passes to create a duct\n";

  // Test modify a duct
  editDuctOp->parameters()->removeAllAssociations();
  editDuctOp->parameters()->associate(resultDuctAux.component());

  // Tweak the target Duct
  targetDuct.setName("rgg test duct tweaked");
  targetDuct.setCutAway(true);
  Duct::Segment seg0, seg1;
  seg0.baseZ = 0;
  seg0.height = 10;
  seg0.layers.push_back(std::make_tuple(0, 0.5, 0.7));
  seg0.layers.push_back(std::make_tuple(1, 1.0, 1.0));

  seg1.baseZ = 10;
  seg1.height = 10;
  seg1.layers.push_back(std::make_tuple(2, 0.4, 0.4));
  seg1.layers.push_back(std::make_tuple(0, 0.5, 0.7));
  seg1.layers.push_back(std::make_tuple(1, 1.0, 1.0));
  targetDuct.setSegments({seg0, seg1});
  json modifiedDuctJson = targetDuct;
  std::string modifiedDuctStr = modifiedDuctJson.dump();

  editDuctOp->parameters()->findString("duct representation")->setValue(modifiedDuctStr);

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

  assert(editDuctResult->findComponent("created")->numberOfValues() == 5);
  assert(editDuctResult->findComponent("modified")->numberOfValues() == 2); // Duct and core
  assert(editDuctResult->findComponent("expunged")->numberOfValues() == 1);

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

  if (!editedDuctAux.hasStringProperty(Duct::propDescription))
  {
    std::cerr << "The edited pin does not have json representation string property\n";
    return 1;
  }
  assert(editedDuctAux.name() == "rgg test duct tweaked");

  Duct editDuctResultPin = json::parse(editedDuctAux.stringProperty(Duct::propDescription)[0]);

  assert(editDuctResultPin == targetDuct);
  std::cout << "Edit Duct operator passes to edit a duct\n";

  return 0;
}
