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

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iomanip>
#include <limits>
#include <numeric>

#include <boost/cast.hpp>
#include <mia/3d/fifof/gauss.hh>

using namespace boost;

NS_BEGIN(gauss_2dstack_filter)

NS_MIA_USE;
using namespace std;

template <class InputIterator, class OutputIterator>
static void convert(InputIterator a, InputIterator e, OutputIterator o)
{
       while (a != e) {
              *o = numeric_cast<typename iterator_traits<OutputIterator>::value_type>(*a);
              ++o;
              ++a;
       }
}

class C2DGaussStackFilterFactory : public C2DFifoFilterPlugin
{
public:
       C2DGaussStackFilterFactory();
private:

       virtual const string do_get_descr() const;
       virtual C2DImageFifoFilter *do_create()const;

       mutable int m_hw;
};


C2DGaussFifoFilter::C2DGaussFifoFilter(size_t hw):
       C2DImageFifoFilter(2 * hw + 1, hw + 1, 0),
       m_hw(hw)
{
       const C2DFilterPluginHandler::Instance& filter_plugins = C2DFilterPluginHandler::instance();
       stringstream filter_descr;
       filter_descr << "gauss:w=" << hw;
       m_gauss2d = filter_plugins.produce(filter_descr.str().c_str());

       if (!m_gauss2d) {
              filter_descr << " is not a supported filter description";
              throw runtime_error(filter_descr.str());
       }

       m_1dfilter = C1DSpacialKernelPluginHandler::instance().produce(filter_descr.str().c_str());
}

template <typename T>
C2DImage *C2DGaussFifoFilter::operator()(const T3DImage<T>& /*buffer*/) const
{
       TRACE("C2DGaussFifoFilter::pull");
       C2DFImage help(m_slice_size);

       if ((int)get_start() == 0 && get_end() == m_1dfilter->size()) {
              const float k = (*m_1dfilter)[0];
              transform(m_buffer->begin(), m_buffer->begin() + help.size(), help.begin(),
              [k](T x) {
                     return x * k;
              });

              for (size_t i =  1; i < get_end(); ++i) {
                     C2DFImage::iterator s_i = m_buffer->begin_at(0, 0, i);
                     const float k = (*m_1dfilter)[i];
                     transform(help.begin(), help.end(), s_i, help.begin(),
                     [k](float x, float y) {
                            return x + k * y;
                     });
              }
       } else {
              const float k = (*m_1dfilter)[0];
              transform(m_buffer->begin_at(0, 0, get_end() - 1),
                        m_buffer->begin_at(0, 0, get_end()),
              help.begin(), [k](float x) {
                     return x * k;
              });

              for (size_t i =  1; i < get_end(); ++i) {
                     C2DFImage::iterator s_i = m_buffer->begin_at(0, 0, get_end() - 1 - i);
                     const float k = (*m_1dfilter)[i];
                     transform(help.begin(), help.end(), s_i, help.begin(),
                     [k](float x, float y) {
                            return x + k * y;
                     });
              }
       }

       T2DImage<T> *retval = new T2DImage<T>(m_slice_size);
       convert(help.begin(), help.end(), retval->begin());
       return retval;
}

// adds new data at the beginning of the input
template <typename T>
C2DImage *C2DGaussFifoFilter::operator()(const T2DImage<T>& input)
{
       TRACE("C2DGaussFifoFilter::push internal");
       copy(input.begin(), input.end(), m_buffer->begin());
       return NULL;
}

void C2DGaussFifoFilter::shift_buffer()
{
       copy_backward(m_buffer->begin(),
                     m_buffer->end() - m_buffer->get_size().x * m_buffer->get_size().y,
                     m_buffer->end());
}

void C2DGaussFifoFilter::do_initialize(::boost::call_traits<P2DImage>::param_type x)
{
       m_slice_size = x->get_size();
       m_buffer.reset(new C3DFImage(C3DBounds(m_slice_size.x, m_slice_size.y, 2 * m_hw + 1)));
       m_dummy.reset(create_buffer(C2DBounds(1, 1), 1, x->get_pixel_type()));
}

void C2DGaussFifoFilter::do_push(::boost::call_traits<P2DImage>::param_type x)
{
       P2DImage tmp = m_gauss2d->filter(*x);
       mia::accumulate(*this, *tmp);
}

P2DImage C2DGaussFifoFilter::do_filter()
{
       return 	P2DImage(mia::filter(*this, *m_dummy));
}

void C2DGaussFifoFilter::post_finalize()
{
       m_buffer.reset(NULL);
       m_dummy.reset(NULL);
}

C2DGaussStackFilterFactory::C2DGaussStackFilterFactory():
       C2DFifoFilterPlugin("gauss"),
       m_hw(1)
{
       add_parameter("w", make_lc_param(m_hw, 1, false, "filter width parameter (2*w+1)"));
}

const string C2DGaussStackFilterFactory::do_get_descr() const
{
       return "Gauss 2D fifo filter";
}

C2DImageFifoFilter *C2DGaussStackFilterFactory::do_create()const
{
       return new C2DGaussFifoFilter(m_hw);
}

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

NS_END // end namespace median_2dstack_filter
