/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef mia_3d_transform_hh
#define mia_3d_transform_hh

#include <iterator>
#include <memory>

#include <mia/core/vector.hh>
#include <mia/core/transformation.hh>
#include <mia/core/filter.hh>
#include <mia/3d/matrix.hh>
#include <mia/3d/image.hh>
#include <mia/3d/interpolator.hh>

NS_MIA_BEGIN


class C3DTransformation;

/**
   \ingroup registration 
   \brief pointer type for a 3D transformation 
*/
typedef std::shared_ptr<C3DTransformation > P3DTransformation;

/**
   \ingroup registration 
   @brief This is the generic base class for 3D transformations.

   This class defines the interface for 3D transformations. Most methods are pure abstract 
   and need to be implemented by a "real" transformation. 
*/

class EXPORT_3D C3DTransformation: public Transformation<C3DImage, C3DInterpolatorFactory> {
public:
	/// @cond LAZY 
	typedef C3DBounds Size; 
	typedef C3DImage Data;
	typedef C3DFVector Vector;
	typedef C3DInterpolatorFactory InterpolatorFactory;
	typedef C3DTransformation type; 
	typedef std::shared_ptr<C3DTransformation> Pointer; 
	static const char *data_descr;
	static const char *dim_descr;
	/// @endcond 
protected: 
	/**
	   This is the abstract base class of the actual implementation of the transformation iterator.  
	*/
	class iterator_impl  {
	public: 
		iterator_impl(); 

		/**
		   Initialize the iterator with its current position and the size of the domain 
		   the actual domain is: 
		   \f$[0,size.x-1]\times [0,size.y-1]\times [0,size.z-1]\f$
		 */
		iterator_impl(const C3DBounds& pos, const C3DBounds& size); 

		/**
		   Initialize the iterator with its current position, 
		   the subarea to be traveresed, and 
		   the size of the domain the complete domain is: 
		   \f$[0,size.x-1]\times [0,size.y-1]\times [0,size.z-1]\f$
		   
		 */
		iterator_impl(const C3DBounds& pos, const C3DBounds& begin, 
			      const C3DBounds& end, const C3DBounds& size); 
		
		/// move to next position 
		void increment(); 

		/// @returns current value of the underlying transformation 
		const C3DFVector&  get_value() const;

		/// @returns a dynamic copy of the iterator 
		virtual iterator_impl * clone() const  __attribute__((warn_unused_result)) = 0; 
		
		/**
		   Compare this iterator to another one. Iterators are equal if they are at the same 
		   position or at the end.
		*/
		bool operator == (const iterator_impl& other) const; 
		
		/// @returns the current position on the support domain grid 
		const C3DBounds& get_pos()const; 

		/// @returns the size of the supported domain 
		const C3DBounds& get_size()const; 
	private:
		virtual const C3DFVector& do_get_value()const = 0; 
		virtual void do_z_increment() = 0; 
		virtual void do_y_increment() = 0; 
		virtual void do_x_increment() = 0; 
		
		C3DBounds m_pos; 
		C3DBounds m_size; 
		C3DBounds m_start; 
		C3DBounds m_end; 


	}; 
public: 
	/**
	   This is an iterator that iterates over the grid of the 
	   domain where the transformation is defined. 
	   It implements the forward_iterator model. 
	   @todo make position readable from outside
	   @todo add iteration over sub-domains  
	 */
	
	class const_iterator : public std::forward_iterator_tag {
	public: 

		/// @cond some_STL_conform_typedefs
		typedef std::forward_iterator_tag iterator_category; 
		typedef C3DFVector value_type; 
		typedef size_t difference_type; 
		typedef C3DFVector *pointer; 
		typedef C3DFVector& reference; 
		/// @endcond 
		
		const_iterator(); 

		/// Copy constructor 
		const_iterator(const const_iterator& other); 		

		/// initialize the iterator with the actual worker object 
		const_iterator(iterator_impl * holder); 

		/// assignment operator 
		const_iterator& operator = (const const_iterator& other); 

		/// prefix increment 
		const_iterator& operator ++(); 
		
		/// postfix increment 
		const_iterator operator ++(int); 

		/// return current value of the transformation 
		const C3DFVector& operator *() const;

		/// return pointer to current value of the transformation 
		const C3DFVector  *operator ->() const;

		/// return the current position in 3D space 
 		const C3DBounds& pos()const; 


		/// @returns the size of the supported domain 
		const C3DBounds& get_size()const; 
		

	private: 
		std::unique_ptr<iterator_impl> m_holder;

		friend EXPORT_3D bool operator == (const C3DTransformation::const_iterator& a, 
						   const C3DTransformation::const_iterator& b); 

	}; 


	using Transformation<C3DImage, C3DInterpolatorFactory>::operator ();

	/**
	   Standard constructor place holder
	 */
	C3DTransformation(const C3DInterpolatorFactory& ipf);

	/**
	   Set the descrition string that was used to create this transformstion 
	   @param s
	 */
	void set_creator_string(const std::string& s); 

	/// @returns the description string used to create this transformations 
	const std::string& get_creator_string()const; 

	/**
	   @returns a newly allocated copy of the actual transformation
	 */
	virtual C3DTransformation *clone() const;

	/**
	   @returns a the inverse transform 
	 */
	virtual C3DTransformation *invert() const = 0;


	/**
	   @returns the start iterator of the transformation that iterates over the grid 
	   of the area the ransformation is defined on 
	 */
	virtual const_iterator begin() const = 0; 

