/* -*- 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_2d_iterator_hh
#define mia_2d_iterator_hh

#include <mia/2d/vector.hh>

NS_MIA_BEGIN

/**
   @ingroup misc 
   \brief a 2D iterator that knows its position in the 2D grid ans supports iterating over 
   sub-ranges 
   

   Iterator to iterate over a sub-range of 2D data that is given on a grid. 
   Two iterators are considered to be equal, if their positions are equal.  
   \tparam the internal iterator that is used to iterate of the original 
   grid without skipping. 
   
 */

template <typename I> 
class EXPORT_2D range2d_iterator_with_boundary_flag: public std::forward_iterator_tag {
public: 
	/// data type reference 
	typedef typename I::reference reference; 
	/// data type pointer  
	typedef typename I::pointer pointer; 
	/// data type for the real iterator in the background 
	typedef I internal_iterator; 

	typedef typename I::value_type value_type; 
	
	/**
	   Enumerate to describe the various positions on the domain boundarys. 
	   These boundaries correspond to the full domain of the data, not to 
           the sub-range this iteratior works on. 
	   I.e. if the sub-range is a subset of the \a open domain (i.e. without its boundary) 
	   then the iterator will never touch the domain boundary. 
	 */
	enum EBoundary {
		eb_none  = 0, /**< no boundary */
		eb_xlow  = 1, /**< at low x-boundary */ 
		eb_xhigh = 2, /**< at high x-boundary */  
		eb_x     = 3, /**< at one of the x-boundaries */  
		eb_ylow = 4,  /**< at low y-boundary */ 
		eb_yhigh = 8, /**< at high y-boundary */
		eb_y     = 0xC, /**< at one of the y-boundaries */  
	}; 
	

	
	/** standard constructor */
	range2d_iterator_with_boundary_flag(); 

	/**
	   Full constructor of the range iterator 
	   @param pos iterator position to initialize the iterator with 
	   @param size size of the original data field 
	   @param start start of the iterator range 
	   @param end end of the iterator range 
	   @param iterator the iterator of the underlying 2D data structure 
	*/
	range2d_iterator_with_boundary_flag(const C2DBounds& pos, const C2DBounds& size, 
			 const C2DBounds& start, const C2DBounds& end, I iterator);
	
	/**
	   End iterator, can't be dereferenced  
	   This iterator is only there to define the end position of the range_iterator. 
	   \param pos end position to set this iterator to. 
	 */
	range2d_iterator_with_boundary_flag(const C2DBounds& pos);

	/// assignment operator 
	range2d_iterator_with_boundary_flag<I>& operator = (const range2d_iterator_with_boundary_flag<I>& other); 
	
	/// copy constructore 
	range2d_iterator_with_boundary_flag(const range2d_iterator_with_boundary_flag<I>& other); 

	/// friend iterator type because we may want to copy a iterator to a const_iterator. 
	template <typename AI> 
	friend class range2d_iterator_with_boundary_flag; 
	
	/**
	   Constructor to construct the iterator  from one that is based on another 
	   iterator type. The usual idea is that a iterator may be converted  into it's const variant. 
	   \tparam AI the other iterator type. Iterator type I must be copy-constructable from 
	   type AI 
	   \param other 
	 */
	template <typename AI>
	range2d_iterator_with_boundary_flag(const range2d_iterator_with_boundary_flag<AI>& other); 


	/**
	   Assignment operator from another type of iterator 
	   \tparam AI other iterator type. The assignment I b = a; with a of type AI must be defined.
	   \param other 
	 */
	template <typename AI>
	range2d_iterator_with_boundary_flag<I>& operator = (const range2d_iterator_with_boundary_flag<AI>& other); 

	
	/// prefix increment 
	range2d_iterator_with_boundary_flag<I>& operator ++(); 
	/// postfix increment 
	range2d_iterator_with_boundary_flag<I> operator ++(int); 
	
	/// @returns current value the iterator points to 
	reference  operator *() const;
	
	/// @returns pointer to the current value the iterator points to 
	pointer    operator ->() const;

