//=========================================================================
//  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/io/Logger.h"

#include "smtk/session/aeva/operators/Import.h"

#include "vtk/aeva/ext/vtkMedReader.h"

#include "smtk/session/aeva/Import_xml.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"

#include "smtk/extension/vtk/io/ImportAsVTKData.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/FileItemDefinition.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ReferenceItem.h"
#include "smtk/attribute/Resource.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"

#include "smtk/model/Face.h"
#include "smtk/model/Group.h"
#include "smtk/model/Model.h"
#include "smtk/model/Vertex.h"
#include "smtk/model/Volume.h"

#include "smtk/operation/MarkGeometry.h"

#include "smtk/resource/Manager.h"

#include "smtk/common/Paths.h"

#include "itkImage.h"
#include "itkImageFileReader.h"
#include "itkImageIOBase.h"
#include "itkImageToVTKImageFilter.h"
#include "itkObjectFactoryBase.h"

#include "vtkCellData.h"
#include "vtkDataArray.h"
#include "vtkFieldData.h"
#include "vtkGenerateGlobalIds.h"
#include "vtkGeometryFilter.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkIntArray.h"
#include "vtkLookupTable.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkMultiThreshold.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkUnstructuredGrid.h"
#include "vtkXMLImageDataReader.h"
#include "vtkXMLPImageDataReader.h"

SMTK_THIRDPARTY_PRE_INCLUDE
#include "boost/filesystem.hpp"
SMTK_THIRDPARTY_POST_INCLUDE

#ifdef ERROR
#undef ERROR
#endif

#include <sstream>

using CellEntity = smtk::model::CellEntity;
using CellEntities = smtk::model::CellEntities;

