//=========================================================================
//  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.
//=========================================================================
#ifndef smtk_session_aeva_Session_h
#define smtk_session_aeva_Session_h

#include "smtk/common/UUID.h"
#include "smtk/model/EntityRef.h"
#include "smtk/model/Session.h"
#include "smtk/session/aeva/Exports.h"

#include "itkImage.h"
#include "itkImageIOBase.h"

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

#include "vtkDataObject.h"
#include "vtkSmartPointer.h"

class vtkDataSet;
class vtkIntArray;

namespace smtk
{
namespace model
{
class Entity;
}
namespace session
{
namespace aeva
{

class Resource;
class Session;
typedef smtk::shared_ptr<Session> SessionPtr;

/**\brief Implement a session from AEVA mesh files to SMTK.
  *
  */
class SMTKAEVASESSION_EXPORT Session : public smtk::model::Session
{
public:
  smtkTypeMacro(Session);
  smtkSuperclassMacro(smtk::model::Session);
  smtkSharedFromThisMacro(smtk::model::Session);
  smtkCreateMacro(smtk::model::Session);
  using SessionInfoBits = smtk::model::SessionInfoBits;

  Session(const Session&) = delete;
  Session& operator=(const Session&) = delete;

  using PixelType = unsigned char;
  static constexpr unsigned int Dimension = 3;
  using ImageType = itk::Image<PixelType, Dimension>;
  using DataEntry = std::pair<smtk::common::UUID, vtkSmartPointer<vtkDataObject> >;

  ~Session() override = default;

  SessionInfoBits allSupportedInformation() const override
  {
    return smtk::model::SESSION_EVERYTHING;
  }

  std::string defaultFileExtension(const smtk::model::Model& model) const override;

  // TODO: Generalize to other image types:
  vtkSmartPointer<vtkDataObject> findStorage(const smtk::common::UUID& uid) const;
  void addStorage(const smtk::common::UUID& uid, vtkSmartPointer<vtkDataObject> const& storage);
  bool removeStorage(const smtk::common::UUID& uid);

  /// Given VTK data with global IDs on \a fieldType, find the set of primary-geometry components.
  bool primaryGeometries(const vtkSmartPointer<vtkDataObject>& data,
    int fieldType, // one of vtkDataObject::AttributeTypes
    std::set<smtk::model::Entity*>& primaries,
    smtk::resource::Resource* resource) const;

  /// Given a component as input, return its "source" primary geometric component.
  ///
  /// This may be the component itself or a separate component.
  /// It is also possible a side set spans multiple primary geometries, in
  /// which case this function will return a nullptr.
  bool primaryGeometries(const smtk::model::Entity* in,
    std::set<smtk::model::Entity*>& primaries) const;

  /// Given a component as input, return its "source" primary geometric component.
  ///
  /// This may be the component itself or a separate component.
  /// It is also possible a side set spans multiple primary geometries, in
  /// which case this function will return a nullptr.
  smtk::model::Entity* primaryGeometry(const smtk::model::Entity* in) const;

  /// Return the primary geometry that "owns" the given global cell/point ID.
  smtk::common::UUID primaryGeometryForCell(vtkIdType globalId) const;
  smtk::common::UUID primaryGeometryForPoint(vtkIdType globalId) const;

  /// Return a list of side sets that reference the given primary geometry.
  bool sideSets(const smtk::model::Entity* in, std::set<smtk::model::Entity*>& sideSets) const;

  /// Return a map that can be used to quickly look up primary geometry from a global ID.
  std::map<vtkIdType, DataEntry> buildGlobalIdLookup(const std::shared_ptr<Resource>& resource,
    vtkDataObject::AttributeTypes centering) const;

  /// The signature of functions used to visit points and cells by their global ID.
  ///
  /// The arguments passed to the function (in order) are:
  /// + the global ID of the point or cell being visited.
  /// + the global ID of the element generating the visitation;
  ///   for instance, when requesting cells whose points are in
  ///   a set of global IDs, the first argument is the cell ID
  ///   while the second argument is one of the points in the
  ///   set of input global point-IDs that caused the cell to be
  ///   visited.
  /// + the primary-geometry data-object owning the point or cell,
  /// + the cell type (or VTK_VERTEX when visiting points)
  /// + the number of points in the cell (or 1 when visiting points)
  /// + the connectivity of the cell being visited (or a pointer to
  ///   the index of the point with ID pointId when visiting points).
  ///
  /// The return value of the function should be either
  /// + vtkVisitation::STOP to indicate iteration should halt, or
  /// + vtkVisitation::CONTINUE to indicate iteration should proceed.
  using ElementVisitor = std::function<
    vtkVisitation(vtkIdType, vtkIdType, vtkDataObject*, int, vtkIdType, vtkIdType const*)>;

  /// Visit primary-geometry cells/points by global ID.
  ///
  /// The \a context determines whether point- or cell-global IDs
  /// are provided *and* whether points or cells of primary geometry
  /// should be visited.
  ///
  /// The return value indicates whether iteration was halted early
  /// (vtkVisitation::STOP) or ran to completion (vtkVisitation::CONTINUE).
  vtkVisitation visitPrimaryDataById(const std::shared_ptr<Resource>& resource,
    const std::set<vtkIdType>& ids,
    vtkVisitationRule context,
    const ElementVisitor& visitor) const;

  static void offsetGlobalIds(vtkDataSet* data,
    long pointIdOffset,
    long cellIdOffset,
    long& maxPointId,
    long& maxCellId);

  static void setPrimary(smtk::resource::Component& c, bool isPrimary);
  static bool isPrimary(const smtk::resource::Component& c);
  static bool isSideSet(const smtk::resource::Component& c) { return !Session::isPrimary(c); }

  /// A utility method to fetch global point or cell IDs from data in a reliable way.
  static vtkIntArray* globalIds(vtkDataObject* data,
    vtkDataObject::AttributeTypes centering = vtkDataObject::CELL);

protected:
  Session() = default;

  SessionInfoBits transcribeInternal(const smtk::model::EntityRef& entity,
    SessionInfoBits requestedInfo,
    int depth = -1) override;

  smtk::model::SessionIOPtr createIODelegate(const std::string& format) override;

  std::map<smtk::common::UUID, vtkSmartPointer<vtkDataObject> > m_storage;
};

} // namespace aeva
} // namespace session
} // namespace smtk

#endif // smtk_session_aeva_Session_h
