/* -*- 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 <iomanip>
#include <limits>
#include <mia/2d/shape.hh>
#include <mia/2d/filter/morphological.hh>

NS_BEGIN(morphological_2dimage_filter)

NS_MIA_USE
using namespace std;
using namespace boost;
namespace bfs = boost::filesystem;

C2DDilate::C2DDilate(P2DShape shape, bool hint):
       m_shape(shape),
       m_more_dark(hint)

{
}

template <typename T>
struct __dispatch_dilate {
       static T2DImage<T> *apply(const T2DImage<T>& image, const C2DShape& shape, bool /*black*/)
       {
              const C2DBounds& size = image.get_size();
              T2DImage<T> *result = new T2DImage<T>(image);
              typename T2DImage<T>::const_iterator src_i = image.begin();
              typename T2DImage<T>::iterator res_i = result->begin();

              for (size_t y = 0; y < size.y; ++y)
                     for (size_t x = 0; x < size.x; ++x,  ++src_i, ++res_i) {
                            C2DShape::const_iterator sb = shape.begin();
                            C2DShape::const_iterator se = shape.end();

                            while (sb != se) {
                                   C2DBounds nl(x + sb->x, y + sb->y);

                                   if (nl < size) {
                                          T val = image(nl);

                                          if (*res_i < val )
                                                 *res_i = val;
                                   }

                                   ++sb;
                            }
                     }

              return result;
       }
};

template <>
struct __dispatch_dilate<bool> {
       static C2DBitImage *apply(const C2DBitImage& image, const C2DShape& shape, bool more_black)
       {
              const C2DBounds& size = image.get_size();
              C2DBitImage *result = 0;

              if (more_black) {
                     result = new C2DBitImage(size, image);
                     C2DBitImage::const_iterator src_i = image.begin();

                     for (size_t y = 0; y < size.y; ++y)
                            for (size_t x = 0; x < size.x; ++x,  ++src_i) {
                                   if ( *src_i ) {
                                          C2DShape::const_iterator sb = shape.begin();
                                          C2DShape::const_iterator se = shape.end();

                                          while (sb != se) {
                                                 C2DBounds nl(x - sb->x, y - sb->y);

                                                 if (nl < size) {
                                                        (*result)(nl) = true;
                                                 }

                                                 ++sb;
                                          }
                                   }
                            }
              } else {
                     result = new C2DBitImage(image);
                     C2DBitImage::iterator res_i = result->begin();

                     for (size_t y = 0; y < size.y; ++y)
                            for (size_t x = 0; x < size.x; ++x,  ++res_i) {
                                   if ( ! *res_i ) {
                                          C2DShape::const_iterator sb = shape.begin();
                                          C2DShape::const_iterator se = shape.end();

                                          while (sb != se) {
                                                 C2DBounds nl(x + sb->x, y + sb->y);

                                                 if (nl < size) {
                                                        if ( image(nl) ) {
                                                               *res_i = true;
                                                               break;
                                                        }
                                                 }

                                                 ++sb;
                                          }
                                   }
                            }
              }

              return result;
       }
};

template <typename T>
typename C2DFilter::result_type C2DDilate::operator () (const T2DImage<T>& image)const
{
       return P2DImage(__dispatch_dilate<T>::apply(image, *m_shape, m_more_dark));
}

C2DFilter::result_type C2DDilate::do_filter (const C2DImage& image)const
{
       return ::mia::filter(*this, image);
}

C2DDilateFilterFactory::C2DDilateFilterFactory():
       C2DMorphFilterFactory("dilate")
{
}


C2DMorphFilterFactory::C2DMorphFilterFactory(const char *name):
       C2DFilterPlugin(name),
       m_hint("black")
{
       add_parameter("shape", make_param(m_shape, "sphere:r=2", false, "structuring element"));
       set<string> valid_set;
       valid_set.insert("black");
       valid_set.insert("white");
       add_parameter("hint", new CSetParameter<string>(m_hint, valid_set, "a hint at the main image content"));
}


C2DFilter *C2DMorphFilterFactory::do_create()const
{
       bool bhint = true;

       if (m_hint == string("black"))
              bhint = true;
       else if (m_hint == string("white"))
              bhint = false;
       else
              throw invalid_argument(string("hint '") + m_hint + string("' not supported"));

       return dodo_create(m_shape, bhint);
}


C2DFilter *C2DDilateFilterFactory::dodo_create(P2DShape shape, bool bhint) const
{
       return new C2DDilate(shape, bhint);
}

const string C2DDilateFilterFactory::do_get_descr()const
{
       return "2d image stack dilate filter";
}

C2DErode::C2DErode(P2DShape shape, bool hint):
       m_shape(shape),
       m_more_dark(hint)

{
}

