#pragma once

#ifndef PLASTIDEFORMERSTORAGE_H
#define PLASTIDEFORMERSTORAGE_H

#include <memory>

// TnzExt includes
#include "ext/plasticdeformer.h"

#undef DVAPI
#undef DVVAR
#ifdef TNZEXT_EXPORTS
#define DVAPI DV_EXPORT_API
#define DVVAR DV_EXPORT_VAR
#else
#define DVAPI DV_IMPORT_API
#define DVVAR DV_IMPORT_VAR
#endif

//=======================================================================

//    Forward Declarations

struct PlasticVisualSettings;
struct DrawableMeshImage;

class PlasticSkeletonDeformation;

//=======================================================================

//***********************************************************************************************
//    PlasticDeformerData  declaration
//***********************************************************************************************

//! PlasticDeformerData contains all the data needed to perform the deformation
//! of a single mesh.
struct DVAPI PlasticDeformerData {
  PlasticDeformer m_deformer;  //!< The mesh deformer itself

  std::unique_ptr<double[]> m_so;      //!< (owned) Faces' stacking order
  std::unique_ptr<double[]> m_output;  //!< (owned) Output vertex coordinates

  std::vector<int> m_faceHints;  //!< Handles' face hints

public:
  PlasticDeformerData();
  ~PlasticDeformerData();

private:
  // Not copyable

  PlasticDeformerData(const PlasticDeformerData &);
  PlasticDeformerData &operator=(const PlasticDeformerData &);
};

//***********************************************************************************************
//    PlasticDeformerDataGroup  definition
//***********************************************************************************************

struct DVAPI PlasticDeformerDataGroup {
  std::unique_ptr<PlasticDeformerData[]>
      m_datas;  //!< (owned) The deformer datas array. One per mesh.
  std::vector<PlasticHandle>
      m_handles;  //!< Source handles (emanated from skeleton vertices).
  std::vector<TPointD>
      m_dstHandles;  //!< Corresponding destination handle positions

  int m_compiled;  //!< Whether compiled data is present about a certain
                   //! datatype.
  int m_upToDate;  //!< Whether updated  data is present about a certain
                   //! datatype.

  double m_outputFrame;  //!< The frame of current output values.
  //!< Value numeric_limits::max can be used to invalidate
  //!< deformation outputs
  TAffine m_skeletonAffine;  //!< The skeleton affine applied to each handle.
  //!< In case it changes, the deformation is automatically recompiled.

  double m_soMin,
      m_soMax;  //!< SO min and max across the whole group (useful while drawing
                //!< due to the unboundedness of the SO parameter)
  std::vector<std::pair<int, int>>
      m_sortedFaces;  //!< Pairs of face-mesh indices, by sorted stacking order.
  //!< This is the order that must be followed when drawing faces.
public:
  PlasticDeformerDataGroup();
  ~PlasticDeformerDataGroup();

private:
  // Not copyable

  PlasticDeformerDataGroup(const PlasticDeformerDataGroup &);
  PlasticDeformerDataGroup &operator=(const PlasticDeformerDataGroup &);
};

//***********************************************************************************************
//    PlasticDeformerStorage  declaration
//***********************************************************************************************

//! PlasticDeformerStorage is the global storage class for plastic deformers.
/*!
  PlasticDeformer models a mesh-deformer object that can be used together with
  texturing to create an interactive image-deformation technique. Direct use
  of PlasticDeformer instances is discouraged, as they can be considered as
  technical low-level interfaces to the deformation job.

\par Rationale

  The crucial reason behind the existence of this class lies in the
  necessity to cache PlasticDeformer instances to achieve real-time speed in
  the processing of part of its algorithms.
  \n\n
  In this context, caching is all about storing intermediate data so that they
  are recalculated only when strictly necessary.
  In PlasticDeformer's case, we can see 3 chained processing stages that need
  to be calculated before the final result is produced:

  \li <B> Initialization: <\B> A mesh is acquired
  \li <B> Compilation: <\B> The deformation's source domain data is acquired
  \li <B> Deformation: <\B> The deformation's destination domain data is
acquired

  Each step requires that the previous one has been computed. Obviously when,
  say, only the deformation's destination data change, there is no need to
process
  the first 2 steps - their cached data remains valid.

\par Interface

  This class provides the preferential high-level interface to plastic
deformers.
  \n\n
  The storage caches data about every requested deform specification, that is
kept
  until an explicit release command is issued. A data request is issued with a
call
  to the process() method, which accepts specification of a subset of
deform-related
  data to be returned, skipping unnecessary computations.
  \n\n
  The storage requires explicit content invalidation to know that is must
  recalculate subsequent requests. Invalidation is specific to changes in a
  PlasticSkeletonDeformation instance, and can be restrained to the destination
  (deformed) domain, or require more expensive invalidation.

\par Deformations and Data requests

  Cached data is uniquely associated with pairs of meshImage and deformed
skeletons,
  meaning that the same meshImage can be deformed by multiple skeletons, and
vice versa.
  \n\n
  A deformer data request needs an affine transform in input, which is intended
to
  map the skeleton deformation on top of the mesh (in mesh coordinates). The
storage
  <I> does not <\I> store a PlasticDeformer instance for each different affine
supplied,
  but rather \a invalidates the old data in case it changes.
  \n\n
  The mesh vertex coordinates resulting from a deformation will be stored in the
resulting
  PlasticDeformerData::m_output member of each entry of the output array, one
for each mesh
  of the input meshImage. The returned coordinates are intended in the mesh
reference.

\par Lifetime and Ownership

  This class <B> does not claim ownerhip <\B> of neither the mesh nor the
deformation that are
  specified in a deformer request - and <I> lifetime tracking <\I> of the
objects involved in
  plastic deforming is a burden of callers.
  \n\n
  This class requires that the destruction of either a mesh or a deformation be
signaled by a
  call to the appropriate release method in order to enforce the associated
deformers destruction.

  \warning No call to the appropriate release method when either a mesh or a
deformation which
           has requested a deformer is destroyed \b will result in a \b leak.
*/