	/**
	   @returns the end iterator of the transformation that iterates over the grid 
	   of the area the ransformation is defined on 
	 */
	
	virtual const_iterator end() const = 0; 


	/**
	   @returns the start iterator of the transformation that iterates over the grid 
	   of the area the ransformation is defined on 
	 */
	virtual const_iterator begin_range(const C3DBounds& begin, const C3DBounds& end) const = 0; 

	/**
	   @returns the end iterator of the transformation that iterates over the grid 
	   of the area the ransformation is defined on 
	 */
	
	virtual const_iterator end_range(const C3DBounds& begin, const C3DBounds& end) const = 0; 

	/**
	   Placeholder for transformations that might need special initializations
	   like the B-spline based transformation
	 */
	virtual void reinit() const;


	/**
	   Transforation upscaling to new image size
	   @param size new size of the transformation
	   @returns shared pointer to upscaled transformation
	 */
	P3DTransformation upscale(const C3DBounds& size) const;

	/**
	   update a transformation by using a vector field
	   \remark this is too specialized and should go away
	 */
	virtual void update(float step, const C3DFVectorfield& a) = 0;

	/**
	   @returns the number of free parameters this transformation provides
	 */
	virtual size_t degrees_of_freedom() const = 0;

	/**
	   set the transformation to be the identity transform
	 */
	virtual void set_identity() = 0;

	/**
	   evaluate the derivative (Jacobian matrix) of the transformation at the given
	   grid coordinates
	   @param x
	   @param y
	   @param z
	   @returns 3x3 matrix of the derivative
	 */
	virtual C3DFMatrix derivative_at(int x, int y, int z) const = 0;

	/**
	   evaluate the derivative (Jacobian matrix) of the transformation at the given
	   coordinates
	   @param x
	   @returns 3x3 matrix of the derivative
	 */
	virtual C3DFMatrix derivative_at(const C3DFVector& x) const = 0;

	/**
	   Translate the input gradient to a vector field in the space of the transformation field
	   \remark this is too specialized and needs to be replaced by something
	 */
	virtual void translate(const C3DFVectorfield& gradient, CDoubleVector& params) const = 0;

	/**
	   @returns the transformation parameters as a flat value array
	 */
	virtual CDoubleVector get_parameters() const = 0;

	/**
	   sets the transformation parameters from a flat value array
	 */
	virtual void set_parameters(const CDoubleVector& params) = 0;

	/**
	   @returns the (approximate) maximum absolute translation of the transformation over the whole domain
	 */
	virtual float get_max_transform() const = 0;

	/**
	   A transformation is defined on [0,X-1]x[0.Y-1]y[0,Z-1]. 
	   @returns the upper boundaries (X,Y,Z) of this range 
	 */
	virtual const C3DBounds& get_size() const = 0;

	/**
	   evaluate the pertuberation of a vectorfield combined with this transformation
	   \param[in,out] v vectorfield to be pertuberated
	   @returns maximum value of the pertuberation
	   \remark this makes only sense for fluid dynamics registration and should be handled elsewhere
	 */
	virtual float pertuberate(C3DFVectorfield& v) const = 0;

	/**
	   @returns the displacement at coordinate x
	   \remark rename the function to something that explains better whats going on
	 */
	virtual C3DFVector apply(const C3DFVector& x) const  __attribute__((deprecated));

	/**
	   Returns the displacement of the transformation at a given coordinate
	   \param x coordinate
	   \returns actual displacement 
	 */
	
	virtual C3DFVector get_displacement_at(const C3DFVector& x) const = 0;
	
        /**
	   apply the actual transformation to point x
	   @returns transformed point
	 */
	virtual C3DFVector operator () (const C3DFVector& x) const = 0;

	/**
	   Evaluate the Jacobian of the transformation when updated with vector field v by factor delta
	   @returns Jacobian
	   \remark this only is used for fluid dynamics registration and should probably be moved elsewhere
	 */
	virtual float get_jacobian(const C3DFVectorfield& v, float delta) const = 0;

	/**
	   Increase the number of coefficients along the axis according to the 
	   maximum given by the c-rate. This is the complemantary step to upscaling the 
	   transformation. 
	   \returns true if an actual refinment was done. 
	 */
	virtual bool refine(); 

	/**
	   Enable some additional debugging.  
	 */
	void set_debug(); 

	/* Attributes */
	/**
	   This attribute defines the voxel spacing of the input data of this transform. 
	 */
	static constexpr const char *input_spacing_attr = "in-voxel-spacing"; 

	/**
	   This attribute defines the output voxel spacing of this transform. 
	 */
	static constexpr const char *output_spacing_attr = "out-voxel-spacing"; 


	virtual C3DBounds get_minimal_supported_image_size() const; 
protected: 
	/// @returns information about the debug state 
	bool get_debug()const; 

private: 
	P3DImage do_transform(const C3DImage& input, const C3DInterpolatorFactory& ipf) const; 

	virtual P3DTransformation do_upscale(const C3DBounds& size) const = 0;

	std::string m_creator_string;  
	virtual C3DTransformation *do_clone() const = 0;
	bool m_debug; 
};


/**
   \ingroup registration 
   \brief Compare two transformation iterators
   @param a
   @param b
   @returns \a true if iterators are not equal, \a false otherwise 
   
*/
EXPORT_3D bool operator != (const C3DTransformation::const_iterator& a, 
			    const C3DTransformation::const_iterator& b); 


NS_MIA_END

#endif