	/** \returns the current position within the 2D grid with respect to the 
	    full size of the grid. 
	 */
	const C2DBounds& pos() const; 

	/// @cond NOFRIENDDOC
	template <typename T> friend
	bool operator == (const range2d_iterator_with_boundary_flag<T>& left, 
			  const range2d_iterator_with_boundary_flag<T>& right); 

	template <typename T> friend
	bool operator != (const range2d_iterator_with_boundary_flag<T>& left, 
			  const range2d_iterator_with_boundary_flag<T>& right); 
	/// @endcond 

	/**
	   Return the internal iterator
	 */
	internal_iterator get_point(); 

	/// \returns the flags describing whether the iterator is on a domain boundary. 
	int get_boundary_flags() const; 

private: 

	void increment_y(); 
	void increment_z(); 

	C2DBounds m_pos; 
	C2DBounds m_size; 
	C2DBounds m_begin; 
	C2DBounds m_end; 
	int m_xstride; 
	I m_iterator; 
	int m_boundary; 
}; 



template <typename I> 
template <typename AI>
range2d_iterator_with_boundary_flag<I>& range2d_iterator_with_boundary_flag<I>::operator = (const range2d_iterator_with_boundary_flag<AI>& other)
{
	m_pos = other.m_pos; 
	m_size = other.m_size;  
	m_begin = other.m_begin; 
	m_end = other.m_end; 
	m_iterator = other.m_iterator; 
	m_xstride = other.m_xstride; 
	m_boundary = other.m_boundary; 
	return *this; 
}

template <typename I> 
template <typename AI>
range2d_iterator_with_boundary_flag<I>::range2d_iterator_with_boundary_flag(const range2d_iterator_with_boundary_flag<AI>& other):
	m_pos(other.m_pos), 
	m_size(other.m_size), 
	m_begin(other.m_begin), 
	m_end(other.m_end), 
	m_xstride(other.m_xstride),
	m_iterator(other.m_iterator), 
	m_boundary(other.m_boundary)
{
}	

template <typename T> 
bool operator == (const range2d_iterator_with_boundary_flag<T>& left, const range2d_iterator_with_boundary_flag<T>& right)
{
	return left.m_pos == right.m_pos; 
}

template <typename T> 
bool operator != (const range2d_iterator_with_boundary_flag<T>& left, const range2d_iterator_with_boundary_flag<T>& right)
{
	return left.m_pos != right.m_pos; 
}


template <typename I> 
class EXPORT_2D range2d_iterator: public  std::iterator<std::forward_iterator_tag, typename I::value_type>
{
public: 
	/// data type reference 
	typedef typename I::reference reference; 
	/// data type pointer  
	typedef typename I::pointer pointer; 
	/// data type for the real iterator in the background 
	typedef I internal_iterator; 

	typedef typename I::value_type value_type; 
	
	/** standard constructor */
	range2d_iterator(); 

	/**
	   Full constructor of the range iterator 
	   @param pos iterator position to initialize the iterator with 
	   @param size size of the original data field 
	   @param start start of the iterator range 
	   @param end end of the iterator range 
	   @param iterator the iterator of the underlying 2D data structure 
	*/
	range2d_iterator(const C2DBounds& pos, const C2DBounds& size, 
			 const C2DBounds& start, const C2DBounds& end, I iterator);
	
	/**
	   End iterator, can't be dereferenced  
	   This iterator is only there to define the end position of the range_iterator. 
	   \param pos end position to set this iterator to. 
	 */
	range2d_iterator(const C2DBounds& pos);

	/// assignment operator 
	range2d_iterator<I>& operator = (const range2d_iterator<I>& other); 
	
	/// copy constructore 
	range2d_iterator(const range2d_iterator<I>& other); 

	/// friend iterator type because we may want to copy a iterator to a const_iterator. 
	template <typename AI> 
	friend class range2d_iterator; 
	
