/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef __itkSpatialObject_h
#define __itkSpatialObject_h

// Disable warning for lengthy symbol names in this file only

#include "itkAffineGeometryFrame.h"
#include "itkCovariantVector.h"
#include "itkExceptionObject.h"
#include <list>
#include "itkSpatialObjectProperty.h"
#include "itkProcessObject.h"
#include "itkIndex.h"
#include "itkImageRegion.h"
#include "itkSpatialObjectTreeNode.h"

namespace itk
{
/**
 * \class SpatialObject
 * \brief Implementation of the composite pattern
 *
 * The purpose of this class is to implement the composite pattern [Design
 * Patterns, Gamma, 1995] within itk, so that it becomes easy to create an
 * environment containing objects within a scene, and to manipulate the
 * environment as a whole or any of its component objects.  An
 * object has a list of transformations to transform index coordinates
 * to the corresponding coordinates in the real world coordinate
 * system, and a list of inverse transformation to go backward.  Any
 * spatial objects can be plugged to a spatial object as children.  To
 * implement your own spatial object, you need to derive from the
 * following class, which requires the definition of just a few pure
 * virtual functions.  Examples of such functions are ValueAt(),
 * IsEvaluableAt(), and IsInside(), each of which has a meaning
 * specific to each particular object type.
 * \ingroup ITKSpatialObjects
 */

template< unsigned int VDimension >
class SpatialObjectTreeNode;

template< unsigned int VDimension = 3 >
class SpatialObject:
  public DataObject
{
public:

  typedef double ScalarType;

  itkStaticConstMacro(MaximumDepth, unsigned int, 9999999);

  /** Return the maximum depth that a tree of spatial objects can
   * have.  This provides convenient access to a static constant. */
  unsigned int GetMaximumDepth() const { return MaximumDepth; }

  typedef SpatialObject< VDimension > Self;
  typedef DataObject                  Superclass;

  typedef SmartPointer< Self >       Pointer;
  typedef SmartPointer< const Self > ConstPointer;

  typedef Point< ScalarType, VDimension > PointType;
  // Spatial Function Iterator needs the following typedef
  typedef Point< ScalarType, VDimension > InputType;
  typedef PointType *                     PointPointer;

  typedef Vector< ScalarType, VDimension >          VectorType;
  typedef CovariantVector< ScalarType, VDimension > CovariantVectorType;
  typedef VectorType *                              VectorPointer;

  typedef double *SpacingType;

  typedef CovariantVector< double, VDimension > OutputVectorType;
  typedef OutputVectorType *                    OutputVectorPointer;

  typedef ScalableAffineTransform< double, VDimension > TransformType;
  typedef typename TransformType::Pointer               TransformPointer;
  typedef const TransformType *                         TransformConstPointer;

  typedef VectorContainer< IdentifierType, PointType >  VectorContainerType;

  typedef BoundingBox< IdentifierType, VDimension, ScalarType, VectorContainerType > BoundingBoxType;
  typedef typename BoundingBoxType::Pointer                                          BoundingBoxPointer;

  typedef AffineGeometryFrame< double, VDimension > AffineGeometryFrameType;
  typedef typename AffineGeometryFrameType::Pointer AffineGeometryFramePointer;

  /** Return type for the list of children */
  typedef std::list< Pointer > ChildrenListType;
  typedef ChildrenListType *   ChildrenListPointer;

  /** Index typedef support. An index is used to access pixel values. */
  typedef Index< VDimension >                IndexType;

  /** Offset typedef support. An offset represent relative position
   * between indices. */
  typedef Offset< VDimension >                 OffsetType;
  typedef ImageRegion< VDimension >            RegionType;
  typedef Size< VDimension >                   SizeType;
  typedef SpatialObjectProperty< float >       PropertyType;
  typedef typename PropertyType::Pointer       PropertyPointer;

  typedef SpatialObjectTreeNode< VDimension > TreeNodeType;

  /** Return true if the object has a parent object. Basically, only
   *  the root object , or some isolated objects should return false. */
  virtual bool HasParent(void) const;

  /** Get the typename of the SpatialObject */
  virtual const char * GetTypeName(void) const { return m_TypeName.c_str(); }

  /** Dimension of the object.  This constant is used by functions that are
   * templated over SpatialObject type when they need compile time access
   * to the dimension of the object. */
  itkStaticConstMacro(ObjectDimension, unsigned int, VDimension);

  /** Get the dimensionality of the object */
  unsigned int GetObjectDimension(void) const { return VDimension; }

  /** Method for creation through the object factory. */
  itkNewMacro(Self);

  /** Run-time type information (and related methods). */
  itkTypeMacro(SpatialObject, DataObject);

  /** Set/Get the AffineGeometryFrame */
  itkSetObjectMacro(AffineGeometryFrame, AffineGeometryFrameType);
  itkGetModifiableObjectMacro(AffineGeometryFrame, AffineGeometryFrameType);

  /** This defines the transformation from the global coordinate frame.
   *  By setting this transform, the local transform is computed */
  void SetObjectToWorldTransform(TransformType *transform);
  itkGetModifiableObjectMacro(ObjectToWorldTransform, TransformType);

  itkGetModifiableObjectMacro(IndexToWorldTransform, TransformType);

  /** Compute the World transform when the local transform is set
   *  This function should be called each time the local transform
   *  has been modified */
  void ComputeObjectToWorldTransform(void);

  /** Compute the Local transform when the global transform is set */
  void ComputeObjectToParentTransform(void);

  /** Return the Modified time of the LocalToWorldTransform */
  unsigned long GetTransformMTime(void);

  /** Return the Modified time of the WorldToLocalTransform */
  unsigned long GetWorldTransformMTime(void);

  /** Returns the value at a point */
  virtual bool ValueAt(const PointType & point, double & value,
                       unsigned int depth = 0,
                       char *name = ITK_NULLPTR) const;

  /** Returns true if the object can provide a "meaningful" value at
   * a point.   Often defaults to returning same answer as IsInside, but
   * certain objects influence space beyond their spatial extent,
   * e.g., an RFA Needle Spatial Object can cause a burn
   * that extends beyond the tip of the needle.
   */
  virtual bool IsEvaluableAt(const PointType & point,
                             unsigned int depth = 0,
                             char *name = ITK_NULLPTR) const;

  /** Returns true if a point is inside the object. */
  virtual bool IsInside(const PointType & point,
                        unsigned int depth = 0,
                        char *name = ITK_NULLPTR) const;

  /** Returns true if a point is inside the object - provided
   * to make spatial objects compatible with spatial functions
   * and conditional iterators for defining regions of interest.
   */
  bool Evaluate(const PointType & point) const
  {
    return this->IsInside(point);
  }

  /** Return the n-th order derivative value at the specified point. */
  virtual void DerivativeAt(const PointType & point,
                            short unsigned int order,
                            OutputVectorType & value,
                            unsigned int depth = 0,
                            char *name = ITK_NULLPTR);

  /** Returns the latest modified time of the spatial object, and
   * any of its components. */
  virtual ModifiedTimeType GetMTime(void) const ITK_OVERRIDE;

  /** Returns the latest modified time of the spatial object, but not
   *  the modification time of the children */
  unsigned long GetObjectMTime(void) const
  {
    return Superclass::GetMTime();
  }

  /** Set the region object that defines the size and starting index
   * for the largest possible region this image could represent.  This
   * is used in determining how much memory would be needed to load an
   * entire dataset.  It is also used to determine boundary
   * conditions.
   * \sa ImageRegion, SetBufferedRegion(), SetRequestedRegion() */
  virtual void SetLargestPossibleRegion(const RegionType & region);

  /** Get the region object that defines the size and starting index
   * for the largest possible region this image could represent.  This
   * is used in determining how much memory would be needed to load an
   * entire dataset.  It is also used to determine boundary
   * conditions.
   * \sa ImageRegion, GetBufferedRegion(), GetRequestedRegion() */
  virtual const RegionType & GetLargestPossibleRegion() const
  { return m_LargestPossibleRegion; }

  /** Set the region object that defines the size and starting index
   * of the region of the image currently loaded in memory.
   * \sa ImageRegion, SetLargestPossibleRegion(), SetRequestedRegion() */
  virtual void SetBufferedRegion(const RegionType & region);

  /** Get the region object that defines the size and starting index
   * of the region of the image currently loaded in memory.
   * \sa ImageRegion, SetLargestPossibleRegion(), SetRequestedRegion() */
  virtual const RegionType & GetBufferedRegion() const
  { return m_BufferedRegion; }

  /** Set the region object that defines the size and starting index
   * for the region of the image requested (i.e., the region of the
   * image to be operated on by a filter).
   * \sa ImageRegion, SetLargestPossibleRegion(), SetBufferedRegion() */
  virtual void SetRequestedRegion(const RegionType & region);

  /** Set the requested region from this data object to match the requested
   * region of the data object passed in as a parameter.  This method
   * implements the API from DataObject. The data object parameter must be
   * castable to an ImageBase. */
  virtual void SetRequestedRegion(const DataObject *data) ITK_OVERRIDE;

  /** Get the region object that defines the size and starting index
   * for the region of the image requested (i.e., the region of the
   * image to be operated on by a filter).
   * \sa ImageRegion, SetLargestPossibleRegion(), SetBufferedRegion() */
  virtual const RegionType & GetRequestedRegion() const
  { return m_RequestedRegion; }

  /** Get the offset table.  The offset table gives increments for
   * moving from one pixel to next in the current row, column, slice,
   * etc..  This table if of size [VImageDimension+1], because its
   * values are computed progressively as: {1, N1, N1*N2,
   * N1*N2*N3,...,(N1*...*Nn)} Where the values {N1,...,Nn} are the
   * elements of the BufferedRegion::Size array.  The last element of
   * the OffsetTable is equivalent to the BufferSize.  Having a
   * [VImageDimension+1] size array, simplifies the implementation of
   * some data accessing algorithms. */
  const OffsetValueType * GetOffsetTable() const { return m_OffsetTable; }

  /** Compute an offset from the beginning of the buffer for a pixel
   * at the specified index. */
  OffsetValueType ComputeOffset(const IndexType & ind) const
  {
    // need to add bounds checking for the region/buffer?
    OffsetValueType   offset = 0;
    const IndexType & bufferedRegionIndex = m_BufferedRegion.GetIndex();

    // data is arranged as [][][][slice][row][col]
    // with Index[0] = col, Index[1] = row, Index[2] = slice
    for ( int i = VDimension - 1; i > 0; i-- )
      {
      offset += ( ind[i] - bufferedRegionIndex[i] ) * m_OffsetTable[i];
      }
    offset += ( ind[0] - bufferedRegionIndex[0] );

    return offset;
  }

  /** Compute the index of the pixel at a specified offset from the
   * beginning of the buffered region. */
  IndexType ComputeIndex(OffsetValueType offset) const
  {
    IndexType         index;
    const IndexType & bufferedRegionIndex = m_BufferedRegion.GetIndex();

    for ( int i = VDimension - 1; i > 0; i-- )
      {
      index[i] = static_cast< IndexValueType >( offset / m_OffsetTable[i] );
      offset -= ( index[i] * m_OffsetTable[i] );
      index[i] += bufferedRegionIndex[i];
      }
    index[0] = bufferedRegionIndex[0] + static_cast< IndexValueType >( offset );

    return index;
  }

  /** Copy information from the specified data set.  This method is
   * part of the pipeline execution model. By default, a ProcessObject
   * will copy meta-data from the first input to all of its
   * outputs. See ProcessObject::GenerateOutputInformation().  Each
   * subclass of DataObject is responsible for being able to copy
   * whatever meta-data it needs from from another DataObject.
   * ImageBase has more meta-data than its DataObject.  Thus, it must
   * provide its own version of CopyInformation() in order to copy the
   * LargestPossibleRegion from the input parameter. */
  virtual void CopyInformation(const DataObject *data) ITK_OVERRIDE;

  /** Update the information for this DataObject so that it can be used
   * as an output of a ProcessObject.  This method is used the pipeline
   * mechanism to propagate information and initialize the meta data
   * associated with a DataObject. This method calls its source's
   * ProcessObject::UpdateOutputInformation() which determines modified
   * times, LargestPossibleRegions, and any extra meta data like spacing,
   * origin, etc. */
  virtual void UpdateOutputInformation() ITK_OVERRIDE;

  /** Set the RequestedRegion to the LargestPossibleRegion.  This
   * forces a filter to produce all of the output in one execution
   * (i.e. not streaming) on the next call to Update(). */
  virtual void SetRequestedRegionToLargestPossibleRegion() ITK_OVERRIDE;

  /** Determine whether the RequestedRegion is outside of the
   * BufferedRegion. This method returns true if the RequestedRegion
   * is outside the BufferedRegion (true if at least one pixel is
   * outside). This is used by the pipeline mechanism to determine
   * whether a filter needs to re-execute in order to satisfy the
   * current request.  If the current RequestedRegion is already
   * inside the BufferedRegion from the previous execution (and the
   * current filter is up to date), then a given filter does not need
   * to re-execute */
  virtual bool RequestedRegionIsOutsideOfTheBufferedRegion() ITK_OVERRIDE;

  /** Verify that the RequestedRegion is within the
   * LargestPossibleRegion.  If the RequestedRegion is not within the
   * LargestPossibleRegion, then the filter cannot possible satisfy
   * the request. This method returns true if the request can be
   * satisfied and returns fails if the request cannot. This method is
   * used by PropagateRequestedRegion().  PropagateRequestedRegion()
   * throws a InvalidRequestedRegionError exception is the requested
   * region is not within the LargestPossibleRegion. */
  virtual bool VerifyRequestedRegion() ITK_OVERRIDE;

  /** Returns a pointer to the property object applied to this class. */
  PropertyType * GetProperty(void);

  const PropertyType * GetProperty(void) const { return m_Property; }

  /** Set the property applied to the object. */
  void SetProperty(PropertyType *property);

  /** Get/Set the ID */
  itkGetConstReferenceMacro(Id, int);
  itkSetMacro(Id, int);

  /** Set/Get the parent Identification number */
  itkSetMacro(ParentId, int);
  itkGetConstReferenceMacro(ParentId, int);

  /** Specify that the object has been updated */
  virtual void Update(void) ITK_OVERRIDE;

  /** Set the tree container */
  itkSetObjectMacro(TreeNode, TreeNodeType)

  /** Return a raw pointer to the node container */
  itkGetModifiableObjectMacro(TreeNode, TreeNodeType);

  /** Theses functions are just calling the AffineGeometryFrame functions */
  /** Set the spacing of the spatial object. */
  void SetSpacing(const double spacing[itkGetStaticConstMacro(ObjectDimension)])
  {
  m_AffineGeometryFrame->GetModifiableIndexToObjectTransform()->SetScale(spacing);
  this->Modified();
  }
  /** Get the spacing of the spatial object. */
  virtual const double * GetSpacing() const
  {
  return this->GetIndexToObjectTransform()->GetScale();
  }

  /** Transform points from the internal data coordinate system
   * of the object (typically the indices of the image from which
   * the object was defined) to "physical" space (which accounts
   * for the spacing, orientation, and offset of the indices)
   */
  const TransformType * GetIndexToObjectTransform(void) const;

  TransformType * GetModifiableIndexToObjectTransform(void)
    {
    return m_AffineGeometryFrame->GetModifiableIndexToObjectTransform();
    }
  TransformType * GetIndexToObjectTransform(void)
    {
    return m_AffineGeometryFrame->GetModifiableIndexToObjectTransform();
    }


  /** Transforms points from the object-specific "physical" space
   * to the "physical" space of its parent object.
   */
  void SetObjectToParentTransform(TransformType *transform);

  TransformType * GetObjectToParentTransform(void);

  const TransformType * GetObjectToParentTransform(void) const;

  /** Transforms points from the object-specific "physical" space
   * to the "physical" space of its parent object.
   */
  TransformType * GetObjectToNodeTransform(void);

  const TransformType * GetObjectToNodeTransform(void) const;

  /** Theses functions are just calling the itkSpatialObjectTreeNode
   *  functions */

  /** Add an object to the list of children. */
  void AddSpatialObject(Self *pointer);

  /** Remove the object passed as arguments from the list of
   * children. May this function
   * should return a false value if the object to remove is
   * not found in the list. */
  void RemoveSpatialObject(Self *object);

  /** Return a pointer to the parent object in the hierarchy tree */
  virtual const Self * GetParent(void) const;

  /** Return a pointer to the parent object in the hierarchy tree */
  virtual Self * GetParent(void);

  /** Returns a list of pointer to the children affiliated to this object.
   * A depth of 0 returns the immediate childred. A depth of 1 returns the
   * children and those children's children.
   * \warning User is responsible for freeing the list, but not the elements of
   * the list. */
  virtual ChildrenListType * GetChildren(unsigned int depth = 0,
                                         char *name = ITK_NULLPTR) const;

  /** Returns the number of children currently assigned to the object. */
  unsigned int GetNumberOfChildren(unsigned int depth = 0,
                                   char *name = ITK_NULLPTR) const;

  /** Set the list of pointers to children to the list passed as argument. */
  void SetChildren(ChildrenListType & children);

  /** Clear the spatial object by deleting all lists of children
   * and subchildren */
  virtual void Clear(void);

  /**
   * Compute an axis-aligned bounding box for an object and its selected
   * children, down to a specified depth.  After computation, the
   * resulting bounding box is stored in this->m_Bounds.
   *
   * By default, the bounding box children depth is maximum, meaning that
   * the bounding box for the object and all its recursive children is
   * computed.
   * This depth can be set (before calling ComputeBoundingBox) using
   * SetBoundingBoxChildrenDepth().
   *
   * By calling SetBoundingBoxChildrenName(), it is possible to
   * restrict the bounding box computation to objects of a specified
   * type or family of types.  The spatial objects included in the
   * computation are those whose typenames share, as their initial
   * substring, the string specified via SetBoundingBoxChildrenName().
   * The root spatial object (on which the method is called) is not
   * treated specially.  If its typename does not match the bounding
   * box children name, then it is not included in the bounding box
   * computation, but its descendents that match the string are
   * included.
   */
  virtual bool ComputeBoundingBox() const;

  virtual bool ComputeLocalBoundingBox() const
  {
    std::cerr << "SpatialObject::ComputeLocalBoundingBox Not Implemented!"
              << std::endl;
    return false;
  }

  /** Get a pointer to the bounding box of the object.
   *  The extents and the position of the box are not computed. */
  virtual BoundingBoxType * GetBoundingBox() const;

  /** Set/Get the depth at which the bounding box is computed */
  itkSetMacro(BoundingBoxChildrenDepth, unsigned int);
  itkGetConstReferenceMacro(BoundingBoxChildrenDepth, unsigned int);

  /** Set/Get the name of the children to consider when computing the
   *  bounding box */
  itkSetMacro(BoundingBoxChildrenName, std::string);
  itkGetConstReferenceMacro(BoundingBoxChildrenName, std::string);

  /** Set the pointer to the parent object in the tree hierarchy
   *  used for the spatial object patter. */
  void SetParent(Self *parent);

  /** These function are just calling the node container transforms */
  void SetNodeToParentNodeTransform(TransformType *transform);

  TransformType * GetNodeToParentNodeTransform(void);

  const TransformType * GetNodeToParentNodeTransform(void) const;

  /** Set/Get the default inside value (ValueAt()) of the object.
   *  Default is 1.0 */
  itkSetMacro(DefaultInsideValue, double);
  itkGetConstMacro(DefaultInsideValue, double);

  /** Set/Get the default outside value (ValueAt()) of the object.
   *  Default is 0.0 */
  itkSetMacro(DefaultOutsideValue, double);
  itkGetConstMacro(DefaultOutsideValue, double);

  /** Return the type of the spatial object as a string
   *  This is used by the SpatialObjectFactory */
  virtual std::string GetSpatialObjectTypeAsString() const;

protected:

  /** Constructor. */
  SpatialObject();

  /** Destructor. */
  virtual ~SpatialObject();

  virtual void PrintSelf(std::ostream & os, Indent indent) const ITK_OVERRIDE;

  /** Calculate the offsets needed to move from one pixel to the next
   * along a row, column, slice, volume, etc. These offsets are based
   * on the size of the BufferedRegion. This should be called after
   * the BufferedRegion is set. */
  void ComputeOffsetTable();

  itkSetMacro(Dimension, unsigned int);
  itkGetConstReferenceMacro(Dimension, unsigned int)
  itkSetMacro(TypeName, std::string);
  itkGetModifiableObjectMacro(Bounds, BoundingBoxType);
  itkGetModifiableObjectMacro(InternalInverseTransform, TransformType);

  /** This convenience method take the IndexToWorldTransform, and
   * if it can compute its inverse, then stores the result in the
   * InternalInverse member variable, that can be later accessed
   * with the method GetInternalInverseTransform(). This method is
   * not exposed to users, it is only intended to be called internally
   * by derived classes. */
  bool SetInternalInverseTransformToWorldToIndexTransform() const;

private:

  SpatialObject(const Self &);  //purposely not implemented
  void operator=(const Self &); //purposely not implemented

  BoundingBoxPointer       m_Bounds;
  mutable ModifiedTimeType m_BoundsMTime;

  TransformPointer m_ObjectToParentTransform;
  TransformPointer m_ObjectToWorldTransform;
  TransformPointer m_IndexToWorldTransform;

  /** Type of spatial object */
  std::string m_TypeName;

  unsigned int m_Dimension;

  OffsetValueType m_OffsetTable[3 + 1];

  RegionType m_LargestPossibleRegion;
  RegionType m_RequestedRegion;
  RegionType m_BufferedRegion;

  std::string     m_BoundingBoxChildrenName;
  unsigned int    m_BoundingBoxChildrenDepth;
  PropertyPointer m_Property;

  /** Object Identification Number */
  int m_Id;
  int m_ParentId;

  /** Pointer to the tree container */
  typename TreeNodeType::Pointer m_TreeNode;

  /** Pointer to the AffineGeometryFrame */
  AffineGeometryFramePointer m_AffineGeometryFrame;

  /** We keep an internal list of smart pointers to the immediate children
   *  This avoid the deletion of a child */
  ChildrenListType m_InternalChildrenList;

  /** We create an inverse transform pointer since it take time to create
   *  it each time to get the inverse transform in the IsInside() method */
  TransformPointer m_InternalInverseTransform;

  /** Default inside value for the ValueAt() */
  double m_DefaultInsideValue;

  /** Default outside value for the ValueAt() */
  double m_DefaultOutsideValue;
};
} // end of namespace itk

#if !defined( CABLE_CONFIGURATION )
#ifndef ITK_MANUAL_INSTANTIATION
#include "itkSpatialObject.hxx"
#endif
#endif

#endif // __itkSpatialObject_h
