/* -*- 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 <sstream>
#include <ostream>
#include <fstream>
#include <mia/core.hh>
#include <mia/2d.hh>

NS_MIA_USE;
using namespace std;

const SProgramDescription g_description = {
	
	{pdi_group, 	"Processing of series of 2D images in a 3D fashion (out-of-core)"}, 
	{pdi_short, "histogram of a series of 2D images"}, 
	
	
	{pdi_description, "This program evaluates the histogram over a series of 2D images"}, 
	
	
	{pdi_example_descr,"Evaluate the histogram over all consecutively numbered images that follow the "
	 "numbering pattern inputXXXX.exr and store the output histogram in histo.txt. "
	 "Use the range [0,256] and 64 bins."}, 
	
	
	{pdi_example_code, "-i input0000.exr -o histo.txt --min 0 --max 256 --bins 64"}
}; 

class CAutoHistogramAccumulator : public TFilter<bool> {
public: 
	template <typename T>
	bool operator () (const T2DImage<T>& image) {
		for(auto i = image.begin(); i != image.end(); ++i) {
			auto slot = m_hist.find(*i);
			if (slot == m_hist.end()) {
				m_hist[*i] = 1; 
			}else{
				++m_hist[*i];
			}
		}
		return true;
	}

	bool save(const string& fname)const
	{
		ofstream file(fname.c_str());
		for (auto ih :m_hist) {
			file << ih.first << " " << ih.second << "\n";
		}
		return file.good();
	}

	int propose_threshold()const {
		assert(!m_hist.empty());
		
		float min_val = m_hist.begin()->first; 
		float max_val = m_hist.rbegin()->first; 

		float down_start_search = (max_val - min_val) / 10 + min_val; 
		float up_end_search = (max_val - min_val) / 6 + min_val; 

		auto i = m_hist.begin(); 
		
		while (i->first < down_start_search) 
			++i; 
		
		auto val = i->second;
		
		auto pos = i; 
		while (i !=  m_hist.begin()) {
			if (val > i->second) {
				val = i->second; 
				pos = i; 
			}
			--i; 
		}
		
		while (i->first < up_end_search) {
			if (val > i->second) {
				val = i->second; 
				pos = i; 
			}
			++i; 
		}
		
		return pos->first; 
	}
	
private: 
	map<float, size_t> m_hist; 
}; 

class CHistAccumulator : public TFilter<bool> {

public:
	CHistAccumulator(float min, float max, size_t bins):
		m_histo(THistogramFeeder<float>(min, max, bins)), 
		m_last(0)
	{
	}

	template <typename T>
	bool operator () (const T2DImage<T>& image) {
		for(auto i = image.begin(); i != image.end(); ++i)
			m_histo.push(*i);
		return true;
	}

	void resize() {
		for (size_t i = 0; i < 	m_histo.size(); ++i) {
			if (m_histo[i] > 0 )
				m_last = i;
		}
	}
			
	bool save(const string& fname)const
	{
		ofstream file(fname.c_str());
		for (size_t i = 0; i < 	m_last; ++i) {
			const THistogram<THistogramFeeder<float > >::value_type v = m_histo.at(i);
			file << v.first << " " << v.second << "\n";
		}
		return file.good();
	}

	int propose_threshold()const {
		int pos = m_last / 10; 
		int max_pos = m_last / 6; 
		double val = m_histo[pos];
		
		for (int i = pos; i > 10; --i)
			if (m_histo[i] < val) {
				pos = i; 
				val = m_histo[i]; 
			}
		
		for (int i = pos; i < max_pos; ++i)
			if (m_histo[i] < val) {
				pos = i; 
				val = m_histo[i]; 
			}
		return pos; 
	}
private:
	THistogram<THistogramFeeder<float > > m_histo;
	size_t m_last; 
};



int do_main( int argc, char *argv[] )
{
	float hmin = 0;
	float hmax = 65535;
	size_t bins = 65536;

	string out_filename;
	string in_filename;
		
	const C2DImageIOPluginHandler::Instance& imageio = C2DImageIOPluginHandler::instance();

	CCmdOptionList options(g_description);
	options.add(make_opt( in_filename, "in-file", 'i', "input image(s) to be filtered", 
			      CCmdOptionFlags::required_input, &imageio));
	options.add(make_opt( out_filename, "out-histogram", 'o', "output file name", 
			      CCmdOptionFlags::required_output));
	options.add(make_opt( hmin, "min", 0, "minimum of histogram range"));
	options.add(make_opt( hmax, "max", 0, "maximum of histogram range"));
	options.add(make_opt( bins, "bins", 0, "number of histogram bins, set to zero to create a bin for "
			      "each intensity value available in the input data. In this case the histogram "
			      "range is also evaluated automatically"));
		
	if (options.parse(argc, argv) != CCmdOptionList::hr_no)
		return EXIT_SUCCESS; 




	size_t start_filenum = 0;
	size_t end_filenum  = 0;
	size_t format_width = 0;

	string src_basename = get_filename_pattern_and_range(in_filename, start_filenum, end_filenum, format_width);
		
	if (start_filenum >= end_filenum)
		throw invalid_argument(string("no files match pattern ") + src_basename);

	if (bins > 0) {
		CHistAccumulator histo_accu(hmin, hmax, bins);
		for (size_t i = start_filenum; i < end_filenum; ++i) {
			string src_name = create_filename(src_basename.c_str(), i);
			C2DImageIOPluginHandler::Instance::PData  in_image_list = imageio.load(src_name);
			cvmsg() << "Read:" << src_name << "\r";
			if (in_image_list.get() && in_image_list->size()) {
				for (auto k = in_image_list->begin(); k != in_image_list->end(); ++k)
					accumulate(histo_accu, **k);
			}
		}
		cvmsg() << "\n";
		
		histo_accu.resize(); 
		
		if (!histo_accu.save(out_filename))
			throw runtime_error(string("Error writing output file:") + out_filename);
		cout << histo_accu.propose_threshold(); 
		
	}else{
		CAutoHistogramAccumulator histo; 
		for (size_t i = start_filenum; i < end_filenum; ++i) {
			string src_name = create_filename(src_basename.c_str(), i);
			C2DImageIOPluginHandler::Instance::PData  in_image_list = imageio.load(src_name);
			cvmsg() << "Read:" << src_name << "\r";
			if (in_image_list.get() && in_image_list->size()) {
				for (auto k = in_image_list->begin(); k != in_image_list->end(); ++k)
					accumulate(histo, **k);
			}
		}
		cvmsg() << "\n";
		
		
		if (!histo.save(out_filename))
			throw runtime_error(string("Error writing output file:") + out_filename);
		
		cout << histo.propose_threshold(); 
	}
	
	return EXIT_SUCCESS;

}
#include <mia/internal/main.hh>
MIA_MAIN(do_main); 