	/**
	   Constructor to construct the iterator  from one that is based on another 
	   iterator type. The usual idea is that a iterator may be converted  into it's const variant. 
	   \tparam AI the other iterator type. Iterator type I must be copy-constructable from 
	   type AI 
	   \param other 
	 */
	template <typename AI>
	range2d_iterator(const range2d_iterator<AI>& other); 


	/**
	   Assignment operator from another type of iterator 
	   \tparam AI other iterator type. The assignment I b = a; with a of type AI must be defined.
	   \param other 
	 */
	template <typename AI>
	range2d_iterator<I>& operator = (const range2d_iterator<AI>& other); 

	
	/// prefix increment 
	range2d_iterator<I>& operator ++(); 
	/// postfix increment 
	range2d_iterator<I> operator ++(int); 
	
	/// @returns current value the iterator points to 
	reference  operator *() const;
	
	/// @returns pointer to the current value the iterator points to 
	pointer    operator ->() const;

	/** \returns the current position within the 2D grid with respect to the 
	    full size of the grid. 
	 */
	const C2DBounds& pos() const; 

	/// @cond NOFRIENDDOC
	template <typename T> friend
	bool operator == (const range2d_iterator<T>& left, const range2d_iterator<T>& right); 
	/// @endcond 

	/**
	   Return the internal iterator
	 */
	internal_iterator get_point(); 

	
	range2d_iterator_with_boundary_flag<I> with_boundary_flag() const;

	 /**
	    access elements relative to the iterator position 
	 */
	 const reference operator[] (int i) const;

private: 

	void increment_y(); 
	void increment_z(); 

	C2DBounds m_pos; 
	C2DBounds m_size; 
	C2DBounds m_begin; 
	C2DBounds m_end; 
	int m_xstride; 
	I m_iterator; 
}; 



template <typename I> 
template <typename AI>
range2d_iterator<I>& range2d_iterator<I>::operator = (const range2d_iterator<AI>& other)
{
	m_pos = other.m_pos; 
	m_size = other.m_size;  
	m_begin = other.m_begin; 
	m_end = other.m_end; 
	m_iterator = other.m_iterator; 
	m_xstride = other.m_xstride; 
	return *this; 
}

template <typename I> 
template <typename AI>
range2d_iterator<I>::range2d_iterator(const range2d_iterator<AI>& other):
	m_pos(other.m_pos), 
	m_size(other.m_size), 
	m_begin(other.m_begin), 
	m_end(other.m_end), 
	m_xstride(other.m_xstride),
	m_iterator(other.m_iterator)
{
}	




/**
   Compare two range iterators. There equivalence is only decided based on the grid position. 
 */

template <typename I> 
bool operator == (const range2d_iterator<I>& left, const range2d_iterator<I>& right)
{
	// we really want these two to the same range 
//	assert(left.m_size == right.m_size);
//	assert(left.m_begin == right.m_begin);
//	assert(left.m_end == right.m_end);

	return left.m_pos == right.m_pos; 

}

/**
   Compare two range iterators. There equivalence is only decided based on the grid position. 
 */
template <typename I> 
bool operator != (const range2d_iterator<I>& a, const range2d_iterator<I>& b)
{
	return !(a == b); 
}

template <typename I> 
const typename range2d_iterator<I>::reference
range2d_iterator<I>::operator[] (int i) const
{
	return m_iterator[i];
}


NS_MIA_END


namespace std {

template <typename I>
class iterator_traits< mia::range2d_iterator<I> > {
public: 
	typedef typename I::difference_type  difference_type; 
	typedef typename I::value_type	value_type; 
	typedef typename I::pointer	pointer; 
	typedef typename I::reference	reference; 
	typedef forward_iterator_tag	iterator_category; 
}; 

template <typename I>
class iterator_traits< mia::range2d_iterator_with_boundary_flag<I> > {
public: 
	typedef typename I::difference_type  difference_type; 
	typedef typename I::value_type	value_type; 
	typedef typename I::pointer	pointer; 
	typedef typename I::reference	reference; 
	typedef forward_iterator_tag	iterator_category; 
}; 

}

#endif
