//=========================================================================
//  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/CellSelection.h"
#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/ExportModel.h"
#include "smtk/session/aeva/operators/Import.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/StringItem.h"

#include "smtk/common/testing/cxx/helpers.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 <vtkPolyData.h>

#include <iostream>

namespace
{
std::string dataRoot = AEVA_DATA_DIR;
std::string writeRoot = AEVA_SCRATCH_DIR;
}

smtk::model::Entity::Ptr importMed(const smtk::operation::Manager::Ptr& operationManager,
  const smtk::session::aeva::Resource::Ptr& aevaResource,
  const std::vector<std::string>& filenames)
{
  // Create an import operation
  smtk::session::aeva::Import::Ptr importOp =
    operationManager->create<smtk::session::aeva::Import>();
  test(!!importOp, "No import operation");

  // Set the file path(s)
  importOp->parameters()->findFile("filename")->setNumberOfValues(filenames.size());
  for (std::size_t i = 0; i < filenames.size(); ++i)
  {
    importOp->parameters()->findFile("filename")->setValue(i, filenames[i]);
  }
  // set the resource
  if (aevaResource)
  {
    importOp->parameters()->associate(aevaResource);
  }

  // Test the ability to operate
  test(importOp->ableToOperate(), "Import operation unable to operate");

  // Execute the operation
  smtk::operation::Operation::Result importOpResult = importOp->operate();

  // Test for success
  test(importOpResult->findInt("outcome")->value() ==
      static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED),
    "Import operation failed\n");

  // Retrieve the resulting model
  smtk::attribute::ComponentItemPtr componentItem =
    std::dynamic_pointer_cast<smtk::attribute::ComponentItem>(
      importOpResult->findComponent("created"));

  // Import should create a model and surface + volume at least for each file.
  test(componentItem->numberOfValues() >= 3 * filenames.size(), "Component count too small");

  smtk::model::Entity::Ptr model;
  std::size_t validCount = 0;
  for (std::size_t i = 0; i < componentItem->numberOfValues(); ++i)
  {
    // Access the generated model
    smtk::model::Entity::Ptr entity =
      std::dynamic_pointer_cast<smtk::model::Entity>(componentItem->value(i));

    // Test model validity
    if (entity && entity->referenceAs<smtk::model::Model>().isValid())
    {
      ++validCount;
      model = entity;
    }
  }
  test(validCount == filenames.size(), "Valid models and filename count don't match");
  return model;
}

int TestMedImportExport(int argc, char* argv[])
{
  (void)argc;
  (void)argv;
  std::string files[] = { "/med/oks003_FMB_AGS_LVTIT.med", "/med/oks003_FMC_AGS_03_LVTIT.med" };
  vtkIdType numPts[2] = { 20171, 24870 };

  // Create a resource manager
  smtk::resource::Manager::Ptr resourceManager = smtk::resource::Manager::create();

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

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

  // Register the aeva session to the operation manager
  {
    smtk::session::aeva::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::session::aeva::Session::Ptr session = smtk::session::aeva::Session::create();
  auto aevaResource = resourceManager->create<smtk::session::aeva::Resource>();
  aevaResource->setSession(session);
  std::vector<std::string> paths;

  for (int i = 0; i < 2; ++i)
  {
    std::string importFilePath(dataRoot);
    importFilePath += files[i];
    std::vector<std::string> importPath = { importFilePath };

    // use a separate resource for the first import.
    auto rsrc = resourceManager->create<smtk::session::aeva::Resource>();
    rsrc->setSession(session);
    smtk::model::Entity::Ptr model = importMed(operationManager, rsrc, importPath);

    // Access all of the model's faces
    smtk::model::EntityRefs faces =
      model->modelResource()->entitiesMatchingFlagsAs<smtk::model::EntityRefs>(smtk::model::FACE);
    test(!faces.empty(), "No faces");

    smtk::model::Face face = *faces.begin();
    std::cout << "First face is " << face.name() << " " << face.entity() << "\n";

    vtkSmartPointer<vtkDataSet> facePD =
      vtkDataSet::SafeDownCast(session->findStorage(face.entity()));

    test(!!facePD.Get(), "No geometry for face");

    smtkTest(facePD->GetNumberOfPoints() == numPts[i] && facePD->GetNumberOfCells() > 0,
      "Face geometry contains an unexpected number of points and/or cells\n"
        << facePD->GetNumberOfPoints() << " " << facePD->GetNumberOfCells());

    // Create an ReconstructSurface operation
    auto exportOp = operationManager->create<smtk::session::aeva::ExportModel>();
    test(!!exportOp, "No Export operation");

    // Set the input
    exportOp->parameters()->associate(model);

    std::string writePath(writeRoot);
    writePath += "/" + smtk::common::UUID::random().toString() + ".med";
    exportOp->parameters()->findFile("filename")->setValue(writePath);

    test(exportOp->ableToOperate(), "Export operation unable to operate");

    // Execute the operation
    smtk::operation::Operation::Result exportResult = exportOp->operate();

    test(exportResult->findInt("outcome")->value() ==
        static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED),
      "Export operation failed");

    // re-import, common resource.
    paths.push_back(writePath);
    // check below
  }

  // test multi-import with the two exported files.
  auto model = importMed(operationManager, aevaResource, paths);

  smtk::model::EntityRefs faces =
    aevaResource->entitiesMatchingFlagsAs<smtk::model::EntityRefs>(smtk::model::FACE);
  test(!faces.empty(), "No faces");

  for (const auto& entity : faces)
  {
    smtk::model::Face face(entity);

    vtkSmartPointer<vtkDataSet> facePD =
      vtkDataSet::SafeDownCast(session->findStorage(face.entity()));

    test(!!facePD.Get(), "No geometry for face");

    // both .med in one resource, allow either point count.
    smtkTest(
      (facePD->GetNumberOfPoints() == numPts[0] || facePD->GetNumberOfPoints() == numPts[1]) &&
        facePD->GetNumberOfCells() > 0,
      "Re-import geometry contains an unexpected number of points and/or cells\n"
        << facePD->GetNumberOfPoints() << " " << facePD->GetNumberOfCells());
  }
  return 0;
}
