/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2015 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/>.
 *
 */
	
NS_MIA_USE;
using namespace std; 

static char const *plugin_name = "rgg";
static const CStringOption param_map("map", "seed class map", "");
static const CFloatOption param_seed("seed", "threshold for seed probability", 0.9f, 0.0f, 1.0f); 


	
class C2DRGG: public C2DFilter {
	float m_seed; 
	CProbabilityVector m_pv;
	vector<T2DVector<int> >  m_env;
public:
	C2DRGG(float seed,const CProbabilityVector& pv):
		m_seed(seed), 
		m_pv(pv)
	{
		m_env.push_back(T2DVector<int>(-1,  0)); 
		m_env.push_back(T2DVector<int>( 1,  0)); 
		m_env.push_back(T2DVector<int>( 0, -1)); 
		m_env.push_back(T2DVector<int>( 0,  1)); 
	}
	
	template <class T>
	typename C2DRGG::result_type operator () (const T2DImage<T>& data) const ;

};


class C2DRGGImageFilter: public C2DImageFilterBase {
	C2DRGG m_filter; 
public:
	C2DRGGImageFilter(float seed,const CProbabilityVector& pv); 


	virtual P2DImage do_filter(const C2DImage& image) const;
};


class C2DRGGImageFilterFactory: public C2DFilterPlugin {
public: 
	C2DRGGImageFilterFactory();
	virtual C2DFilter *create(const CParsedOptions& options) const;
	virtual const string do_get_descr()const; 
};


template <class T>
typename C2DRGG::result_type C2DRGG::operator () (const T2DImage<T>& data) const
{
	
//	const bool is_integral = ::boost::is_integral<T>::value; 
	
	C2DUBImage *result = new C2DUBImage(data.get_size(), data); 
	
	// find seed segmentation 
	transform(data.begin(), data.end(), result->begin(), FMapClass(m_seed, m_pv)); 
	
	// evaluate all boundary pixels and get gradient
	
	C2DUBImage::iterator r = result->begin(); 
	
	vector<T2DVector<int> >::const_iterator ke = m_env.end(); 
	
	priority_queue<Contact<C2DBounds,T> > contacts; 
	
	typename T2DImage<T>::const_iterator di = data.begin(); 
	
	for (size_t y = 0; y < data.get_size().y; ++y)
		for (size_t x = 0; x < data.get_size().x; ++x, ++r, ++di) {
			if (*r != undefined) {
				vector<T2DVector<int> >::const_iterator kb = m_env.begin(); 
				while (kb != ke) {
					size_t ix = kb->x + x; 
					if (ix < data.get_size().x) {
						size_t iy = kb->y + y;
						if (iy < data.get_size().y) {
							if ((*result)(ix, iy) == undefined) {
								T p2 = data(ix, iy);
								Contact<C2DBounds,T> c(C2DBounds(ix, iy), *r, p2, *di > p2 ? *di - p2 : p2 - *di, 0);
								contacts.push(c);  
								//cvdebug() << "contact: " << c << "\n";
							}
						}
					}
					++kb; 
				}
			}
		}
	
	cvdebug() << "Have " << contacts.size() << " contact points\n"; 
	
        // run through the contacts, and add to the neighbor
	while (!contacts.empty()) {
		Contact<C2DBounds, T> c = contacts.top(); 
		contacts.pop(); 
		
		C2DUBImage::value_type& r = (*result)(c.l); 
		// it is possible, that this value is already set by a better neighbour
		if (r != undefined) 
			continue; 
		
		r = c.cl;
	
		vector<T2DVector<int> >::const_iterator kb = m_env.begin(); 
		while (kb != ke) {
			size_t ix = kb->x + c.l.x; 
			if (ix < data.get_size().x) {
				size_t iy = kb->y + c.l.y;
				if (iy < data.get_size().y) {
					if ((*result)(ix, iy) == undefined) {
						T p2 = data(ix, iy);
						Contact<C2DBounds,T> c_new(C2DBounds(ix, iy), c.cl, p2, 
									   c.value > p2 ? c.value - p2 : p2 - c.value, 0);
						contacts.push(c_new);  
						//cvdebug() << "contact: " << c_new << "\n"; 
					}
				}
			}
			++kb; 
		}
	}
		
	return P2DImage(result); 
}

C2DRGGImageFilter::C2DRGGImageFilter(float seed,const CProbabilityVector& pv):
	m_filter( seed, pv)
{
}

P2DImage C2DRGGImageFilter::do_filter(const C2DImage& image) const
{
	return wrap_filter(m_filter,image); 
}

C2DRGGImageFilterFactory::C2DRGGImageFilterFactory():
	C2DFilterPlugin(plugin_name)
{
	add_help(param_map);
	add_help(param_seed);
}

C2DFilter *C2DRGGImageFilterFactory::create(const CParsedOptions& options) const
{
	string map_name = param_map.get_value(options); 
	float seed_thresh = param_seed.get_value(options); 
	
	CProbabilityVector pv = ::load_probability_map(map_name); 
	if (pv.empty())
		throw invalid_argument(string("Unable to load probability map from ") + map_name); 
	
	
	
	return new C2DRGGImageFilter(seed_thresh, pv);
}

const string C2DRGGImageFilterFactory::do_get_descr()const
{
	return "2D region growing with probability based stopping funtion"; 
}

extern "C" EXPORT CPluginBase *get_plugin_interface()
{
	return new C2DRGGImageFilterFactory(); 
}
} // end namespace rgg_2dimage_filter
