/* -*- 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/3d/filter/labelscale.hh>

NS_BEGIN( labelscale_3dimage_filter)

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

C3DLabelscale::C3DLabelscale(const C3DBounds& 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 C3DLabelscale::result_type C3DLabelscale::operator () (const T3DImage<T>& data) const
{
       T3DImage<T> *result = new T3DImage<T>(m_out_size, data);
       C3DBounds block_size = C3DBounds::_1;
       C3DFVector coordinate_scale = C3DFVector::_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);
       }

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

       vector<T> buffer;
       buffer.reserve(block_size.product());
       auto ir = result->begin();

       for (unsigned z = 0; z < m_out_size.z; ++z) {
              unsigned iz = static_cast<unsigned>(floor(z * coordinate_scale.z));
              auto rangez = clamp_coord(iz, block_size.z, data.get_size().z);

              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);
                            C3DBounds ibegin(rangex.first, rangey.first, rangez.first);
                            C3DBounds iend(rangex.second, rangey.second, rangez.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);
                     }
              }
       }

       C3DFVector voxel_size = data.get_voxel_size();
       C3DFVector factor(float(data.get_size().x / float(m_out_size.x) ),
                         float(data.get_size().y / float(m_out_size.y) ),
                         float(data.get_size().z / float(m_out_size.z) ));
       result->set_voxel_size(voxel_size * factor);
       return P3DImage(result);
}

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

C3DLabelscaleFilterPluginFactory::C3DLabelscaleFilterPluginFactory():
       C3DFilterPlugin("labelscale")
{
       add_parameter("out-size", new C3DBoundsParameter(m_out_size, true, "target size given as two coma separated values"));
}

C3DFilter *C3DLabelscaleFilterPluginFactory::do_create()const
{
       if (m_out_size.x < 1 || m_out_size.y < 1 || m_out_size.z < 1) {
              throw create_exception<invalid_argument>("labelscale: Output sizes must be positive (got",
                            m_out_size, ")");
       }

       return new C3DLabelscale(m_out_size);
}

const std::string C3DLabelscaleFilterPluginFactory::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 C3DLabelscaleFilterPluginFactory();
}


NS_END
