//=========================================================================
//  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/model/Volume.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 importImage(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 volume at least for each file.
  test(componentItem->numberOfValues() >= 2 * 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 TestImageImportExport(int argc, char* argv[])
{
  (void)argc;
  (void)argv;
  std::string files[] = {
    "/itk/1.3.12.2.1107.5.2.19.45406.2014120211045428131243076.0.0.0_small.nii",
    "/itk/1.3.12.2.1107.5.2.19.45406.2014120210325342222042395.0.0.0_small.nii"
  };
  vtkIdType numPts[2] = { 438600, 100352 };

  // 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 = importImage(operationManager, rsrc, importPath);

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

    smtk::model::EntityRefs volumes =
      model->modelResource()->entitiesMatchingFlagsAs<smtk::model::EntityRefs>(smtk::model::VOLUME);
    test(!volumes.empty(), "No volumes");
    smtk::model::Volume volume = *volumes.begin();
    std::cout << "First volume is " << volume.name() << " " << volume.entity() << "\n";

    vtkSmartPointer<vtkDataSet> volumePD =
      vtkDataSet::SafeDownCast(session->findStorage(volume.entity()));

    test(!!volumePD.Get(), "No geometry for volume");

    smtkTest(volumePD->GetNumberOfPoints() == numPts[i] && volumePD->GetNumberOfCells() > 0,
      "Volume geometry contains an unexpected number of points and/or cells\n"
        << volumePD->GetNumberOfPoints() << " " << volumePD->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() + ".vti";
    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 = importImage(operationManager, aevaResource, paths);

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

  for (const auto& entity : volumes)
  {
    smtk::model::Volume volume(entity);

    vtkSmartPointer<vtkDataSet> volumePD =
      vtkDataSet::SafeDownCast(session->findStorage(volume.entity()));

    test(!!volumePD.Get(), "No geometry for volume");

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