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

NS_BEGIN( labelscale_2dimage_filter)

using namespace mia; 
using std::vector; 
using std::pair; 
using std::make_pair; 
using std::invalid_argument; 

C2DLabelscale::C2DLabelscale(const C2DBounds& out_size):
        m_out_size(out_size)
{
}

inline pair<unsigned, unsigned> clamp_coord(unsigned i, unsigned di, unsigned ni) 
{
	unsigned first = i * di; 
	unsigned second = first +  di; 
	return make_pair(first, second > ni? ni: second); 
}; 


template <typename  T>
T get_max_represented(const vector<T>& buffer)
{
        T max_elm = T(); 
        if (buffer.empty()) 
                return max_elm; 
        unsigned int max_num = 1; 
        unsigned int cur_num = 1; 
        
        max_elm = buffer[0]; 
	
        T cur_elm = max_elm; 
        
        for (auto ib = buffer.begin() + 1; ib != buffer.end() ; ++ib) {
                if (cur_elm == *ib) {
                        ++cur_num; 
                }else{
                        if (cur_num > max_num) {
                                max_num = cur_num; 
                                max_elm = cur_elm; 
                        }
                        cur_num = 1; 
                        cur_elm = *ib; 
                }
        }
        if (cur_num > max_num) {
                max_elm = cur_elm; 
        }
        return max_elm; 
}; 

template <typename  T>
typename C2DLabelscale::result_type C2DLabelscale::operator () (const T2DImage<T>& data) const
{
        T2DImage<T> *result = new T2DImage<T>(m_out_size, data); 

        C2DBounds block_size = C2DBounds::_1; 
        C2DFVector coordinate_scale = C2DFVector::_1; 

        if (m_out_size.x  < data.get_size().x ){
                block_size.x = (data.get_size().x + m_out_size.x - 1) / m_out_size.x; 
        }else{
                coordinate_scale.x = float(data.get_size().x) / float(m_out_size.x); 
        }

                
        if (m_out_size.y  < data.get_size().y ) {
                block_size.y = (data.get_size().y + m_out_size.y - 1) / m_out_size.y;
        }else{
                coordinate_scale.y = float(data.get_size().y) / float(m_out_size.y); 
        }
        
        vector<T> buffer; 
        buffer.reserve(block_size.product());
        auto ir = result->begin(); 

        for (unsigned y = 0; y < m_out_size.y; ++y) {
                unsigned iy = static_cast<unsigned>(floor(y * coordinate_scale.y)); 
                auto rangey = clamp_coord(iy, block_size.y, data.get_size().y); 
                for (unsigned x = 0; x < m_out_size.x; ++x, ++ir) {
                        unsigned ix = static_cast<unsigned>(floor(x * coordinate_scale.x)); 
                        auto rangex = clamp_coord(ix, block_size.x, data.get_size().x);
                        C2DBounds ibegin(rangex.first, rangey.first); 
                        C2DBounds iend(rangex.second, rangey.second);
			
                        buffer.clear(); 
                        for_each(data.begin_range(ibegin, iend), data.end_range(ibegin, iend), 
                                 [&buffer](T pixel) {
                                         buffer.push_back(pixel); 
                                 });
                        
                        sort(buffer.begin(), buffer.end()); 
                        *ir = get_max_represented(buffer);
                }
        }
        
        C2DFVector pixel_size = data.get_pixel_size();
        C2DFVector factor(float(data.get_size().x / float(m_out_size.x) ),
			  float(data.get_size().y / float(m_out_size.y) ));

	result->set_pixel_size(pixel_size * factor);
	
	return P2DImage(result); 


}

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

C2DLabelscaleFilterPluginFactory::C2DLabelscaleFilterPluginFactory():
C2DFilterPlugin("labelscale")
{
        add_parameter("out-size", new C2DBoundsParameter(m_out_size, true, "target size given as two coma separated values"));
}

C2DFilter *C2DLabelscaleFilterPluginFactory::do_create()const
{
        if (m_out_size.x < 1 || m_out_size.y < 1) {
                throw create_exception<invalid_argument>("labelscale: Output sizes must be positive (got", 
                                                         m_out_size, ")"); 
        }
        return new C2DLabelscale(m_out_size); 
}

const std::string C2DLabelscaleFilterPluginFactory::do_get_descr()const
{
        return "A filter that only creates output voxels that are already created in "
                "the input image. Scaling is done by using a voting algorithms that "
                "selects the target pixel value based on the highest pixel count of a "
                "certain label in the corresponding source region. If the region "
                "comprises two labels with the same count, the one with the lower number "
                "wins.";
}

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


NS_END
