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

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 mitkProportionalTimeGeometry_h
#define mitkProportionalTimeGeometry_h

// MITK
#include <MitkCoreExports.h>
#include <mitkCommon.h>
#include <mitkGeometry3D.h>
#include <mitkTimeGeometry.h>

namespace mitk
{
  /**
  * \brief Organizes geometries over proportional time steps
  *
  * For this TimeGeometry implementation it is assumed that
  * the durations of the time steps are equidistant, e.g.
  * the durations of the time steps in one ProportionalTimeGeometry
  * are the same. The geometries of the time steps are independent,
  * and not linked to each other. Since the timeBounds of the
  * geometries are different for each time step it is not possible
  * to set the same geometry to different time steps. Instead
  * copies should be used.
  *
  * \addtogroup geometry
  */
  class MITKCORE_EXPORT ProportionalTimeGeometry : public TimeGeometry
  {
  public:
    mitkClassMacro(ProportionalTimeGeometry, TimeGeometry);

    ProportionalTimeGeometry();
    typedef ProportionalTimeGeometry self;
    itkFactorylessNewMacro(Self);
    itkCloneMacro(Self);

      /**
      * \brief Returns the number of time steps.
      *
      * Returns the number of time steps for which
      * geometries are saved. The number of time steps
      * is also the upper bound of the time steps. The
      * minimum time steps is always 0.
      */
      TimeStepType CountTimeSteps() const override;
    /**
    * \brief Returns the first time point for which the object is valid.
    *
    * Returns the first valid time point for this geometry. If only one
    * time steps available it usually goes from -max to +max. The time point
    * is given in ms.
    */
    TimePointType GetMinimumTimePoint() const override;
    /**
    * \brief Returns the last time point for which the object is valid
    *
    * Gives the last time point for which a valid geometry is saved in
    * this time geometry. The time point is given in ms.
    */
    TimePointType GetMaximumTimePoint() const override;

    /**
    * \brief Returns the first time point for which the object is valid.
    *
    * Returns the first valid time point for the given TimeStep. The time point
    * is given in ms.
    */
    TimePointType GetMinimumTimePoint(TimeStepType step) const override;
    /**
    * \brief Returns the last time point for which the object is valid
    *
    * Gives the last time point for the Geometry specified by the given TimeStep. The time point is given in ms.
    */
    TimePointType GetMaximumTimePoint(TimeStepType step) const override;

    /**
    * \brief Get the time bounds (in ms)
    */
    TimeBounds GetTimeBounds() const override;

    /**
    * \brief Get the time bounds for the given TimeStep (in ms)
    */
    TimeBounds GetTimeBounds(TimeStepType step) const override;

    /**
    * \brief Tests if a given time point is covered by this object
    *
    * Returns true if a geometry can be returned for the given time
    * point and falls if not. The time point must be given in ms.
    */
    bool IsValidTimePoint(TimePointType timePoint) const override;
    /**
    * \brief Test for the given time step if a geometry is available
    *
    * Returns true if a geometry is defined for the given time step.
    * Otherwise false is returned.
    * The time step is defined as positive number.
    */
    bool IsValidTimeStep(TimeStepType timeStep) const override;
    /**
    * \brief Converts a time step to a time point
    *
    * Converts a time step to a time point in a way that
    * the new time point indicates the same geometry as the time step.
    * If the original time steps does not point to a valid geometry,
    * a time point is calculated that also does not point to a valid
    * geometry, but no exception is raised.
    */
    TimePointType TimeStepToTimePoint(TimeStepType timeStep) const override;
    /**
  * \brief Converts a time point to the corresponding time step
  *
  * Converts a time point to a time step in a way that
  * the new time step indicates the same geometry as the time point.
  * If a negative invalid time point is given always time step 0 is
  * returned. If an positive invalid time step is given an invalid
  * time step will be returned.
  */
    TimeStepType TimePointToTimeStep(TimePointType timePoint) const override;
    /**
    * \brief Returns the geometry which corresponds to the given time step
    *
    * Returns a clone of the geometry which defines the given time step. If
    * the given time step is invalid an null-pointer is returned.
    */
    BaseGeometry::Pointer GetGeometryCloneForTimeStep(TimeStepType timeStep) const override;

    /**
    * \brief Returns the geometry which corresponds to the given time point
    *
    * Returns the geometry which defines the given time point. If
    * the given time point is invalid an null-pointer is returned.
    *
    * If the returned geometry is changed this will affect the saved
    * geometry.
    */
    BaseGeometry::Pointer GetGeometryForTimePoint(TimePointType timePoint) const override;
    /**
    * \brief Returns the geometry which corresponds to the given time step
    *
    * Returns the geometry which defines the given time step. If
    * the given time step is invalid an null-pointer is returned.
    *
    * If the returned geometry is changed this will affect the saved
    * geometry.
    */
    BaseGeometry::Pointer GetGeometryForTimeStep(TimeStepType timeStep) const override;

