/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center (DKFZ)
All rights reserved.

Use of this source code is governed by a 3-clause BSD license that can be
found in the LICENSE file.

============================================================================*/

#ifndef mitkSlicedGeometry3D_h
#define mitkSlicedGeometry3D_h

#include <mitkBaseGeometry.h>
#include <mitkPlaneGeometry.h>

namespace mitk
{
  class SliceNavigationController;
  class NavigationController;

  /** \brief Describes the geometry of a data object consisting of slices.
  *
  * A PlaneGeometry can be requested for each slice. In the case of
  * \em evenly-spaced, \em plane geometries (m_EvenlySpaced==true),
  * only the 2D-geometry of the first slice has to be set (to an instance of
  * PlaneGeometry). The 2D geometries of the other slices are calculated
  * by shifting the first slice in the direction m_DirectionVector by
  * m_Spacing.z * sliceNumber. The m_Spacing member (which is only
  * relevant in the case m_EvenlySpaced==true) describes the size of a voxel
  * (in mm), i.e., m_Spacing.x is the voxel width in the x-direction of the
  * plane. It is derived from the reference geometry of this SlicedGeometry3D,
  * which usually would be the global geometry describing how datasets are to
  * be resliced.
  *
  * By default, slices are oriented in the direction of one of the main axes
  * (x, y, z). However, by means of rotation, it is possible to realign the
  * slices in any possible direction. In case of an inclined plane, the spacing
  * is derived as a product of the (regular) geometry spacing and the direction
  * vector of the plane.
  *
  * SlicedGeometry3D and the associated PlaneGeometries have to be initialized in
  * the method GenerateOutputInformation() of BaseProcess (or CopyInformation /
  * UpdateOutputInformation of BaseData, if possible, e.g., by analyzing pic
  * tags in Image) subclasses. See also
  *
  * \sa itk::ProcessObject::GenerateOutputInformation(),
  * \sa itk::DataObject::CopyInformation() and
  * \a itk::DataObject::UpdateOutputInformation().
  *
  * Rule: everything is in mm (or ms for temporal information) if not
  * stated otherwise.
  *
  * \warning The hull (i.e., transform, bounding-box and
  * time-bounds) is only guaranteed to be up-to-date after calling
  * UpdateInformation().
  *
  * \ingroup Geometry
  */
  class MITKCORE_EXPORT SlicedGeometry3D : public mitk::BaseGeometry
  {
  public:
    mitkClassMacro(SlicedGeometry3D, BaseGeometry);

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

      /**
      * \brief Returns the PlaneGeometry of the slice (\a s).
      *
      * If (a) m_EvenlySpaced==true, (b) we don't have a PlaneGeometry stored
      * for the requested slice, and (c) the first slice (s=0)
      * is a PlaneGeometry instance, then we calculate the geometry of the
      * requested as the plane of the first slice shifted by m_Spacing[3]*s
      * in the direction of m_DirectionVector.
      *
      * \warning The PlaneGeometries are not necessarily up-to-date and not even
      * initialized.
      *
      * The PlaneGeometries have to be initialized in the method
      * GenerateOutputInformation() of BaseProcess (or CopyInformation /
      * UpdateOutputInformation of BaseData, if possible, e.g., by analyzing
      * pic tags in Image) subclasses. See also
      *
      * \sa itk::ProcessObject::GenerateOutputInformation(),
      * \sa itk::DataObject::CopyInformation() and
      * \sa itk::DataObject::UpdateOutputInformation().
      */
      virtual mitk::PlaneGeometry *GetPlaneGeometry(int s) const;
    /**
     * \brief Switch between image geometry and normal geometry with origin correction.
     *
     * Overrides BaseGeometry to also propagate the change to all
     * contained PlaneGeometries.
     *
     * \param[in] isAnImageGeometry true to switch to image geometry,
     *            false to switch to normal geometry.
     */
    void ChangeImageGeometryConsideringOriginOffset(const bool isAnImageGeometry) override;

    /**
     * \brief Get the bounding box of this sliced geometry.
     *
     * \return Const pointer to the bounding box.
     * \pre The bounding box must not be null.
     */
    const mitk::BoundingBox *GetBoundingBox() const override;

    /**
    * \brief Get the number of slices
    */
    itkGetConstMacro(Slices, unsigned int);

      /**
      * \brief Set PlaneGeometry of slice \a s.
      */
      virtual bool SetPlaneGeometry(mitk::PlaneGeometry *geometry2D, int s);

    /**
    * \brief Check whether a slice exists
    */
    virtual bool IsValidSlice(int s = 0) const;

