/* -*- 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 <iomanip>
#include <limits>
#include <boost/static_assert.hpp>
#include <mia/3d/filter/morphological.hh>

#include <mia/core/threadedmsg.hh>
#include <mia/core/parallel.hh>

NS_BEGIN(morph_3dimage_filter)

NS_MIA_USE
using namespace std;
using namespace boost;
namespace bfs=boost::filesystem;


C3DDilate::C3DDilate(P3DShape shape, bool hint):
	m_shape(shape),
	m_more_dark(hint)

{
}

template <typename T>
struct __dispatch_dilate {
	static T3DImage<T> *apply(const T3DImage<T>& image, const C3DShape& shape, bool /*black*/) {
		const C3DBounds& size = image.get_size();

		T3DImage<T> *result = new T3DImage<T>(size, image);
		copy(image.begin(), image.end(), result->begin()); 
		
		auto run_slice = [&size, &image, &shape, &result](const C1DParallelRange& range) {
			for (auto z = range.begin(); z != range.end(); ++z){
				auto src_i = image.begin_at(0,0,z);
				auto res_i = result->begin_at(0,0,z);
				for (size_t y = 0; y < size.y; ++y)
					for (size_t x = 0; x < size.x; ++x,  ++src_i, ++res_i) {
						auto sb = shape.begin();
						auto  se = shape.end();
						
						while (sb != se) {
							C3DBounds nl(x + sb->x, y + sb->y, z + sb->z);
							if (nl < size) {
								T val = image(nl);
								if (*res_i < val )
									*res_i = val;
							}
							++sb;
							
						}
					}
			}
		};
		pfor(C1DParallelRange(0, image.get_size().z, 1), run_slice); 
		return result;
	}
};

template <>
struct __dispatch_dilate<bool> {
	static C3DBitImage *apply(const C3DBitImage& image, const C3DShape& shape, bool more_black) {

		const C3DBounds& size = image.get_size();
		C3DBitImage *result = 0;

		if (more_black) {
			result = new C3DBitImage(size, image);
			C3DBitImage::const_iterator src_i = image.begin();
			for (size_t z = 0; z < size.z; ++z)
				for (size_t y = 0; y < size.y; ++y)
					for (size_t x = 0; x < size.x; ++x,  ++src_i) {
						if ( *src_i ) {
							C3DShape::const_iterator sb = shape.begin();
							C3DShape::const_iterator se = shape.end();

							while (sb != se) {
								C3DBounds nl(x + sb->x, y + sb->y, z + sb->z);

								if (nl < size) {
									(*result)(nl) = true;
								}
								++sb;
							}
						}
					}
		}else {
			result = new C3DBitImage(image);
			C3DBitImage::iterator res_i = result->begin();
			for (size_t z = 0; z < size.z; ++z)
				for (size_t y = 0; y < size.y; ++y)
					for (size_t x = 0; x < size.x; ++x,  ++res_i) {
						if ( ! *res_i ) {

							C3DShape::const_iterator sb = shape.begin();
							C3DShape::const_iterator se = shape.end();

							while (sb != se) {
								C3DBounds nl(x - sb->x, y - sb->y, z - sb->z);
								if (nl < size) {
									if ( image(nl) ) {
										*res_i = true;
										break;
									}
								}
								++sb;
							}
						}
					}

		}
		return result;
	}
};

template <typename T>
typename C3DFilter::result_type C3DDilate::operator () (const T3DImage<T>& image)const
{
	return P3DImage(__dispatch_dilate<T>::apply(image, *m_shape, m_more_dark));
}

C3DFilter::result_type C3DDilate::do_filter (const C3DImage& image)const
{
	return ::mia::filter(*this, image);
}

C3DDilateFilterFactory::C3DDilateFilterFactory():
	C3DMorphFilterFactory("dilate")
{
}


C3DMorphFilterFactory::C3DMorphFilterFactory(const char *name):
	C3DFilterPlugin(name),
	m_hint("black")
{
	add_parameter("shape", make_param(m_shape, "sphere:r=2", false, "structuring element"));
	add_parameter("hint", new CStringParameter(m_hint, CCmdOptionFlags::none, "a hint at the main image content (black|white)"));
}


C3DFilter *C3DMorphFilterFactory::do_create()const
{
	bool bhint = true;

	if (m_hint == string("black"))
		bhint = true;
	else if (m_hint == string("white"))
		bhint = false;
	else
		throw invalid_argument(string("hint '") + m_hint + string("' not supported"));
	return dodo_create(m_shape, bhint);
}


C3DFilter *C3DDilateFilterFactory::dodo_create(P3DShape shape, bool bhint) const
{
	return new C3DDilate(shape, bhint);
}

const string C3DDilateFilterFactory::do_get_descr()const
{
	return "3d image stack dilate filter";
}

C3DErode::C3DErode(P3DShape shape, bool hint):
	m_shape(shape),
	m_more_dark(hint)

{
}

