//=========================================================================
//  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/Predicates.h"
#include "smtk/session/aeva/Registrar.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"
#include "smtk/session/aeva/operators/AllPrimitivesFeature.h"
#include "smtk/session/aeva/operators/Import.h"
#include "smtk/session/aeva/operators/NormalFeature.h"
#include "smtk/session/aeva/operators/PointsOfPrimitivesFeature.h"
#include "smtk/session/aeva/operators/ProximityFeature.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/VoidItem.h"

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

#include "smtk/resource/Manager.h"

#include "smtk/operation/Manager.h"

#include "vtkDataObject.h"
#include "vtkDataSetAttributes.h"
#include "vtkIntArray.h"
#include "vtkPolyData.h"
#include "vtkTransform.h"
#include "vtkTransformFilter.h"

#include <iostream>

using namespace smtk::session::aeva;

namespace
{

smtk::common::Managers::Ptr Init()
{
  auto managers = smtk::common::Managers::create();

  // Construct smtk managers
  {
    smtk::resource::Registrar::registerTo(managers);
    smtk::operation::Registrar::registerTo(managers);
  }

  // access smtk managers
  auto resourceManager = managers->get<smtk::resource::Manager::Ptr>();
  auto operationManager = managers->get<smtk::operation::Manager::Ptr>();

  // Initialize smtk managers
  {
    smtk::attribute::Registrar::registerTo(operationManager);
    operationManager->registerResourceManager(resourceManager);
    smtk::session::aeva::Registrar::registerTo(resourceManager);
    smtk::session::aeva::Registrar::registerTo(operationManager);
  }

  return managers;
}

template<typename Managers>
std::shared_ptr<Resource> ImportFile(const std::string& filename,
  std::shared_ptr<Resource>& resource,
  Managers& managers)
{
  auto operationManager = managers->template get<smtk::operation::Manager::Ptr>();

  // Create an import operation
  Import::Ptr importOp = operationManager->template create<Import>();
  if (!importOp)
  {
    std::cerr << "  No import operation\n";
    return resource;
  }

  importOp->parameters()->associations()->appendValue(resource);
  importOp->parameters()->findFile("filename")->setValue(filename);

  // Test the ability to operate
  if (!importOp->ableToOperate())
  {
    std::cerr << "  Import operation unable to operate\n";
    return resource;
  }

  // Execute the operation
  smtk::operation::Operation::Result importOpResult = importOp->operate();
  if (importOpResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Import operation failed\n";
    return resource;
  }

  if (!resource)
  {
    auto resourceItem = importOpResult->findResource("resource");
    resource = std::dynamic_pointer_cast<smtk::session::aeva::Resource>(resourceItem->value());
    if (!resource || !resource->session())
    {
      std::cerr << "  Resulting resource is invalid\n";
      return resource;
    }
  }

  return resource;
}

bool translateBox(std::shared_ptr<smtk::model::Entity>& box)
{
  if (!box)
  {
    std::cerr << "Null box to translate.\n";
    return false;
  }

  auto resource = std::dynamic_pointer_cast<smtk::session::aeva::Resource>(box->resource());
  if (!resource)
  {
    std::cerr << "Box has no resource.\n";
    return false;
  }

  auto session = resource->session();
  vtkSmartPointer<vtkDataObject> geom = session->findStorage(box->id());
  if (!geom)
  {
    std::cerr << "Box has no geometry.\n";
    return false;
  }

  vtkNew<vtkTransformFilter> transformFilter;
  vtkNew<vtkTransform> transform;
  transform->Translate(1.5, 0., 0.);
  transformFilter->SetInputDataObject(geom);
  transformFilter->SetTransform(transform.GetPointer());
  transformFilter->Update();
  vtkNew<vtkPolyData> replacement;
  replacement->ShallowCopy(transformFilter->GetOutput());
  session->addStorage(box->id(), replacement);

  return true;
}

template<typename Managers>
bool testAllPrimitives(std::shared_ptr<smtk::model::Entity>& workpiece, Managers& managers)
{
  std::cout << "Select all primitives of workpiece.\n";
  auto operationManager = managers->template get<smtk::operation::Manager::Ptr>();
  auto featureOp = operationManager->template create<AllPrimitivesFeature>();
  if (!featureOp)
  {
    std::cerr << "  Could not create feature op.\n";
    return false;
  }
  featureOp->parameters()->associations()->setValue(workpiece);
  auto result = featureOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Feature op failed.\n";
    return false;
  }

  auto created = result->findComponent("created");
  if (created->numberOfValues() != 1 || !created->value())
  {
    std::cerr << "  No output selection.\n";
    return false;
  }

  auto session = dynamic_cast<Resource*>(workpiece->resource().get())->session();
  auto geom = session->findStorage(created->value()->id());
  if (!geom)
  {
    std::cerr << "  No selection geometry.\n";
    return false;
  }

  vtkIdType numberOfCells = smtk::session::aeva::NumberOfCells(geom);
  if (numberOfCells != 24)
  {
    std::cerr << "  Unexpected number of cells (" << numberOfCells << " != 24).\n";
    return false;
  }

  std::cerr << "  Done.\n";
  return true;
}