    /**
     * \brief Get the reference geometry used for reslicing calculations.
     *
     * \return Const pointer to the reference geometry, or nullptr if not set.
     */
    virtual const BaseGeometry* GetReferenceGeometry() const;

    /**
     * \brief Set the reference geometry for reslicing calculations.
     *
     * The reference geometry is typically the global geometry describing
     * how datasets are to be resliced. It is propagated to each
     * PlaneGeometry managed by this SlicedGeometry3D.
     *
     * \param[in] referenceGeometry The reference geometry (may be nullptr).
     */
    virtual void SetReferenceGeometry(const BaseGeometry *referenceGeometry);

    /**
     * \brief Check whether a reference geometry has been set.
     *
     * \return true if a non-null reference geometry is available.
     */
    bool HasReferenceGeometry() const;

    /**
    * \brief Set the SliceNavigationController corresponding to this sliced
    * geometry.
    *
    * The SNC needs to be informed when the number of slices in the geometry
    * changes, which can occur whenthe slices are re-oriented by rotation.
    */
    virtual void SetSliceNavigationController(mitk::SliceNavigationController *snc);
    /**
     * \brief Get the SliceNavigationController associated with this geometry.
     *
     * \return Pointer to the SliceNavigationController, or nullptr if none is set.
     */
    mitk::SliceNavigationController *GetSliceNavigationController();

    /**
    * \brief Set/Get whether the SlicedGeometry3D is evenly-spaced
    * (m_EvenlySpaced)
    *
    * If (a) m_EvenlySpaced==true, (b) we don't have a PlaneGeometry stored for
    * the requested slice, and (c) the first slice (s=0) is a PlaneGeometry
    * instance, then we calculate the geometry of the requested as the plane
    * of the first slice shifted by m_Spacing.z * s in the direction of
    * m_DirectionVector.
    *
    * \sa GetPlaneGeometry
    */
    itkGetConstMacro(EvenlySpaced, bool);

      virtual void SetEvenlySpaced(bool on = true);

    /**
    * \brief Set/Get the vector between slices for the evenly-spaced case
    * (m_EvenlySpaced==true).
    *
    * If the direction-vector is (0,0,0) (the default) and the first
    * 2D geometry is a PlaneGeometry, then the direction-vector will be
    * calculated from the plane normal.
    *
    * \sa m_DirectionVector
    */
    virtual void SetDirectionVector(const mitk::Vector3D &directionVector);
    itkGetConstMacro(DirectionVector, const mitk::Vector3D &);

    /** \brief Property key for the number of slices. */
    static const std::string SLICES;
    /** \brief Property key for the direction vector between slices. */
    const static std::string DIRECTION_VECTOR;
    /** \brief Property key for the evenly-spaced flag. */
    const static std::string EVENLY_SPACED;

    /**
    * \brief Tell this instance how many PlaneGeometries it shall manage. Bounding
    * box and the PlaneGeometries must be set additionally by calling the respective
    * methods!
    *
    * \warning Bounding box and the 2D-geometries must be set additionally: use
    * SetBounds(), SetGeometry().
    */
    virtual void InitializeSlicedGeometry(unsigned int slices);

    /**
    * \brief Completely initialize this instance as evenly-spaced with slices
    * parallel to the provided PlaneGeometry that is used as the first slice and
    * for spacing calculation.
    *
    * Initializes the bounding box according to the width/height of the
    * PlaneGeometry and \a slices. The spacing is calculated from the PlaneGeometry.
    */
    virtual void InitializeEvenlySpaced(mitk::PlaneGeometry *geometry2D, unsigned int slices);

    /**
    * \brief Completely initialize this instance as evenly-spaced with slices
    * parallel to the provided PlaneGeometry that is used as the first slice and
    * for spacing calculation (except z-spacing).
    *
    * Initializes the bounding box according to the width/height of the
    * PlaneGeometry and \a slices. The x-/y-spacing is calculated from the
    * PlaneGeometry.
    */
    virtual void InitializeEvenlySpaced(mitk::PlaneGeometry *geometry2D,
                                        mitk::ScalarType zSpacing,
                                        unsigned int slices);

    /**
    * \brief Completely initialize this instance as evenly-spaced plane slices
    * parallel to a side of the provided BaseGeometry and using its spacing
    * information.
    *
    * Initializes the bounding box according to the width/height of the
    * BaseGeometry and the number of slices according to
    * BaseGeometry::GetExtent(2).
    *
    * \param geometry3D
    * \param orientation side parallel to which the slices will be oriented
    * \param top if \a true, create plane at top, otherwise at bottom
    * (for AnatomicalPlane Axial, for other plane locations respectively)
    * \param frontside defines the side of the plane (the definition of
    * front/back is somewhat arbitrary)
    *
    * \param rotated rotates the plane by 180 degree around its normal (the
    * definition of rotated vs not rotated is somewhat arbitrary)
    */
    virtual void InitializePlanes(const mitk::BaseGeometry *geometry3D,
                                  mitk::AnatomicalPlane orientation,
                                  bool top = true,
                                  bool frontside = true,
                                  bool rotated = false);