template <typename T>
struct __dispatch_erode {
	static T3DImage<T> *apply(const T3DImage<T>& image, const C3DShape& shape, bool /*black*/) {
		const C3DBounds size = image.get_size();

		T3DImage<T> *result = new T3DImage<T>(size, image);
		copy(image.begin(), image.end(), result->begin()); 
		
		auto run_slice = [&size, &image, &shape, &result](const C1DParallelRange& range) {
			for (auto z = range.begin(); z != range.end(); ++z){
				auto src_i = image.begin_at(0,0,z);
				auto res_i = result->begin_at(0,0,z);
				for (size_t y = 0; y < size.y; ++y)
					for (size_t x = 0; x < size.x; ++x,  ++src_i, ++res_i) {
						auto  sb = shape.begin();
						auto  se = shape.end();
						
						while (sb != se) {
							C3DBounds nl(x + sb->x, y + sb->y, z + sb->z);
							if (nl < size) {
								T val = image(nl);
								if (*res_i > val )
									*res_i = val;
							}
							++sb;
						}
					}
			}
		}; 
		pfor(C1DParallelRange(0, image.get_size().z, 1), run_slice); 
		return result;
	}
};

template <>
struct __dispatch_erode<bool> {
	static C3DBitImage *apply(const C3DBitImage& image, const C3DShape& shape, bool more_black) {

		const C3DBounds& size = image.get_size();
		C3DBitImage *result = 0;

		if (more_black) {
			result = new C3DBitImage(image);
			C3DBitImage::iterator res_i = result->begin();
			for (size_t z = 0; z < size.z; ++z)
				for (size_t y = 0; y < size.y; ++y)
					for (size_t x = 0; x < size.x; ++x,  ++res_i) {
						if ( *res_i ) {

							C3DShape::const_iterator sb = shape.begin();
							C3DShape::const_iterator se = shape.end();

							while (sb != se) {
								C3DBounds nl(x - sb->x, y - sb->y, z - sb->z);
								if (nl < size) {
									if ( !image(nl) ) {
										*res_i = false;
										break;
									}
								}
								++sb;
							}
						}
					}
		}else {
			result = new C3DBitImage(size, image);
			fill(result->begin(), result->end(), true);
			C3DBitImage::const_iterator src_i = image.begin();
			for (size_t z = 0; z < size.z; ++z)
				for (size_t y = 0; y < size.y; ++y)
					for (size_t x = 0; x < size.x; ++x,  ++src_i) {

						if ( !*src_i ) {
							C3DShape::const_iterator sb = shape.begin();
							C3DShape::const_iterator se = shape.end();

							while (sb != se) {
								C3DBounds nl(x + sb->x, y + sb->y, z + sb->z);

								if (nl < size) {
									(*result)(nl) = false;
								}
								++sb;
							}
						}
					}


		}
		return result;
	}
};

template <typename T>
typename C3DFilter::result_type C3DErode::operator () (const T3DImage<T>& image) const
{
	return P3DImage(__dispatch_erode<T>::apply(image, *m_shape, m_more_dark));
}

mia::P3DImage C3DErode::do_filter (const C3DImage& image)const
{
	return ::mia::filter(*this, image);
}

C3DErodeFilterFactory::C3DErodeFilterFactory():
	C3DMorphFilterFactory("erode")
{
}

C3DFilter *C3DErodeFilterFactory::dodo_create(P3DShape shape, bool bhint) const
{
	return new C3DErode(shape, bhint);
}

const string C3DErodeFilterFactory::do_get_descr()const
{
	return "3d image stack erode filter";
}

C3DOpenClose::C3DOpenClose(P3DShape shape, bool hint, bool open):
	m_erode(shape, hint),
	m_dilate(shape, hint),
	m_open(open)
{
}

P3DImage C3DOpenClose::do_filter(const C3DImage& src) const
{
	if (m_open) {
		P3DImage tmp = m_erode.filter(src);
		return m_dilate.filter(*tmp);
	}else{
		P3DImage tmp = m_dilate.filter(src);
		return m_erode.filter(*tmp);
	}
}


C3DOpenFilterFactory::C3DOpenFilterFactory():
	C3DMorphFilterFactory("open")
{
}

C3DFilter *C3DOpenFilterFactory::dodo_create(P3DShape shape, bool hint) const
{
	return new C3DOpenClose(shape, hint, true);
}

const string C3DOpenFilterFactory::do_get_descr()const
{
	return "morphological open";
}

C3DCloseFilterFactory::C3DCloseFilterFactory():
	C3DMorphFilterFactory("close")
{
}

C3DFilter *C3DCloseFilterFactory::dodo_create(P3DShape shape, bool hint)const
{
	return new C3DOpenClose(shape, hint, false);
}

const string C3DCloseFilterFactory::do_get_descr()const
{
	return "morphological close";
}


extern "C" EXPORT CPluginBase *get_plugin_interface()
{
	CPluginBase *p = new C3DErodeFilterFactory();
	p->append_interface(new C3DDilateFilterFactory());
	p->append_interface(new C3DOpenFilterFactory());
	p->append_interface(new C3DCloseFilterFactory());
	return p;
}


NS_END