    /**
    * \brief Tests if all necessary information are set and the object is valid
    */
    bool IsValid() const override;

    /**
    * \brief Initializes a new object with one time steps which contains an empty geometry.
    */
    void Initialize() override;

    /**
    * \brief Expands the time geometry to the given number of time steps.
    *
    * Initializes the new time steps with empty geometries if no timesteps
    * in the geometry so far. Otherwise fills the new times steps with
    * clones of the first time step.
    * Shrinking is not supported.
    */
    void Expand(TimeStepType size) override;
    /**
    * \brief Sets the geometry for the given time step
    *
    * This method does not afflict other time steps, since the geometry for
    * each time step is saved individually.
    */
    void SetTimeStepGeometry(BaseGeometry *geometry, TimeStepType timeStep) override;

    /**
    * \brief Replaces the geometry instances with clones of the passed geometry.
    *
    * Replaces the geometries of all time steps with clones of the passed
    * geometry. Replacement strategy depends on the implementation of TimeGeometry
    * sub class.
    * @remark The time points itself stays untouched. Use this method if you want
    * to change the spatial properties of a TimeGeometry and preserve the time
    * "grid".
    */
    void ReplaceTimeStepGeometries(const BaseGeometry *geometry) override;

    /**
     * \brief Get the time point of the first time step (in ms).
     * \return The minimum time point.
     */
    itkGetConstMacro(FirstTimePoint, TimePointType);

    /**
     * \brief Set the time point of the first time step (in ms).
     * \param[in] _arg The first time point value.
     */
    itkSetMacro(FirstTimePoint, TimePointType);

    /**
     * \brief Get the uniform duration of each time step (in ms).
     * \return The step duration.
     */
    itkGetConstMacro(StepDuration, TimePointType);

    /**
     * \brief Set the uniform duration of each time step (in ms).
     * \param[in] _arg The step duration value.
     */
    itkSetMacro(StepDuration, TimePointType);

    /**
     * \brief Remove all stored geometries, leaving the container empty.
     */
    void ClearAllGeometries();

    /**
     * \brief Pre-allocate space for a given number of geometries.
     *
     * This only reserves memory; it does not create geometry objects.
     *
     * \param[in] numberOfGeometries The number of slots to reserve.
     */
    void ReserveSpaceForGeometries(TimeStepType numberOfGeometries);

    /**
     * \brief Initialize with copies of a given geometry for each time step.
     *
     * Clones \a geometry for each of the \a timeSteps steps. If
     * \a timeSteps < 2, the time bounds span from -max to +max with
     * infinite duration. Otherwise, the first time point is 0 and
     * the step duration is 1 ms.
     *
     * \param[in] geometry The geometry to clone for each step.
     * \param[in] timeSteps Number of time steps (minimum 1).
     */
    void Initialize(const BaseGeometry *geometry, TimeStepType timeSteps);

    /**
     * \brief Initialize with empty Geometry3D instances for each time step.
     *
     * Creates a default Geometry3D and delegates to
     * Initialize(const BaseGeometry*, TimeStepType).
     *
     * \param[in] timeSteps Number of time steps (minimum 1).
     */
    void Initialize(TimeStepType timeSteps);

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

  protected:
    ProportionalTimeGeometry(const ProportionalTimeGeometry &other);
    ~ProportionalTimeGeometry() override;

    std::vector<BaseGeometry::Pointer> m_GeometryVector;
    TimePointType m_FirstTimePoint;
    TimePointType m_StepDuration;

    mitkCloneMacro(Self);
  }; // end class ProportialTimeGeometry

  /**
  * @brief Equal A function comparing two ProportionalTimeGeometries for being identical.
  *
  * @ingroup MITKTestingAPI
  *
  * The function compares two instances of ProportionalTimeGeometries in all their aspects.
  *
  * The parameter eps is a tolerance value for all methods which are internally used for comparison.
  * If you want to use different tolerance values for different parts of the geometry, feel free to use
  * the other comparison methods and write your own implementation of Equal.
  *
  * @param rightHandSide Compare this against leftHandSide.
  * @param leftHandSide Compare this against rightHandSide.
  * @param eps Tolerance for comparison. You can use mitk::eps in most cases.
  * @param verbose Flag indicating if the user wants detailed console output or not.
  *
  * @return True, if all comparison are true. False in any other case.
  */
  MITKCORE_EXPORT bool Equal(const mitk::ProportionalTimeGeometry &leftHandSide,
                             const mitk::ProportionalTimeGeometry &rightHandSide,
                             ScalarType eps,
                             bool verbose);

} // end namespace MITK
#endif