class DVAPI PlasticDeformerStorage {
  class Imp;
  std::unique_ptr<Imp> m_imp;

public:
  enum DataType {
    NONE    = 0x0,
    HANDLES = 0x1,  // Handles data
    SO      = 0x4,  // Stacking Order data
    MESH    = 0x8,  // The deformed Mesh

    ALL = HANDLES | SO | MESH
  };

public:
  PlasticDeformerStorage();
  ~PlasticDeformerStorage();

  static PlasticDeformerStorage *instance();

  //! This function processes the specified meshImage-deformation pair,
  //! returning a DataGroup
  //! with the required data.
  /*!
\note This function \b caches all required data, so that subsequent requests
about the same
triplet can be sped up considerably in case of cache match.
*/
  const PlasticDeformerDataGroup *process(
      double frame, const TMeshImage *meshImage,
      const PlasticSkeletonDeformation *deformation, int skeletonId,
      const TAffine &deformationToMeshAffine, DataType dataType = ALL);

  //! Performs the specified deformation once, \b without caching data.
  /*!
This method allows the user to perform a single-shot Platic deformation, without
dealing with
caching issues.

\note Since caching is disabled, this method is comparably \a slower than its
cached counterpart,
    in case the same deformation is repeatedly invoked.
    It is meant to be used only in absence of user interaction.

\warning The returned pointer is owned by the \b caller, and must be manually
deleted
       when no longer needed.
*/
  static const PlasticDeformerDataGroup *processOnce(
      double frame, const TMeshImage *meshImage,
      const PlasticSkeletonDeformation *deformation, int skeletonId,
      const TAffine &deformationToMeshAffine, DataType dataType = ALL);

  //! Similarly to invalidateSkeleton(), for every deformer attached to the
  //! specified mesh.
  void invalidateMeshImage(const TMeshImage *meshImage,
                           int recompiledDataType = NONE);

  //! Schedules all stored deformers associated to the specified deformation for
  //! either re-deformation (stage 3 invalidation) or re-compilation (stage 2
  //! invalidation).
  /*!
Recompilation should be selected whenever the deformation has sustained source
domain
changes, such as a vertex addition or removal, or when the \a source skeletal
configuration has changed.

\note Recompilation is typically a slower process than the mere deformers
update.
Select it explicitly only when it truly needs to be done.
*/
  void invalidateSkeleton(const PlasticSkeletonDeformation *deformation,
                          int skeletonId, int recompiledDataType = NONE);

  //! Similarly to invalidateSkeleton(), for everything attached to the
  //! specified deformation.
  void invalidateDeformation(const PlasticSkeletonDeformation *deformation,
                             int recompiledDataType = NONE);

  //! Releases all deformers associated to the specified mesh image.
  void releaseMeshData(const TMeshImage *meshImage);

  //! Releases all deformers associated to the specified skeletal deformation.
  void releaseSkeletonData(const PlasticSkeletonDeformation *deformation,
                           int skeletonId);

  //! Releases all deformers associated to the specified skeletal deformation.
  void releaseDeformationData(const PlasticSkeletonDeformation *deformation);

  //! Releases all deformers, effectively returning the storage to its empty
  //! state.
  void clear();

private:
  //! Retrieves the group of deformers (one per mesh in the image) associated to
  //! the input
  //! pair, eventually creating one if none did exist.
  PlasticDeformerDataGroup *deformerData(
      const TMeshImage *meshImage,
      const PlasticSkeletonDeformation *deformation, int skeletonId);
};

#endif  // PLASTIDEFORMERSTORAGE_H