template<typename Managers>
bool testNormalFeature(std::shared_ptr<smtk::model::Entity>& workpiece, Managers& managers)
{
  std::cout << "Select primitives of workpiece with similar surface normal.\n  ";
  auto operationManager = managers->template get<smtk::operation::Manager::Ptr>();
  auto featureOp = operationManager->template create<NormalFeature>();
  if (!featureOp)
  {
    std::cerr << "  Could not create feature op.\n";
    return false;
  }
  featureOp->parameters()->associations()->setValue(workpiece);
  auto result = featureOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Feature op failed.\n";
    return false;
  }

  auto created = result->findComponent("created");
  if (created->numberOfValues() != 1 || !created->value())
  {
    std::cerr << "  No output selection.\n";
    return false;
  }

  auto session = dynamic_cast<Resource*>(workpiece->resource().get())->session();
  auto geom = session->findStorage(created->value()->id());
  if (!geom)
  {
    std::cerr << "  No selection geometry.\n";
    return false;
  }

  vtkIdType numberOfCells = smtk::session::aeva::NumberOfCells(geom);
  if (numberOfCells != 4)
  {
    std::cerr << "  Unexpected number of cells (" << numberOfCells << " != 24).\n";
    return false;
  }

  std::cerr << "  Done.\n";
  return true;
}

template<typename Managers>
bool testPointsOfPrimitives(std::shared_ptr<smtk::model::Entity>& workpiece, Managers& managers)
{
  std::cout << "Select points of primitives of workpiece.\n";
  auto operationManager = managers->template get<smtk::operation::Manager::Ptr>();
  auto featureOp = operationManager->template create<PointsOfPrimitivesFeature>();
  if (!featureOp)
  {
    std::cerr << "  Could not create feature op.\n";
    return false;
  }
  featureOp->parameters()->associations()->setValue(workpiece);
  auto result = featureOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Feature op failed.\n";
    return false;
  }

  auto created = result->findComponent("created");
  if (created->numberOfValues() != 1 || !created->value())
  {
    std::cerr << "  No output selection.\n";
    return false;
  }

  auto session = dynamic_cast<Resource*>(workpiece->resource().get())->session();
  auto geom = session->findStorage(created->value()->id());
  if (!geom)
  {
    std::cerr << "  No selection geometry.\n";
    return false;
  }

  vtkIdType numberOfCells = smtk::session::aeva::NumberOfCells(geom);
  if (numberOfCells != 16)
  {
    std::cerr << "  Unexpected number of cells (" << numberOfCells << " != 16).\n";
    return false;
  }

  int dim = smtk::session::aeva::EstimateParametricDimension(geom);
  if (dim != 0)
  {
    std::cerr << "  Unexpected selection dimension (" << dim << " != 0).\n";
    return false;
  }

  std::cerr << "  Done.\n";
  return true;
}

template<typename Managers>
bool testProximityFeature(std::shared_ptr<smtk::model::Entity>& workpiece,
  std::shared_ptr<smtk::model::Entity>& tool,
  Managers& managers)
{
  std::cout << "Select primitives of workpiece near tool.\n";
  auto operationManager = managers->template get<smtk::operation::Manager::Ptr>();
  auto featureOp = operationManager->template create<ProximityFeature>();
  if (!featureOp)
  {
    std::cerr << "  Could not create feature op.\n";
    return false;
  }
  featureOp->parameters()->associations()->setValue(workpiece);
  featureOp->parameters()->findComponent("target")->setValue(tool);
  featureOp->parameters()->findDouble("distance")->setValue(0.625);
  auto result = featureOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Feature op failed.\n";
    return false;
  }

  auto created = result->findComponent("created");
  if (created->numberOfValues() != 1 || !created->value())
  {
    std::cerr << "  No output selection.\n";
    return false;
  }

  auto session = dynamic_cast<Resource*>(workpiece->resource().get())->session();
  auto geom = session->findStorage(created->value()->id());
  if (!geom)
  {
    std::cerr << "  No selection geometry.\n";
    return false;
  }

  vtkIdType numberOfCells = smtk::session::aeva::NumberOfCells(geom);
  if (numberOfCells != 10)
  {
    std::cerr << "  Unexpected number of cells (" << numberOfCells << " != 10).\n";
    return false;
  }

  std::cerr << "  Done.\n";
  return true;
}

}

int TestFeatureSelectionOps(int argc, char* argv[])
{
  (void)argc;
  (void)argv;

  auto managers = Init();
  bool ok = true;
  std::shared_ptr<Resource> rsrc;
  std::shared_ptr<smtk::model::Entity> boxes1;
  std::shared_ptr<smtk::model::Entity> boxes2;

  // Test that importing files of all types produce data with global cell and node IDs.

  std::cout << "Loading test data\n";
  std::string stl_file = AEVA_DATA_DIR "/stl/box.stl";
  rsrc = ImportFile(stl_file, rsrc, managers);
  rsrc = ImportFile(stl_file, rsrc, managers);

  std::function<void(const smtk::resource::Component::Ptr&)> findBoxes =
    [&ok, &boxes1, &boxes2](const smtk::resource::Component::Ptr& shape) {
      auto ent = std::dynamic_pointer_cast<smtk::model::Entity>(shape);
      if (ent && ent->isCellEntity())
      {
        if (!boxes1)
        {
          boxes1 = ent;
        }
        else if (!boxes2)
        {
          boxes2 = ent;
        }
        else
        {
          std::cout << "More cells than expected.\n";
          ok = false;
        }
      }
    };
  rsrc->visit(findBoxes);
  if (!ok)
  {
    return EXIT_FAILURE;
  }

  if (!translateBox(boxes2))
  {
    return EXIT_FAILURE;
  }

  ok &= testAllPrimitives(boxes1, managers);
  ok &= testNormalFeature(boxes1, managers);
  ok &= testPointsOfPrimitives(boxes1, managers);
  ok &= testProximityFeature(boxes1, boxes2, managers);

  if (!ok)
  {
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}
