/* -*- 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 <queue>
#include <stdexcept>

#include <mia/2d/filter/regiongrow.hh>

NS_BEGIN(regiongrow_2d_filter)
NS_MIA_USE;
using namespace std;

C2DRegiongrowFilter::C2DRegiongrowFilter(const mia::C2DImageDataKey& seed_image_key, mia::P2DShape neighborhood):
       m_seed_image_key(seed_image_key)
{
       m_neighborhood.reserve(neighborhood->size() - 1);

       for (auto i = neighborhood->begin(); i != neighborhood->end(); ++i)
              if ( *i != MPosition::_0 )
                     m_neighborhood.push_back(*i);
}

struct GrowLocation {
       C2DBounds pos;
       float value;
};


template <typename  T>
C2DRegiongrowFilter::result_type C2DRegiongrowFilter::operator () (const mia::T2DImage<T>& data) const
{
       auto size = data.get_size();
       C2DImageIOPlugin::PData in_image_list = m_seed_image_key.get();

       if (!in_image_list || in_image_list->empty())
              throw create_exception<invalid_argument>( "C2DRegiongrowFilter: Empty image list loaded from pool");

       P2DImage pseed_image = (*in_image_list)[0];

       if (pseed_image->get_pixel_type() != it_bit)
              throw invalid_argument("C2DRegiongrowFilter: seed image must be of type 'bit'");

       const C2DBitImage& seed_image = dynamic_cast<const C2DBitImage&>(*pseed_image);

       if (size != seed_image.get_size())
              throw create_exception<invalid_argument>( "Input image is of size ", size,
                            ", but seed image is ", seed_image.get_size());

       C2DBitImage *presult = new C2DBitImage(size, data);
       P2DImage result(presult);
       queue<GrowLocation> seeds;
       auto i = data.begin_range(Position::_0, size);
       auto e = data.end_range(Position::_0, size);
       auto s = seed_image.begin();
       auto r = presult->begin();

       // read initial seeds and set initial output
       while (i != e) {
              if (*s) {
                     GrowLocation gloc;
                     gloc.pos = i.pos();
                     gloc.value = *i;
                     seeds.push(gloc);
              }

              ++i;
              ++r, ++s;
       }

       while (!seeds.empty()) {
              auto t = seeds.front();
              seeds.pop();

              // already visited?
              if ((*presult)(t.pos))
                     continue;

              // set output pixel
              (*presult)(t.pos) = true;

              for (auto n = m_neighborhood.begin(); n != m_neighborhood.end(); ++n) {
                     GrowLocation gloc;
                     gloc.pos = Position( t.pos + *n);

                     // inside?
                     if (gloc.pos < size) {
                            // already visited?
                            if ((*presult)(gloc.pos))
                                   continue;

                            gloc.value = data(gloc.pos);

                            if (gloc.value >= t.value)
                                   seeds.push(gloc);
                     }
              }
       }

       return result;
}

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

C2DRegiongrowFilterPlugin::C2DRegiongrowFilterPlugin():
       mia::C2DFilterPlugin("regiongrow")
{
       add_parameter("seed", new CStringParameter(m_seed_image, CCmdOptionFlags::required_input, "seed image (bit valued)",
                     &C2DImageIOPluginHandler::instance()));
       add_parameter("n", make_param(m_neighborhood, "8n", false, "Neighborhood shape"));
}

mia::C2DFilter *C2DRegiongrowFilterPlugin::do_create()const
{
       C2DImageDataKey seed_key = C2DImageIOPluginHandler::instance().load_to_pool(m_seed_image);
       return new C2DRegiongrowFilter(seed_key, m_neighborhood);
}

const std::string C2DRegiongrowFilterPlugin::do_get_descr()const
{
       return "Region growing startin from a seed until only along increasing gradients";
}

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

NS_END
