//=========================================================================
//  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/Import.h"
#include "smtk/session/aeva/operators/Read.h"
#include "smtk/session/aeva/operators/Write.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/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 importFile(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 and/or 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 TestResourceReadWrite(int argc, char* argv[])
{
  (void)argc;
  (void)argv;
  std::string files[] = { "/med/oks003_FMB_AGS_LVTIT.med",
    "/itk/1.3.12.2.1107.5.2.19.45406.2014120211045428131243076.0.0.0_small.nii" };
  vtkIdType numPts[2] = { 20171, 438600 };

  // 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 (const auto& file : files)
  {
    std::string importFilePath(dataRoot);
    importFilePath += file;
    std::vector<std::string> importPath = { importFilePath };

    smtk::model::Entity::Ptr model = importFile(operationManager, aevaResource, importPath);
  }
  auto writeOp = operationManager->create<smtk::session::aeva::Write>();
  test(!!writeOp, "No Write operation");

  // Set the input
  writeOp->parameters()->associate(aevaResource);

  std::string writePath(writeRoot);
  writePath += "/" + smtk::common::UUID::random().toString() + ".smtk";
  aevaResource->setLocation(writePath);

  test(writeOp->ableToOperate(), "Write operation unable to operate");

  // Execute the operation
  smtk::operation::Operation::Result res = writeOp->operate();

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

  auto readOp = operationManager->create<smtk::session::aeva::Read>();
  test(!!readOp, "No Read operation");

  // Set the input
  // readOp->parameters()->associate(aevaResource);
  readOp->parameters()->findFile("filename")->setValue(writePath);

  test(readOp->ableToOperate(), "Read operation unable to operate");

  // Execute the operation
  res = readOp->operate();

  test(res->findInt("outcome")->value() ==
      static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED),
    "Read operation failed");
  auto readResource = std::dynamic_pointer_cast<smtk::session::aeva::Resource>(
    res->findResource("resource")->value());

  // Access all of the model's faces
  smtk::model::EntityRefs faces =
    readResource->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[0] && facePD->GetNumberOfCells() > 0,
    "Face geometry contains an unexpected number of points and/or cells\n"
      << facePD->GetNumberOfPoints() << " " << facePD->GetNumberOfCells());

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

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

    if (volumePD.Get())
    {
      smtkTest(volumePD->GetNumberOfPoints() == numPts[1] && volumePD->GetNumberOfCells() > 0,
        "Volume geometry contains an unexpected number of points and/or cells\n"
          << volumePD->GetNumberOfPoints() << " " << volumePD->GetNumberOfCells());
    }
  }

  return 0;
}
