/* -*- 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/>.
 *
 */

#include <mia/2d/transform.hh>
#include <mia/2d/deformer.hh>

NS_MIA_BEGIN
using namespace std; 


C2DTransformation::C2DTransformation(const C2DInterpolatorFactory& ipf):
	Transformation<C2DImage, C2DInterpolatorFactory>(ipf)
{

}

void C2DTransformation::set_creator_string(const std::string& s)
{
	m_creator_string = s; 
}


const std::string& C2DTransformation::get_creator_string()const
{
	return m_creator_string; 
}

C2DTransformation *C2DTransformation::clone() const
{
	C2DTransformation *result = do_clone(); 
	if (result) 
		result->set_creator_string(get_creator_string()); 
	return result; 
}

C2DTransformation::Pointer C2DTransformation::upscale(const C2DBounds& size) const
{
	Pointer result = do_upscale(size); 
	if (result) 
		result->set_creator_string(get_creator_string()); 
	return result;	
}


C2DTransformation::iterator_impl::iterator_impl():
	m_pos(0,0), 
	m_size(0,0)
{
}

C2DTransformation::iterator_impl::iterator_impl(const C2DBounds& pos, const C2DBounds& size):
	m_pos(pos), 
	m_size(size)
{
}

void C2DTransformation::iterator_impl::increment()
{
	++m_pos.x;
	if (m_pos.x < m_size.x)
		do_x_increment();
	else {
		if (m_pos.y < m_size.y) {
			++m_pos.y;
			if (m_pos.y < m_size.y)
				m_pos.x = 0;
			else 
				return ; 
			do_y_increment();
		}
	}
}

void C2DTransformation::iterator_impl::advance(unsigned int delta)
{
	unsigned int delta_x_max = m_size.x - m_pos.x; 
	if (delta < delta_x_max) {
		m_pos.x += delta; 
		do_x_increment();
		return; 
	}
	++m_pos.y; 
	delta -= delta_x_max; 
	m_pos.x = 0; 
	
	if (delta) {
		m_pos.x += delta % m_size.x; 
		m_pos.y += delta / m_size.x;
	}
	if (m_pos.y < m_size.y) 
		do_y_increment();
#ifndef NDEBUG
	else if (m_pos.y == m_size.y) 
		cvdebug() << "C2DTransformation::iterator_impl::advance: iterator at end\n"; 
	else 
		cvwarn() << "C2DTransformation::iterator_impl::advance: iterator past end\n"; 
#endif	
}


C2DTransformation::const_iterator& C2DTransformation::const_iterator::operator += (unsigned int delta)
{
	m_holder->advance(delta); 
	return *this; 
}

void C2DTransformation::const_iterator::print(ostream& os) const 
{
	m_holder->print(os); 
}

const C2DBounds& C2DTransformation::iterator_impl::get_pos()const
{
	return m_pos; 
}

void C2DTransformation::iterator_impl::print(ostream& os) const 
{
	os << "Iterator[pos=" << m_pos << " of " << m_size <<"]"; 
}

const C2DBounds& C2DTransformation::iterator_impl::get_size()const
{
	return m_size; 
}


const C2DFVector&  C2DTransformation::iterator_impl::get_value() const
{
	return do_get_value(); 
}

bool C2DTransformation::iterator_impl::operator == (const C2DTransformation::iterator_impl& b) const
{
	assert(m_size == b.m_size); 
	return m_pos == b.m_pos || (m_pos.y == m_size.y && b.m_pos.y == b.m_size.y); 
}

EXPORT_2D bool operator == (const C2DTransformation::const_iterator& a, 
			    const C2DTransformation::const_iterator& b)
{
	return *a.m_holder == *b.m_holder; 
}


EXPORT_2D bool operator != (const C2DTransformation::const_iterator& a, 
			    const C2DTransformation::const_iterator& b)
{
	return !(a == b); 
}


C2DTransformation::const_iterator::const_iterator():
	m_holder((iterator_impl*)0)
{
}

C2DTransformation::const_iterator::const_iterator(iterator_impl * holder):
	m_holder(holder)
{
}

C2DTransformation::const_iterator::const_iterator(const const_iterator& other):
	m_holder(other.m_holder->clone())
{
}

C2DTransformation::const_iterator& 
C2DTransformation::const_iterator::operator = (const const_iterator& other)
{
	m_holder.reset(other.m_holder->clone()); 
	return *this; 
}

C2DTransformation::const_iterator& C2DTransformation::const_iterator::operator ++()
{
	m_holder->increment(); 
	return *this; 
}

C2DTransformation::const_iterator C2DTransformation::const_iterator::operator ++(int)
{
	auto old = m_holder->clone(); 
	++(*this); 
	return C2DTransformation::const_iterator(old); 
}

const C2DFVector&  C2DTransformation::const_iterator::operator *() const
{
	assert(m_holder); 
	return m_holder->get_value(); 
}

const C2DFVector  *C2DTransformation::const_iterator::operator ->() const
{
	assert(m_holder); 
	return &m_holder->get_value(); 
}

const C2DBounds& C2DTransformation::const_iterator::pos() const
{
	assert(m_holder); 
	return m_holder->get_pos(); 
}

const C2DBounds& C2DTransformation::const_iterator::get_size() const
{
	assert(m_holder); 
	return m_holder->get_size(); 
}


bool C2DTransformation::refine()
{
	return false; 
}

C2DBounds C2DTransformation::get_minimal_supported_image_size() const
{
	return C2DBounds::_1;
}

/**
   @brief Helper functor for 2D image transformations 
   
   Helper Functor to evaluate a transformed image by applying a given 
   transformation and using the provided interpolator type
*/

struct F2DTransform : public TFilter<P2DImage> {

	/**
	   Construtor 
	   @param ipf interpolation factory to use 
	   @param trans tranformation to be applied 
	 */
	F2DTransform(const C2DInterpolatorFactory& ipf, const C2DTransformation& trans):
		m_ipf(ipf),
		m_trans(trans)
		{
		}
	
	template <typename T>
	P2DImage operator ()(const T2DImage<T>& image) const {
		T2DImage<T> *timage = new T2DImage<T>(m_trans.get_size(), image);
		
		unique_ptr<T2DInterpolator<T>> interp(m_ipf.create(image.data()));
		
		auto r = timage->begin();
		auto v = m_trans.begin();
		
		for (size_t y = 0; y < image.get_size().y; ++y)
			for (size_t x = 0; x < image.get_size().x; ++x, ++r, ++v) {
				*r = (*interp)(*v);
			}
		return P2DImage(timage);
	}
private:
	const C2DInterpolatorFactory& m_ipf;
	const C2DTransformation& m_trans;
};

P2DImage C2DTransformation::do_transform(const C2DImage& image, const C2DInterpolatorFactory& ipf) const
{
	return mia::filter(F2DTransform(ipf, *this), image);
}


const char *C2DTransformation::data_descr = "2dtransform";
const char *C2DTransformation::dim_descr = "2dimage"; 

NS_MIA_END
