//=========================================================================
//  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/Registrar.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.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/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 <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, Managers& managers)
{
  auto operationManager = managers->template get<smtk::operation::Manager::Ptr>();

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

  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 expectedArrays(const smtk::model::Entity::Ptr& shape)
{
  bool status = false;
  if (!shape)
  {
    std::cerr << "Null shape\n";
    return status;
  }

  if (!shape->isCellEntity())
  {
    // Silently ignore non-cell components.
    status = true;
    return status;
  }
  std::cerr << "  Considering " << shape->name() << " (" << shape->flagSummary() << ")\n";
  auto rsrc = std::dynamic_pointer_cast<Resource>(shape->resource());
  if (!rsrc)
  {
    std::cerr << "    Shape (" << shape->name() << ") has no aeva resource.\n";
    return status;
  }
  auto session = rsrc->session();
  auto data = session->findStorage(shape->id());
  if (!data)
  {
    std::cerr << "    Shape (" << shape->name() << ") has no VTK data.\n";
    return status;
  }

  if (vtkImageData::SafeDownCast(data))
  {
    std::cout << "    TODO: Arrays not currently tested on image data.\n";
    status = true;
    return status;
  }

  vtkIntArray* ipgids = nullptr;
  vtkIntArray* icgids = nullptr;
  auto* pointData = data->GetAttributes(vtkDataObject::POINT);
  auto* cellData = data->GetAttributes(vtkDataObject::CELL);
  auto* pgids = pointData->GetGlobalIds();
  if (!pgids)
  {
    std::cerr << "    Point global IDs not present.\n";
  }
  if (pgids && !(ipgids = vtkIntArray::SafeDownCast(pgids)))
  {
    std::cerr << "    Point global IDs, \"" << pgids->GetName() << "\" "
              << "(" << pgids->GetClassName() << "), not integer-valued.\n";
  }
  auto* cgids = cellData->GetGlobalIds();
  if (!cgids)
  {
    std::cerr << "    Point global IDs not present.\n";
  }
  if (cgids && !(icgids = vtkIntArray::SafeDownCast(cgids)))
  {
    std::cerr << "    Cell global IDs, \"" << cgids->GetName() << "\" "
              << "(" << cgids->GetClassName() << "), not integer-valued.\n";
  }
  status = ipgids && icgids;

  if (pointData->GetNormals())
  {
    std::cerr << "    AEVA data should *not* have point normals.\n";
    status = false;
  }
  if (cellData->GetNormals())
  {
    std::cerr << "    AEVA data should *not* have cell normals.\n";
    status = false;
  }

  if (status)
  {
    std::cerr << "    OK.\n";
  }

  return status;
}

}

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

  auto managers = Init();

  bool ok = true;
  std::function<void(const smtk::resource::Component::Ptr&)> testArraysOnObject =
    [&ok](const smtk::resource::Component::Ptr& shape) {
      ok &= expectedArrays(std::dynamic_pointer_cast<smtk::model::Entity>(shape));
    };

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

  std::string med_file = AEVA_DATA_DIR "/med/oks003_FMC_AGS_03_LVTIT.med";
  std::string nifti_file =
    AEVA_DATA_DIR "/itk/1.3.12.2.1107.5.2.19.45406.2014120211045428131243076.0.0.0_small.nii";
  std::string stl_file = AEVA_DATA_DIR "/stl/box.stl";
  std::string vtk_file = AEVA_DATA_DIR "/vtk/oks003_TBB_AGS.vtk";

  std::shared_ptr<Resource> rsrc;

  std::cout << "\nNIFTI image data\n";
  rsrc = ImportFile(nifti_file, managers);
  if (rsrc)
  {
    rsrc->visit(testArraysOnObject);
  }

  std::cout << "\nMED mesh data\n";
  rsrc = ImportFile(med_file, managers);
  if (rsrc)
  {
    rsrc->visit(testArraysOnObject);
  }

  std::cout << "\nSTL mesh data\n";
  rsrc = ImportFile(stl_file, managers);
  if (rsrc)
  {
    rsrc->visit(testArraysOnObject);
  }

  std::cout << "\nVTK mesh data\n";
  rsrc = ImportFile(vtk_file, managers);
  if (rsrc)
  {
    rsrc->visit(testArraysOnObject);
  }

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