/* -*- 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/export_handler.hh>

#include <mia/2d/imageio.hh>
#include <mia/core/ioplugin.cxx>
#include <mia/core/iohandler.cxx>
#include <mia/core/errormacro.hh>
#include <mia/core/tools.hh>
#include <mia/core/attribute_names.hh>

NS_MIA_BEGIN

using namespace std; 


const char *io_2dimage_type::data_descr = "2dimage";

C2DImageVector *C2DImageVector::clone() const
{
	return new C2DImageVector(*this);
}

C2DImageIOPPH::C2DImageIOPPH()
{
	C2DFVectorTranslator::register_for("pixel");
}


template <> const char *  const 
TPluginHandler<C2DImageIOPlugin>::m_help =  
   "These plug-ins handle loading and storing of 2D images to the supported image file types.";


P2DImageVector EXPORT_2D create_image2d_vector(P2DImage image)
{
	P2DImageVector out_list_ptr(new C2DImageIOPluginHandler::Instance::Data);
	out_list_ptr->push_back(image);
	return out_list_ptr;
}

P2DImage  EXPORT_2D load_image2d(const std::string& filename)
{
	C2DImageIOPluginHandler::Instance::PData  in_image_list =
		C2DImageIOPluginHandler::instance().load(filename);

	if (!in_image_list || in_image_list->empty()) {
		throw create_exception<runtime_error>( filename, " no image  found");
	}
	if (in_image_list->size() > 1) {
		cvwarn() << "load_image2d:'" << filename
			 << "' more then one image found, loading only first one\n";
	}

	return *in_image_list->begin();
}

bool  EXPORT_2D save_image(const std::string& filename, P2DImage image)
{
	C2DImageIOPluginHandler::Instance::Data out_image_list;
	out_image_list.push_back(image);
	return C2DImageIOPluginHandler::instance().save(filename, out_image_list);
}

bool  EXPORT_2D save_image(const std::string& filename, C2DImage& image)
{
	return save_image(filename, P2DImage(&image, void_destructor<C2DImage>())); 
}

C2DImageGroupedSeries  EXPORT_2D load_image_series(const std::vector<std::string>& filenames, 
						   CProgressCallback *cb)
{
	typedef map<int, C2DImageSeries> C2DImageSeriesGroupHelper; 
	typedef map<int, C2DImageSeriesGroupHelper> C2DGroupSeriesHelper; 
	typedef map<string, C2DGroupSeriesHelper> C2DImageGroupedSeriesHelper; 


	C2DImageGroupedSeriesHelper collector; 
	const static string unknown_protocol("Unknown"); 
	
	int step = 0; 
	if (cb) 
		cb->set_range(filenames.size()); 

	cvdebug() << "Load " << filenames.size() << "  images\n"; 
	for (auto f = filenames.begin(); f != filenames.end(); ++f, ++step) {
		// give some feedback 
		cvdebug() << "Load " << *f << "\n"; 
		if (cb && ! (step & 0x1f) )
			cb->update(step); 
		
		C2DImageIOPluginHandler::Instance::PData  in_image_list =
			C2DImageIOPluginHandler::instance().load(*f);
		if (!in_image_list) {
			cverr() << "load_image_series: File '" << *f 
				<< "' doesn't provide images this software can read\n"; 
			continue; 
		}
		for (auto i = in_image_list->begin(); i != in_image_list->end(); ++i) {
			// look for protocol attribute 
			auto protocol = (*i)->get_attribute_as_string(IDProtocolName); 
			std::string key = protocol.empty() ? unknown_protocol : protocol;
			if (collector.find(key) == collector.end())
				collector[key] = C2DGroupSeriesHelper(); 
			C2DGroupSeriesHelper& group = collector[key]; 

			// look for series number 
			auto seriesnr = (*i)->get_attribute(IDSeriesNumber);
			const CIntAttribute *psn = dynamic_cast<const CIntAttribute *>(seriesnr.get());
			int saqnr = -1; 
			if (psn) 
				saqnr = *psn; 

			if (group.find(saqnr) == group.end())
				group[saqnr] = C2DImageSeriesGroupHelper(); 
			C2DImageSeriesGroupHelper& subgroup = group[saqnr]; 

			// look for acquisition number 
			auto attr = (*i)->get_attribute(IDAcquisitionNumber); 
			const CIntAttribute *paq = dynamic_cast<const CIntAttribute *>(attr.get());
			int aqnr = -1; 
			if (paq) 
				aqnr = *paq; 
			if (subgroup.find(aqnr) == subgroup.end()) 
				subgroup[aqnr] = C2DImageSeries(); 
			
			// look for slice location number, if non exists fake one
			attr = (*i)->get_attribute(IDSliceLocation);
			const CFloatAttribute *pinst = dynamic_cast<const CFloatAttribute *>(attr.get());
			float location; 
			if (!pinst) {
				location = 0.0; 
				(*i)->set_attribute(IDSliceLocation, PAttribute(new CFloatAttribute(location))); 
			} else 
				location = floor(1000.0 * *pinst) / 1000.0; 
			
			subgroup[aqnr].push_back(*i); 
			cvinfo() << "Add '" <<* f  
				  << "' to Protocol group '" << protocol
				  << "' with acquisition no. " << aqnr 
				  << "' and location no. " << location << "\n"; 
		}
	}
	// now sort the slices and move the groups to a zero-index based vector 
	C2DImageGroupedSeries result; 
	for (auto g = collector.begin(); g != collector.end(); ++g) {
		cvinfo() << "Protocol '" << g->first << "'\n"; 
		C2DImageSeriesGroup group; 
		for (auto saq = g->second.begin(); saq != g->second.end(); ++saq) {
			for (auto aq = saq->second.begin(); aq != saq->second.end(); ++aq) {
				sort(aq->second.begin(), aq->second.end(), 
				     [](const P2DImage& lhs, const P2DImage& rhs) {
					     const auto lhs_attr = lhs->get_attribute(IDSliceLocation);
					     const auto rhs_attr = rhs->get_attribute(IDSliceLocation);
					     const float lhs_inr = dynamic_cast<const CFloatAttribute&>(*lhs_attr); 
					     const float rhs_inr = dynamic_cast<const CFloatAttribute&>(*rhs_attr); 
					     return lhs_inr < rhs_inr; 
				     }); 
				cvinfo() << "  Acquisition " << aq->first << " with "<< aq->second.size() <<" slices\n"; 
				group.push_back(aq->second); 
			}
			result[g->first] = group; 
		}
	}
	return result; 
}

template class TPlugin<io_2dimage_type, io_plugin_type>; 
template class TIOPlugin<io_2dimage_type>;
template class THandlerSingleton<C2DImageIOPPH>;
template class TIOPluginHandler<C2DImageIOPlugin>;
template class TPluginHandler<C2DImageIOPlugin>;

NS_MIA_END