    /**
     * \brief Override to propagate the ImageGeometry flag to all slice geometries.
     *
     * \param[in] isAnImageGeometry true for image geometry, false otherwise.
     */
    void SetImageGeometry(const bool isAnImageGeometry) override;

    /**
     * \brief Execute an operation on this sliced geometry.
     *
     * Handles rotation operations by re-initializing the plane stack
     * after the rotation is applied.
     *
     * \param[in] operation The operation to execute.
     */
    void ExecuteOperation(Operation *operation) override;

    /**
     * \brief Calculate directed spacing from regular spacing and a direction vector.
     *
     * Computes the effective spacing along \a d given the isotropic
     * \a spacing, using the ellipsoid equation.
     *
     * \param[in] spacing The regular voxel spacing.
     * \param[in] d The direction vector.
     * \return The calculated spacing along \a d.
     */
    static double CalculateSpacing(const mitk::Vector3D &spacing, const mitk::Vector3D &d);

  protected:
    SlicedGeometry3D();

    SlicedGeometry3D(const SlicedGeometry3D &other);

    ~SlicedGeometry3D() override;

    /**
    * Reinitialize plane stack after rotation. More precisely, the first plane
    * of the stack needs to spatially aligned, in two respects:
    *
    * 1. Re-alignment with respect to the dataset center; this is necessary
    *    since the distance from the first plane to the center could otherwise
    *    continuously decrease or increase.
    * 2. Re-alignment with respect to a given reference point; the reference
    *    point is a location which the user wants to be exactly touched by one
    *    plane of the plane stack. The first plane is minimally shifted to
    *    ensure this touching. Usually, the reference point would be the
    *    point around which the geometry is rotated.
    */
    virtual void ReinitializePlanes(const Point3D &center, const Point3D &referencePoint);

    ScalarType GetLargestExtent(const BaseGeometry *geometry);

    void PrintSelf(std::ostream &os, itk::Indent indent) const override;

    /** Calculate "directed spacing", i.e. the spacing in directions
    * non-orthogonal to the coordinate axes. This is done via the
    * ellipsoid equation.
    */
    double CalculateSpacing(const mitk::Vector3D &direction) const;

    /** The extent of the slice stack, i.e. the number of slices, depends on the
    * plane normal. For rotated geometries, the geometry's transform needs to
    * be accounted in this calculation.
    */
    mitk::Vector3D AdjustNormal(const mitk::Vector3D &normal) const;

    /**
    * Container for the 2D-geometries contained within this SliceGeometry3D.
    */
    mutable std::vector<PlaneGeometry::Pointer> m_PlaneGeometries;

    /**
    * If (a) m_EvenlySpaced==true, (b) we don't have a PlaneGeometry stored
    * for the requested slice, and (c) the first slice (s=0)
    * is a PlaneGeometry instance, then we calculate the geometry of the
    * requested as the plane of the first slice shifted by m_Spacing.z*s
    * in the direction of m_DirectionVector.
    *
    * \sa GetPlaneGeometry
    */
    bool m_EvenlySpaced;

    /**
    * Vector between slices for the evenly-spaced case (m_EvenlySpaced==true).
    * If the direction-vector is (0,0,0) (the default) and the first
    * 2D geometry is a PlaneGeometry, then the direction-vector will be
    * calculated from the plane normal.
    */
    mutable mitk::Vector3D m_DirectionVector;

    /** Number of slices this SliceGeometry3D is describing. */
    unsigned int m_Slices;

    /** Underlying BaseGeometry for this SlicedGeometry */
    const mitk::BaseGeometry *m_ReferenceGeometry;

    /** SNC correcsponding to this geometry; used to reflect changes in the
    * number of slices due to rotation. */
    // mitk::NavigationController *m_NavigationController;
    mitk::SliceNavigationController *m_SliceNavigationController;

    //##Documentation
    //## @brief PreSetSpacing
    //##
    //## These virtual function allows a different beahiour in subclasses.
    //## Do implement them in every subclass of BaseGeometry. If not needed, use
    //## {Superclass::PreSetSpacing();};
    void PreSetSpacing(const mitk::Vector3D &aSpacing) override;

    mitkCloneMacro(Self);
  };
} // namespace mitk

#endif
