/* -*- 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/core/filter.hh>
#include <mia/core/msgstream.hh>
#include <mia/core/threadedmsg.hh>
#include <mia/core/parallel.hh>

#include <mia/2d/filter/medianmad.hh>
#include <mia/2d/imageio.hh>

#include <limits>
#include <cmath>

#include <gsl/gsl_cblas.h>

NS_BEGIN(medianmad_2dimage_filter);

NS_MIA_USE;
using namespace std;

C2DMedianMad::C2DMedianMad(unsigned hw, double thresh, const std::string& madfilename):
	m_hw(hw),
	m_thresh(thresh),
	m_madfilename(madfilename)
{
}

template <typename T>
std::pair<T, T> evaluate_pixel(const T2DImage<T>& data,
					 unsigned x, unsigned y, unsigned hw, double thresh, vector<T>& buffer)
{
	unsigned startx = x - hw;
	if (startx > data.get_size().x)
		startx = 0;

	unsigned endx = x + hw + 1;
	if (endx > data.get_size().x)
		endx = data.get_size().x;

	unsigned starty = y - hw;
	if (starty > data.get_size().y)
		starty = 0;

	unsigned endy = y + hw + 1;
	if (endy > data.get_size().y)
		endy = data.get_size().y;


	std::pair<T, T> result = {0, 0};

	float orig_value = data(x,y);

	if (orig_value >=  thresh) {
		int n = 0; 
		auto i = buffer.begin(); 
		for (unsigned iy = starty; iy < endy; ++iy) {
			for (unsigned ix = startx; ix < endx; ++ix) {
				double v = data(ix, iy);
				if (v >= thresh) {
					*i++ = v;
					++n; 
				}
			}
		}
		if (i != buffer.begin()) {
			sort(buffer.begin(), i);
			if (n & 1) 
				result.first = buffer[n / 2];
			else
				result.first = static_cast<T>(0.5 * (buffer[n/2] + buffer[n/2 - 1]));

			transform (buffer.begin(), i, buffer.begin(),
				   [result](T x){ return x >  result.first ? x - result.first : result.first - x;});
			sort(buffer.begin(), i);
			if (n & 1) 
				result.second = buffer[n / 2];
			else
				result.second = static_cast<T>(0.5 * (buffer[n/2] + buffer[n/2 - 1]));
			
		}else{
			result.first = orig_value; 
		}
	}
	return result; 
}

template <typename T>
C2DMedianMad::result_type C2DMedianMad::operator () (const T2DImage<T>& data) const
{
	TRACE_FUNCTION;

	cvdebug() << "Running 2D MedianMad with hw=" << m_hw
		  << ", thresh=" << m_thresh
		  << ", and madfile=" << m_madfilename
		  << "\n";

	T2DImage<T> *result_mu = new T2DImage<T>(data.get_size(), data);
	T2DImage<T> *result_sigma = new T2DImage<T>(data.get_size(), data);

	P2DImage presult_sigma(result_sigma);
	
	auto evaluate_row = [&](const C1DParallelRange& range){
		int size = 2 * m_hw + 1; 
		vector<T> buffer(size * size); 
		for (int y = range.begin(); y != range.end(); ++y){
			auto omu = result_mu->begin_at(0,y);
			auto osigma = result_sigma->begin_at(0,y);
			for (unsigned x = 0; x < data.get_size().x; ++x, ++omu, ++osigma){
				auto r = evaluate_pixel(data, x, y, m_hw, m_thresh, buffer);
				*omu = r.first;
				*osigma = r.second; 
			}
		}
	};

	pfor(C1DParallelRange(0, data.get_size().y, 1), evaluate_row);
	
	if (!m_madfilename.empty()) {
		if (!save_image(m_madfilename, presult_sigma)) {
			cverr() << "C2DMedianMad: Unable to save median absolute distance data to '" << m_madfilename <<"'"; 
		}
	
	}
	return P2DImage(result_mu);
}

P2DImage C2DMedianMad::do_filter(const C2DImage& image) const
{
	return mia::filter(*this, image);
}

C2DMedianMadImageFilterFactory::C2DMedianMadImageFilterFactory():
	C2DFilterPlugin("medianmad"),
	m_hw(1),
	m_thresh(0.0)
{
	add_parameter("w", make_lc_param(m_hw, 1, false, "filter width parameter"));
	add_parameter("thresh", make_lc_param(m_thresh, 0.0, false, "Intensity thresholding parameter: Pixels with intensities "
					      "below this threshold will be set to zero, and also not used when evaluating mean "
					      "and variation"));
	add_parameter("madfile", new CStringParameter(m_madfilename, CCmdOptionFlags::required_output,
						      "name of the output file to save the median absolute deviation image too.", 
						      &C2DImageIOPluginHandler::instance()));
}

C2DFilter *C2DMedianMadImageFilterFactory::do_create()const
{
	return new C2DMedianMad(m_hw, m_thresh, m_madfilename);
}

const string C2DMedianMadImageFilterFactory::do_get_descr()const
{
	return "Filter that evaluates simultaniously the pixel wise median and the median absolute deviation (MAD) of an image"
		" in a given window. Pixel intensities below the given threshold will be ignored "
		"and at their loctions the output median and MAD are set to zero. The median "
		"intensity image is directly passed to the pipeline, the variation image is "
		"saved to a file given with the varfile parameter. Both output images have the same pixel type like the input image.";
}

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

NS_END
