/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2016 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/3d/imageio.hh>
#include <mia/3d/filter/mask.hh>


NS_BEGIN(mask_3dimage_filter)
NS_MIA_USE;
using namespace std;

C3DMask::C3DMask(const C3DImageDataKey& image_key):
	m_image_key(image_key)
{
}

template <typename T>
struct __ifthenelse {
	T operator() ( bool yes, T value) const {
		return yes ? value : T();
	}
};

template <typename T>
struct __dispatch_mask {
	static P3DImage apply(const T3DImage<T> */*mask*/, const C3DImage& /*data*/) {
		throw invalid_argument("one of the input image must be binary");
	}
};

class C3DMaskDispatch : public TFilter< P3DImage > {
public:
	C3DMaskDispatch(const C3DBitImage *mask, bool use_data_attr):
		m_mask(mask), 
		m_use_data_attr(use_data_attr)
		{
		}

	template <typename T>
	C3DMaskDispatch::result_type operator () (const mia::T3DImage<T>& data) const 	{
		
		T3DImage<T> *result; 
		if (m_use_data_attr) 
			result = new T3DImage<T>(data.get_size(),data);
		else 
			result = new T3DImage<T>(data.get_size(),*m_mask);

		transform(m_mask->begin(), m_mask->end(), data.begin(),  result->begin(),
			  __ifthenelse<T>());
		return C3DMask::result_type(result);
	}
private:
	const C3DBitImage *m_mask;
	bool m_use_data_attr; 
};


template <>
struct __dispatch_mask<bool> {
	static P3DImage apply(const C3DBitImage *mask, const C3DImage& data) {
		if (data.get_size() != mask->get_size()) {
			throw invalid_argument("Mask: input image and mask must be of same size");
		}

		C3DMaskDispatch m(mask, false);
		return mia::filter(m, data);
	}
};

template <typename T>
C3DMask::result_type C3DMask::operator () (const T3DImage<T>& data) const
{
	C3DImageIOPlugin::PData in_image_list = m_image_key.get();

	if (!in_image_list || in_image_list->empty())
		throw invalid_argument("C3DMaskImage: no image available in data pool");

	P3DImage image = (*in_image_list)[0];

	if (image->get_pixel_type() == it_bit) {
		// use static cast because type is already tested. 
		const C3DBitImage *mask = static_cast<const C3DBitImage*>(image.get());
		C3DMaskDispatch m(mask, true);
		return m(data);
	} else {
		return __dispatch_mask<T>::apply(&data, *image);
	}
}

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

C3DMaskImageFilterFactory::C3DMaskImageFilterFactory():
	C3DFilterPlugin("mask")
{
	add_parameter("input", new CStringParameter(m_mask_filename, CCmdOptionFlags::required_input, "second input image file name", 
			      &C3DImageIOPluginHandler::instance()));
}

C3DFilter *C3DMaskImageFilterFactory::do_create()const
{
	C3DImageDataKey mask_data = C3DImageIOPluginHandler::instance().load_to_pool(m_mask_filename);
	return new C3DMask(mask_data);
}

const std::string C3DMaskImageFilterFactory::do_get_descr()const
{
	return "Mask an image, one image is taken from the parameters list "
                "and the other from the normal filter input. Both images must be of the same dimensions "
		"and one must be binary. The attributes of the image coming through the filter pipeline "
		"are preserved. The output pixel type corresponds to the input image that is not binary.";
}

extern "C" EXPORT CPluginBase *get_plugin_interface()
{
	return new C3DMaskImageFilterFactory();
}
NS_END