template <typename T>
struct __dispatch_erode {
       static T2DImage<T> *apply(const T2DImage<T>& image, const C2DShape& shape, bool /*black*/)
       {
              const C2DBounds size = image.get_size();
              T2DImage<T> *result = new T2DImage<T>(image);
              typename T2DImage<T>::const_iterator src_i = image.begin();
              typename T2DImage<T>::iterator res_i = result->begin();

              for (size_t y = 0; y < size.y; ++y)
                     for (size_t x = 0; x < size.x; ++x,  ++src_i, ++res_i) {
                            C2DShape::const_iterator sb = shape.begin();
                            C2DShape::const_iterator se = shape.end();

                            while (sb != se) {
                                   C2DBounds nl(x + sb->x, y + sb->y);

                                   if (nl < size) {
                                          T val = image(nl);

                                          if (*res_i > val )
                                                 *res_i = val;
                                   }

                                   ++sb;
                            }
                     }

              return result;
       }
};

template <>
struct __dispatch_erode<bool> {
       static C2DBitImage *apply(const C2DBitImage& image, const C2DShape& shape, bool more_black)
       {
              const C2DBounds& size = image.get_size();
              C2DBitImage *result = 0;

              if (more_black) {
                     result = new C2DBitImage(image);
                     C2DBitImage::iterator res_i = result->begin();

                     for (size_t y = 0; y < size.y; ++y)
                            for (size_t x = 0; x < size.x; ++x,  ++res_i) {
                                   if ( *res_i ) {
                                          C2DShape::const_iterator sb = shape.begin();
                                          C2DShape::const_iterator se = shape.end();

                                          while (sb != se) {
                                                 C2DBounds nl(x + sb->x, y + sb->y);

                                                 if (nl < size) {
                                                        if ( !image(nl) ) {
                                                               *res_i = false;
                                                               break;
                                                        }
                                                 }

                                                 ++sb;
                                          }
                                   }
                            }
              } else {
                     result = new C2DBitImage(size, image);
                     fill(result->begin(), result->end(), true);
                     C2DBitImage::const_iterator src_i = image.begin();

                     for (size_t y = 0; y < size.y; ++y)
                            for (size_t x = 0; x < size.x; ++x,  ++src_i) {
                                   if ( !*src_i ) {
                                          C2DShape::const_iterator sb = shape.begin();
                                          C2DShape::const_iterator se = shape.end();

                                          while (sb != se) {
                                                 C2DBounds nl(x - sb->x, y - sb->y);

                                                 if (nl < size) {
                                                        (*result)(nl) = false;
                                                 }

                                                 ++sb;
                                          }
                                   }
                            }
              }

              return result;
       }
};

template <typename T>
typename C2DFilter::result_type C2DErode::operator () (const T2DImage<T>& image)const
{
       return P2DImage(__dispatch_erode<T>::apply(image, *m_shape, m_more_dark));
}

C2DFilter::result_type C2DErode::do_filter (const C2DImage& image)const
{
       return ::mia::filter(*this, image);
}

C2DErodeFilterFactory::C2DErodeFilterFactory():
       C2DMorphFilterFactory("erode")
{
}

C2DFilter *C2DErodeFilterFactory::dodo_create(P2DShape shape, bool bhint) const
{
       return new C2DErode(shape, bhint);
}

const string C2DErodeFilterFactory::do_get_descr()const
{
       return "2d image stack erode filter";
}

C2DOpenClose::C2DOpenClose(P2DShape shape, bool hint, bool open):
       m_erode(shape, hint),
       m_dilate(shape, hint),
       m_open(open)
{
}

P2DImage C2DOpenClose::do_filter(const C2DImage& src) const
{
       if (m_open) {
              P2DImage tmp = m_erode.filter(src);
              return m_dilate.filter(*tmp);
       } else {
              P2DImage tmp = m_dilate.filter(src);
              return m_erode.filter(*tmp);
       }
}


C2DOpenFilterFactory::C2DOpenFilterFactory():
       C2DMorphFilterFactory("open")
{
}

C2DFilter *C2DOpenFilterFactory::dodo_create(P2DShape shape, bool hint) const
{
       return new C2DOpenClose(shape, hint, true);
}

const string C2DOpenFilterFactory::do_get_descr()const
{
       return "morphological open";
}

C2DCloseFilterFactory::C2DCloseFilterFactory():
       C2DMorphFilterFactory("close")
{
}

C2DFilter *C2DCloseFilterFactory::dodo_create(P2DShape shape, bool hint)const
{
       return new C2DOpenClose(shape, hint, false);
}

const string C2DCloseFilterFactory::do_get_descr()const
{
       return "morphological close";
}

extern "C" EXPORT CPluginBase *get_plugin_interface()
{
       CPluginBase *p = new C2DErodeFilterFactory();
       p->append_interface(new C2DDilateFilterFactory());
       p->append_interface(new C2DOpenFilterFactory());
       p->append_interface(new C2DCloseFilterFactory());
       return p;
}

NS_END