namespace smtk
{
namespace session
{
namespace aeva
{
namespace
{

void relateToParent(const smtk::model::EntityRef& parent, const smtk::model::EntityRef& child)
{
  if (parent.isModel())
  {
    if (child.isCellEntity())
    {
      smtk::model::Model(parent).addCell(smtk::model::CellEntity(child));
    }
    else if (child.isGroup())
    {
      smtk::model::Model(parent).addGroup(smtk::model::Group(child));
    }
    else
    {
      smtkErrorMacro(smtk::io::Logger::instance(),
        "Unknown child type " << child.flagSummary() << ". Did not add to model.");
    }
  }
  else if (parent.isGroup())
  {
    smtk::model::Group(parent).addEntity(child);
  }
  else if (parent.isCellEntity())
  {
    smtk::model::EntityRef(parent).addRawRelation(child);
    smtk::model::EntityRef(child).addRawRelation(parent);
  }
}

smtk::model::EntityRef createCellForData(smtk::model::EntityRef& parent,
  vtkSmartPointer<vtkDataObject> const& data,
  smtk::attribute::ComponentItem::Ptr& created,
  int& count,
  smtk::operation::MarkGeometry& marker,
  long pointIdOffset,
  long cellIdOffset,
  long& maxPointId,
  long& maxCellId)
{
  smtk::model::CellEntity result;
  if (!parent.isValid() || !data)
  {
    return result;
  }
  auto resource = std::dynamic_pointer_cast<smtk::session::aeva::Resource>(parent.resource());
  auto const& session = resource->session();
  if (auto* dataset = vtkDataSet::SafeDownCast(data))
  {
    vtkIdType numberOfCells = dataset->GetNumberOfCells();
    vtkIdType numberOfPoints = dataset->GetNumberOfPoints();
    if (numberOfCells == 0 || numberOfPoints == 0)
    {
      return result;
    }
    Session::offsetGlobalIds(dataset, pointIdOffset, cellIdOffset, maxPointId, maxCellId);
    // Assume first cell has "maximal dimension":
    auto* vcell = dataset->GetCell(0);
    auto vdim = vcell->GetCellDimension();
    auto cellComp =
      resource->insertEntityOfTypeAndDimension(smtk::model::CELL_ENTITY, vdim)->second;
    result = smtk::model::CellEntity(cellComp);
    Session::setPrimary(*cellComp, true);
    std::string name = "cell ";
    if (vdim == 3)
    {
      name = "volume ";
    }
    else if (vdim == 2)
    {
      name = "surface ";
    }
    else if (vdim == 1)
    {
      name = "vertex ";
    }
    result.setName(name + std::to_string(count));
    ++count;
    session->addStorage(cellComp->id(), dataset);
    if (vdim < 3)
    {
      marker.markModified(cellComp);
    }
    relateToParent(parent, result);
    created->appendValue(cellComp);
    return result;
  }
  if (auto* multiblock = vtkMultiBlockDataSet::SafeDownCast(data))
  {
    std::vector<smtk::model::CellEntity> members;
    std::string name = "group " + std::to_string(count);
    ++count;
    auto group = resource->addGroup(/* extraFlags */ 0, name);
    created->appendValue(group.component());
    for (unsigned int bb = 0; bb < multiblock->GetNumberOfBlocks(); ++bb)
    {
      auto cell = createCellForData(group,
        multiblock->GetBlock(bb),
        created,
        count,
        marker,
        pointIdOffset,
        cellIdOffset,
        maxPointId,
        maxCellId);
      if (cell.isValid())
      {
        members.emplace_back(cell);
      }
    }
    group.addEntities(members);
    relateToParent(parent, group);
    return group;
  }
  return result;
}
} // anonymous namespace

Import::Result Import::operateInternal()
{
  smtk::session::aeva::Resource::Ptr resource = nullptr;
  smtk::session::aeva::Session::Ptr session = nullptr;
  m_result = this->createResult(Import::Outcome::FAILED);

  this->prepareResourceAndSession(m_result, resource, session);

  smtk::attribute::FileItem::Ptr filenameItem = this->parameters()->findFile("filename");
  std::string filename = filenameItem->value(0);

  std::string potentialName = smtk::common::Paths::stem(filename);
  if (resource->name().empty() && !potentialName.empty())
  {
    resource->setName(potentialName);
  }

  auto itkExtensions = Import::supportedITKFileFormats();
  std::string ext = smtk::common::Paths::extension(filename);
  if (ext == ".med")
  {
    return this->importMedMesh(resource);
  }
  if (ext == ".vti")
  {
    return this->importVTKImage(resource);
  }
  if (itkExtensions.find(ext) != itkExtensions.end())
  {
    return this->importITKImage(resource);
  }
  return this->importVTKMesh(resource);
}

Import::Specification Import::createSpecification()
{
  Specification spec = this->smtk::operation::XMLOperation::createSpecification();
  auto importDef = spec->findDefinition("import");

  std::vector<smtk::attribute::FileItemDefinition::Ptr> fileItemDefinitions;
  auto fileItemDefinitionFilter = [](smtk::attribute::FileItemDefinition::Ptr const& ptr) {
    return ptr->name() == "filename";
  };
  importDef->filterItemDefinitions(fileItemDefinitions, fileItemDefinitionFilter);

  assert(fileItemDefinitions.size() == 1);

  std::stringstream fileFilters;
  // Add fixed list of explicitly-supported import formats:
  fileFilters << "Nearly Raw Raster Data (*.nrrd *.nrrd.gz);; "
              << "Neuroimaging InFormatics Technology Initiative Files (*.nifti *.nia *.nii "
                 "*.nii.gz *.hdr *.img *.img.gz);; "
              << "VTK Image Data (*.vti);; "
              << "Salome Med Mesh (*.med);; ";

  // Add any other extensions ITK supports that we don't know about:
  auto itkExtensions = Import::supportedITKFileFormats();
  // Subtract extensions we just advertised:
  itkExtensions.erase(".nrrd");
  itkExtensions.erase(".nrrd.gz");
  itkExtensions.erase(".nifti");
  itkExtensions.erase(".nia");
  itkExtensions.erase(".nii");
  itkExtensions.erase(".nii.gz");
  itkExtensions.erase(".hdr");
  itkExtensions.erase(".img");
  itkExtensions.erase(".img.gz");
  fileFilters << "ITK Image Data (";
  bool iefirst = true;
  for (const auto& ext : itkExtensions)
  {
    fileFilters << (iefirst ? "*" : " *") << ext;
    iefirst = false;
  }
  fileFilters << ");;";

  // Add extensions SMTK knows VTK can do.
  auto vtkFormats = Import::supportedVTKFileFormats();
  for (const auto& format : vtkFormats)
  {
    std::set<std::string> exts = format.second;
    exts.erase(".med");
    if (exts.empty())
    {
      continue;
    }
    fileFilters << format.first << " (";
    bool vefirst = true;
    for (const auto& ext : exts)
    {
      fileFilters << (vefirst ? "*." : " *.") << ext;
      vefirst = false;
    }
    fileFilters << ");;";
  }

  fileFilters << "All files (*.*)";
  fileItemDefinitions[0]->setFileFilters(fileFilters.str());
  return spec;
}

const char* Import::xmlDescription() const
{
  return Import_xml;
}

vtkSmartPointer<vtkImageData> Import::readVTKImage(const std::string& filename)
{
  vtkSmartPointer<vtkImageData> image;
  // For now, load a surrogate VTK image
  vtkNew<vtkXMLImageDataReader> reader;
  reader->SetFileName(filename.c_str());
  reader->Update();
  image = reader->GetOutput();
  if (image)
  {
    auto* scalars = image->GetPointData()->GetScalars();
    if (scalars)
    {
      auto* lkup = scalars->GetLookupTable();
      if (!lkup)
      {
        vtkNew<vtkLookupTable> greyscale;
        greyscale->SetSaturationRange(0, 0);
        greyscale->SetValueRange(0, 1);
        greyscale->Build();
        scalars->SetLookupTable(greyscale);
        lkup = greyscale;
      }
      lkup->SetTableRange(scalars->GetRange());
    }
  }
  return image;
}

Import::Result Import::importVTKImage(const Resource::Ptr& resource)
{
  auto const& session = resource->session();
  smtk::attribute::FileItem::Ptr filenameItem = this->parameters()->findFile("filename");
  smtk::attribute::StringItem::Ptr labelItem = this->parameters()->findString("label map");
  std::string filename = filenameItem->value();

  vtkSmartPointer<vtkImageData> image = Import::readVTKImage(filename);
  if (!image)
  {
    smtkInfoMacro(this->log(), "Unable to read image from: " << filename << ".");
    return m_result;
  }

  auto model = resource->addModel(3, 3, filename);
  auto modelComp = model.component();
  modelComp->properties().get<std::string>()["aeva_datatype"] = "image";
  modelComp->properties().get<std::string>()["import_filename"] = filename;
  auto volume = resource->addVolume();
  model.addCell(volume);
  auto createdItems = m_result->findComponent("created");
  createdItems->appendValue(modelComp);
  createdItems->appendValue(volume.component());

  session->addStorage(volume.entity(), image);
  if (image->GetPointData()->GetScalars())
  {
    volume.setName(image->GetPointData()->GetScalars()->GetName());
  }
  operation::MarkGeometry(resource).markModified(volume.component());

  m_result->findInt("outcome")->setValue(0, static_cast<int>(Import::Outcome::SUCCEEDED));
  return m_result;
}

Import::Result Import::importITKImage(const Resource::Ptr& resource)
{
  auto const& session = resource->session();
  smtk::attribute::FileItem::Ptr filenameItem = this->parameters()->findFile("filename");
  smtk::attribute::StringItem::Ptr labelItem = this->parameters()->findString("label map");
  std::string filename = filenameItem->value();

  vtkSmartPointer<vtkImageData> image;

  // ITK code
  constexpr unsigned int Dimension = 3;

  using PixelType = unsigned short;
  using ImageType = itk::Image<PixelType, Dimension>;

  using ReaderType = itk::ImageFileReader<ImageType>;
  ReaderType::Pointer reader = ReaderType::New();
  if (!reader)
  {
    smtkInfoMacro(this->log(), "Unable to create reader for \"" << filename << "\".");
    return m_result;
  }
  reader->SetFileName(filename);
  reader->Update();

  ImageType::Pointer img = reader->GetOutput();
  if (!img)
  {
    smtkInfoMacro(this->log(), "Unable to read image from: " << filename << ".");
    return m_result;
  }

  // Hook into smtk resource after the image has been successfully loaded.
  // TODO use preserved UUIDS, if it exists, and call insertModel() insertVolume()
  auto model = resource->addModel(3, 3, filename);
  auto volume = resource->addVolume();
  model.addCell(volume);
  // tag the model with its aeva datatype, so Export can filter against it.
  // TODO maybe should be a typed enum.
  model.component()->properties().get<std::string>()["aeva_datatype"] = "image";
  model.component()->properties().get<std::string>()["import_filename"] = filename;
  auto createdItems = m_result->findComponent("created");
  createdItems->appendValue(model.component());
  createdItems->appendValue(volume.component());

  // Extract data from ITK image
  auto region = img->GetLargestPossibleRegion();
  auto size = region.GetSize();
  auto spacing = img->GetSpacing();
  auto origin = img->GetOrigin();
  auto directions = img->GetDirection();
  // models and images exported from Slicer need coord transform, RAS to LPS
  // TODO: converting the image from LPS to RAS is incorrect for VTK (which wants LPS)
  // but it aligns all the models with these images correctly.
  auto ras_lps_flip(directions);
  ras_lps_flip.SetIdentity();
  ras_lps_flip[0][0] = -1;
  ras_lps_flip[1][1] = -1;

  origin = ras_lps_flip * origin;

  // re-use matrix storage for calc.
  ras_lps_flip *= directions;
  auto directions_vnl = ras_lps_flip.GetVnlMatrix();

  // Set up image importer
  vtkSmartPointer<vtkImageImport> importer = vtkSmartPointer<vtkImageImport>::New();
  importer->SetWholeExtent(0,
    static_cast<int>(size[0]) - 1,
    0,
    static_cast<int>(size[1]) - 1,
    0,
    static_cast<int>(size[2]) - 1);
  importer->SetDataExtentToWholeExtent();
  importer->SetDataDirection(directions_vnl.data_block());
  importer->SetDataSpacing(spacing[0], spacing[1], spacing[2]);
  importer->SetDataOrigin(origin[0], origin[1], origin[2]);
  importer->SetDataScalarType(VTK_UNSIGNED_SHORT);
  importer->SetNumberOfScalarComponents(1);
  importer->SetImportVoidPointer(img->GetBufferPointer());
  importer->Update();

  image = vtkSmartPointer<vtkImageData>::New();
  image->DeepCopy(importer->GetOutput());
  auto* scalars = image->GetPointData()->GetScalars();
  if (scalars)
  {
    auto* lkup = scalars->GetLookupTable();
    if (!lkup)
    {
      vtkNew<vtkLookupTable> greyscale;
      greyscale->SetSaturationRange(0, 0);
      greyscale->SetValueRange(0, 1);
      greyscale->Build();
      scalars->SetLookupTable(greyscale);
      lkup = greyscale;
    }
    lkup->SetTableRange(scalars->GetRange());
  }

  session->addStorage(volume.entity(), image);
  if (image->GetPointData()->GetScalars())
  {
    volume.setName(image->GetPointData()->GetScalars()->GetName());
  }
  operation::MarkGeometry(resource).markModified(volume.component());

  m_result->findInt("outcome")->setValue(0, static_cast<int>(Import::Outcome::SUCCEEDED));
  return m_result;
}

Import::Result Import::importMedMesh(const Resource::Ptr& resource)
{
  auto const& session = resource->session();
  smtk::attribute::FileItem::Ptr filenameItem = this->parameters()->findFile("filename");
  smtk::attribute::StringItem::Ptr labelItem = this->parameters()->findString("label map");
  long pointIdOffset = 0;
  long maxPointId = 0;
  long cellIdOffset = 0;
  long maxCellId = 0;
  if (resource->properties().contains<long>("global point id offset"))
  {
    pointIdOffset = resource->properties().at<long>("global point id offset");
    // smtkInfoMacro(this->log(), "Found pt offset " << pointIdOffset );
  }
  if (resource->properties().contains<long>("global cell id offset"))
  {
    cellIdOffset = resource->properties().at<long>("global cell id offset");
    // smtkInfoMacro(this->log(), "Found cell offset " << cellIdOffset );
  }

  for (size_t j = 0; j < filenameItem->numberOfValues(); j++)
  {
    vtkSmartPointer<vtkMedReader> reader = vtkSmartPointer<vtkMedReader>::New();
    std::string filename = filenameItem->value(j);
    reader->SetFileName(filename.c_str());
    reader->Update();
    vtkSmartPointer<vtkMultiBlockDataSet> output =
      vtkMultiBlockDataSet::SafeDownCast(reader->GetOutput());
    if (!output || output->GetNumberOfBlocks() != 2)
    {
      smtkErrorMacro(this->log(), "Unable to read .med file from: " << filename << ".");
      return m_result;
    }
    vtkSmartPointer<vtkMultiBlockDataSet> geomOutput =
      vtkMultiBlockDataSet::SafeDownCast(output->GetBlock(0));
    vtkSmartPointer<vtkMultiBlockDataSet> groupOutput =
      vtkMultiBlockDataSet::SafeDownCast(output->GetBlock(1));
    if (!geomOutput || !geomOutput->GetNumberOfBlocks() || !groupOutput ||
      !groupOutput->GetNumberOfBlocks())
    {
      smtkErrorMacro(
        this->log(), "Unexpected format from .med file reader, from: " << filename << ".");
      return m_result;
    }

    auto stem = smtk::common::Paths::stem(filename);
    auto model = resource->addModel(3, 3, stem);
    auto modelComp = model.component();
    modelComp->properties().get<std::string>()["aeva_datatype"] = "poly";
    modelComp->properties().get<std::string>()["import_filename"] = filename;
    auto createdItems = m_result->findComponent("created");
    createdItems->appendValue(modelComp);
    CellEntities cells;
    // maintain a list of volumes and the global Ids they contain,
    // so surfaces can be matched to them.
    std::vector<std::pair<CellEntity, std::set<int> > > volumeIds;

    // loop through once and collect global ids from volumes.
    for (vtkIdType i = 0; i < geomOutput->GetNumberOfBlocks(); ++i)
    {
      const char* val = geomOutput->GetMetaData(i)->Get(vtkCompositeDataSet::NAME());
      std::string name = val ? std::string(val) : std::string();
      vtkUnstructuredGrid* mesh = vtkUnstructuredGrid::SafeDownCast(geomOutput->GetBlock(i));
      if (!mesh || !mesh->GetNumberOfCells())
      {
        continue;
      }
      CellEntity cell;
      vtkUnstructuredGrid* data = nullptr;
      auto cellType = mesh->GetCellType(0);
      if (cellType == VTK_TETRA || cellType == VTK_QUADRATIC_TETRA)
      {
        cell = resource->addVolume();
        Session::setPrimary(*cell.component(), true);
        data = mesh;
        size_t index = volumeIds.size();
        if (name.empty())
        {
          name = stem + " volume " + std::to_string(index);
        }
        cell.setName(name);
        // record all used volume IDs, and apply ID offset so they are unique
        Session::offsetGlobalIds(data, pointIdOffset, cellIdOffset, maxPointId, maxCellId);
        std::set<int> idSet;
        vtkIntArray* volNums = vtkIntArray::SafeDownCast(data->GetPointData()->GetGlobalIds());
        for (int i = 0; i < volNums->GetNumberOfValues(); ++i)
        {
          idSet.insert(volNums->GetValue(i));
        }
        volumeIds.emplace_back(cell, idSet);
        // smtkInfoMacro(this->log(), "vol pt ids " << volNums->GetNumberOfValues()
        //                                          << " min: " << int(volNums->GetRange()[0])
        //                                          << " max: " << int(volNums->GetRange()[1]));
        // volNums = vtkIntArray::SafeDownCast(data->GetCellData()->GetGlobalIds());
        // smtkInfoMacro(this->log(), "vol cl ids " << volNums->GetNumberOfValues()
        //                                          << " min: " << int(volNums->GetRange()[0])
        //                                          << " max: " << int(volNums->GetRange()[1]));
      }
    }
    // Loop through again, and connect surfaces to volumes using global ids.
    size_t volNum = 0;
    for (vtkIdType i = 0; i < geomOutput->GetNumberOfBlocks(); ++i)
    {
      const char* val = geomOutput->GetMetaData(i)->Get(vtkCompositeDataSet::NAME());
      std::string name = val ? std::string(val) : std::string();
      vtkUnstructuredGrid* mesh = vtkUnstructuredGrid::SafeDownCast(geomOutput->GetBlock(i));
      if (mesh == nullptr)
      {
        continue;
      }
      CellEntity cell;
      vtkUnstructuredGrid* data = nullptr;
      auto cellType = mesh->GetCellType(0);
      if (cellType == VTK_TETRA || cellType == VTK_QUADRATIC_TETRA)
      {
        // already handled above, retrieve.
        data = mesh;
        cell = volumeIds[volNum].first;
        ++volNum;
      }
      else if (cellType == VTK_TRIANGLE || cellType == VTK_QUADRATIC_TRIANGLE)
      {
        cell = resource->addFace();
        Session::setPrimary(*cell.component(), true);
        data = mesh;
        Session::offsetGlobalIds(data, pointIdOffset, cellIdOffset, maxPointId, maxCellId);

        size_t index = volumeIds.size();
        CellEntity vol;
        vtkIntArray* srfNums = vtkIntArray::SafeDownCast(data->GetPointData()->GetGlobalIds());
        for (size_t j = 0; j < volumeIds.size(); ++j)
        {
          auto& idSet = volumeIds[j].second;
          bool belongs = true;
          for (int i = 0; i < srfNums->GetNumberOfValues(); ++i)
          {
            if (idSet.count(srfNums->GetValue(i)) == 0)
            {
              // found a surface point ID not belonging to this volume, skip
              belongs = false;
              break;
            }
          }
          if (belongs)
          {
            // all surf IDs belong to the volume, too.
            vol = volumeIds[j].first;
            index = j;
            break;
          }
        }
        if (!vol.isValid())
        {
          // empty volume bounded by this valid surface
          vol = resource->addVolume();
          Session::setPrimary(*vol.component(), true);
          index = volumeIds.size();
          vol.setName(stem + " volume " + std::to_string(index));
          volumeIds.emplace_back(vol, std::set<int>());
        }
        if (name.empty())
        {
          name = stem + " surface " + std::to_string(index);
        }
        cell.setName(name);
        // Connect to each other, abd add the volume to the model.
        vol.addRawRelation(cell);
        cell.addRawRelation(vol);
        model.addCell(vol);
        // smtkInfoMacro(this->log(), "srf pt ids " << srfNums->GetNumberOfValues()
        //                                          << " min: " << int(srfNums->GetRange()[0])
        //                                          << " max: " << int(srfNums->GetRange()[1]));
        // srfNums = vtkIntArray::SafeDownCast(data->GetCellData()->GetGlobalIds());
        // smtkInfoMacro(this->log(), "srf cl ids " << srfNums->GetNumberOfValues()
        //                                          << " min: " << int(srfNums->GetRange()[0])
        //                                          << " max: " << int(srfNums->GetRange()[1]));
      }
      else
      {
        smtkWarningMacro(
          this->log(), "Unhandled med cell type, not triangle or tetra: " << mesh->GetCellType(0));
      }

      if (data)
      {
        createdItems->appendValue(cell.component());
        session->addStorage(cell.entity(), data);
        operation::MarkGeometry(resource).markModified(cell.component());
      }
    }
    // handle the groups/side-sets, which are pieces of the geometry output.
    for (vtkIdType i = 0; i < groupOutput->GetNumberOfBlocks(); ++i)
    {
      const char* val = groupOutput->GetMetaData(i)->Get(vtkCompositeDataSet::NAME());
      std::string name = val ? std::string(val) : std::string();
      vtkUnstructuredGrid* mesh = vtkUnstructuredGrid::SafeDownCast(groupOutput->GetBlock(i));
      if (mesh == nullptr)
      {
        continue;
      }
      CellEntity cell;
      vtkDataObject* data = nullptr;
      auto cellType = mesh->GetCellType(0);
      if (cellType == VTK_TETRA || cellType == VTK_QUADRATIC_TETRA)
      {
        cell = resource->addVolume();
        data = mesh;
      }
      else if (cellType == VTK_TRIANGLE || cellType == VTK_QUADRATIC_TRIANGLE)
      {
        cell = resource->addFace();
        data = mesh;
      }
      else if (cellType == VTK_VERTEX)
      {
        cell = resource->addVertex();
        data = mesh;
      }
      else
      {
        // TODO ignore point elements for now.
        smtkWarningMacro(
          this->log(), "Unhandled med cell type, not triangle or tetra: " << mesh->GetCellType(0));
      }

      if (data)
      {
        Session::offsetGlobalIds(mesh, pointIdOffset, cellIdOffset, maxPointId, maxCellId);
        model.addCell(cell);
        // mark the element as being a side-set.
        Session::setPrimary(*cell.component(), false);
        cell.setName(name);

        createdItems->appendValue(cell.component());
        session->addStorage(cell.entity(), data);
        operation::MarkGeometry(resource).markModified(cell.component());
        // DEBUG
        // vtkIntArray* srfNums = vtkIntArray::SafeDownCast(
        //   vtkUnstructuredGrid::SafeDownCast(data)->GetCellData()->GetGlobalIds());
        // if (srfNums)
        // {
        //   smtkInfoMacro(this->log(), "srf id checked "
        //       << name << ": " << srfNums->GetNumberOfValues() << " min: "
        //       << int(srfNums->GetRange()[0]) << " max: " << int(srfNums->GetRange()[1]));
        // }
        // else
        // {
        //   smtkInfoMacro(this->log(), "missing ids " << name);
        // }
      }
    }
    // update the ID offsets per-file
    pointIdOffset = maxPointId + 1;
    cellIdOffset = maxCellId + 1;
  }
  // update the resource ID offsets.
  resource->properties().get<long>()["global point id offset"] = pointIdOffset;
  resource->properties().get<long>()["global cell id offset"] = cellIdOffset;

  m_result->findInt("outcome")->setValue(0, static_cast<int>(Import::Outcome::SUCCEEDED));
  return m_result;
}

Import::Result Import::importVTKMesh(const smtk::session::aeva::Resource::Ptr& resource)
{
  long pointIdOffset = 0;
  long maxPointId = 0;
  long cellIdOffset = 0;
  long maxCellId = 0;
  if (resource->properties().contains<long>("global point id offset"))
  {
    pointIdOffset = resource->properties().at<long>("global point id offset");
  }
  if (resource->properties().contains<long>("global cell id offset"))
  {
    cellIdOffset = resource->properties().at<long>("global cell id offset");
  }

  smtk::attribute::FileItem::Ptr filenameItem = this->parameters()->findFile("filename");
  smtk::extension::vtk::io::ImportAsVTKData vtkImporter;
  auto createdItems = m_result->findComponent("created");
  operation::MarkGeometry marker(resource);
  for (std::size_t ii = 0; ii < filenameItem->numberOfValues(); ++ii)
  {
    std::string filename = filenameItem->value(ii);
    auto data = vtkImporter(filename);
    if (data)
    {
      auto stem = smtk::common::Paths::stem(filename);
      auto model = resource->addModel(3, 3, stem);
      auto modelComp = model.component();
      modelComp->properties().get<std::string>()["aeva_datatype"] = "poly";
      modelComp->properties().get<std::string>()["filename"] = filename;
      modelComp->properties().get<std::string>()["import_filename"] = filename;
      createdItems->appendValue(modelComp);
      int count = 0;
      auto cell = createCellForData(model,
        data,
        createdItems,
        count,
        marker,
        pointIdOffset,
        cellIdOffset,
        maxPointId,
        maxCellId);
      // Volume cells should have surface faces:
      if (cell.isVolume())
      {
        vtkNew<vtkGeometryFilter> boundaryFilter;
        boundaryFilter->SetInputDataObject(0, data);
        boundaryFilter->Update();
        auto bdy = createCellForData(cell,
          boundaryFilter->GetOutputDataObject(0),
          createdItems,
          count,
          marker,
          pointIdOffset,
          cellIdOffset,
          maxPointId,
          maxCellId);
      }
      // update the ID offsets per-file
      pointIdOffset = maxPointId + 1;
      cellIdOffset = maxCellId + 1;
    }
  }

  // update the resource ID offsets.
  resource->properties().get<long>()["global point id offset"] = pointIdOffset;
  resource->properties().get<long>()["global cell id offset"] = cellIdOffset;

  m_result->findInt("outcome")->setValue(0, static_cast<int>(Import::Outcome::SUCCEEDED));
  return m_result;
}

std::set<std::string> Import::supportedITKFileFormats()
{
  std::set<std::string> result;

  std::list<itk::LightObject::Pointer> allobjects =
    itk::ObjectFactoryBase::CreateAllInstance("itkImageIOBase");
  for (auto& obj : allobjects)
  {
    auto* io = dynamic_cast<itk::ImageIOBase*>(obj.GetPointer());
    auto extensions = io->GetSupportedReadExtensions();
    result.insert(extensions.begin(), extensions.end());
  }
  return result;
}

std::map<std::string, std::set<std::string> > Import::supportedVTKFileFormats()
{
  std::map<std::string, std::set<std::string> > result;
  smtk::extension::vtk::io::ImportAsVTKData vtkImporter;
  auto formats = vtkImporter.fileFormats();
  for (const auto& format : formats)
  {
    std::set<std::string> extensions(format.Extensions.begin(), format.Extensions.end());
    result[format.Name] = extensions;
  }
  return result;
}

smtk::resource::ResourcePtr importResource(const std::string& filename)
{
  Import::Ptr importResource = Import::create();
  importResource->parameters()->findFile("filename")->setValue(filename);
  Import::Result result = importResource->operate();
  if (result->findInt("outcome")->value() != static_cast<int>(Import::Outcome::SUCCEEDED))
  {
    return smtk::resource::ResourcePtr();
  }
  return result->findResource("resource")->value();
}
}
}
}
