/* -*- 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/core/splinebc/bc.hh>

NS_MIA_BEGIN

using std::invalid_argument;

CMirrorOnBoundary::CMirrorOnBoundary():
       m_width2(0)
{
}

CMirrorOnBoundary::CMirrorOnBoundary(int width):
       CSplineBoundaryCondition(width),
       m_width2(2 * width - 2)
{
}

CSplineBoundaryCondition *CMirrorOnBoundary::clone () const
{
       return new CMirrorOnBoundary(get_width());
}

void CMirrorOnBoundary::do_set_width(int width)
{
       m_width2 = 2 * width - 2;
}

void CMirrorOnBoundary::test_supported(int /*npoles*/) const
{
}

void CMirrorOnBoundary::do_apply(CSplineKernel::VIndex& index, CSplineKernel::VWeight& /*weights*/) const
{
       for (size_t k = 0; k < index.size(); k++) {
              int idx = (index[k] < 0) ? -index[k] : index[k];
              idx = (get_width() == 1) ? (0) : ((idx < m_width2) ? idx : idx % m_width2);

              if (get_width() <= idx) {
                     idx = m_width2 - idx;
              }

              index[k] = idx;
       }
}


double CMirrorOnBoundary::initial_coeff(const std::vector<double>& coeff, double pole)const
{
       double zn = pole;
       double iz = 1.0 / pole;
       double z2n = pow(pole, (double)(coeff.size() - 1));
       double sum = coeff[0] + z2n * coeff[coeff.size() - 1];
       z2n *= z2n * iz;

       for (size_t n = 1; n < coeff.size()  - 1; n++) {
              sum += (zn + z2n) * coeff[n];
              zn *= pole;
              z2n *= iz;
       }

       return (sum / (1.0 - zn * zn));
}

double CMirrorOnBoundary::initial_anti_coeff(const std::vector<double>& coeff, double pole)const
{
       return ((pole / (pole * pole - 1.0)) *
               (pole * coeff[coeff.size() - 2] + coeff[coeff.size() - 1]));
}



CZeroBoundary::CZeroBoundary(int width):
       CSplineBoundaryCondition(width)
{
}

CSplineBoundaryCondition *CZeroBoundary::clone ()const
{
       return new CZeroBoundary(get_width());
}

void CZeroBoundary::test_supported(int npoles) const
{
       /**
          Unfortunately, the current pre-filtering does not work properly
          for splines with more then one pole.
        */
       if (npoles > 1) {
              throw create_exception<invalid_argument>( "CZeroBoundary: Got ", npoles, ", but currently, "
                            "zero-boundary are not supported for splines with more then one pole");
       }
}


void CZeroBoundary::do_apply(CSplineKernel::VIndex& index, CSplineKernel::VWeight& weights) const
{
       for (size_t k = 0; k < index.size(); k++) {
              if (index[k] < 0 || index[k] >= get_width()) {
                     index[k] = 0;
                     weights[k] = 0;
              }
       }
}

double CZeroBoundary::initial_coeff(const std::vector<double>& coeff, double pole) const
{
       double zn = pole  * pole;
       double ip = 1.0 / pole;
       double z2n = pow(pole, 2.0 * coeff.size() + 2) ;
       double sum = 0.0;

       for (size_t n = 0; n < coeff.size() - 1; n++) {
              sum -= (zn + z2n) * coeff [n];
              zn *= pole;
              z2n *= ip;
       }

       sum -= zn * coeff [coeff.size() - 1];
       return coeff [0] + sum / ( 1 - zn * zn);
}

double CZeroBoundary::initial_anti_coeff(const std::vector<double>& coeff, double pole)const
{
       return - coeff[coeff.size() - 1] * pole;
}


CRepeatBoundary::CRepeatBoundary():
       m_widthm1(0)
{
}

CSplineBoundaryCondition *CRepeatBoundary::clone ()const
{
       return new CRepeatBoundary(get_width());
}

CRepeatBoundary::CRepeatBoundary(int width):
       CSplineBoundaryCondition(width),
       m_widthm1(width - 1)
{
}

void CRepeatBoundary::test_supported(int npoles) const
{
       if (npoles > 1) {
              throw create_exception<invalid_argument>( "CRepeatBoundary:Got ", npoles, "poles, but currently, repeat-boundary is"
                            "not supported for splines with more then one pole");
       }
}

void CRepeatBoundary::do_set_width(int width)
{
       m_widthm1 = width - 1;
}

void CRepeatBoundary::do_apply(CSplineKernel::VIndex& index, CSplineKernel::VWeight& /*weights*/) const
{
       for (size_t k = 0; k < index.size(); k++) {
              if (index[k] < 0)
                     index[k] = 0;
              else if (index[k] > m_widthm1) {
                     index[k] = m_widthm1;
              }
       }
}


double CRepeatBoundary::initial_coeff(const std::vector<double>& coeff, double pole)const
{
       double zn = pole;
       double iz = 1.0 / pole;
       double z2n = pow(pole, (double)(2 * coeff.size()));
       double sum = 0.0;

       for (size_t n = 0; n < coeff.size(); n++) {
              sum += (zn  + z2n) * coeff [n];
              zn *= pole;
              z2n *= iz;
       }

       return coeff[0] + sum / ( 1 - zn * zn);
}

double CRepeatBoundary::initial_anti_coeff(const std::vector<double>& coeff, double pole)const
{
       return - pole / (1 - pole) * coeff[coeff.size() - 1];
}


CMirrorOnBoundaryPlugin::CMirrorOnBoundaryPlugin():
       CSplineBoundaryConditionPlugin("mirror")
{
}

CSplineBoundaryCondition *CMirrorOnBoundaryPlugin::do_create(int width) const
{
       return new CMirrorOnBoundary(width);
}

const std::string CMirrorOnBoundaryPlugin::do_get_descr() const
{
       return "Spline interpolation boundary conditions that mirror on the boundary";
}


CRepeatBoundaryPlugin::CRepeatBoundaryPlugin():
       CSplineBoundaryConditionPlugin("repeat")
{
}

CSplineBoundaryCondition *CRepeatBoundaryPlugin::do_create(int width) const
{
       return new CRepeatBoundary(width);
}

const std::string CRepeatBoundaryPlugin::do_get_descr() const
{
       return "Spline interpolation boundary conditions that repeats the value at the boundary";
}


CZeroBoundaryPlugin::CZeroBoundaryPlugin():
       CSplineBoundaryConditionPlugin("zero")
{
}

CSplineBoundaryCondition *CZeroBoundaryPlugin::do_create(int width) const
{
       return new CZeroBoundary(width);
}

const std::string CZeroBoundaryPlugin::do_get_descr() const
{
       return "Spline interpolation boundary conditions that assumes zero for values outside";
}


extern "C" EXPORT CPluginBase  *get_plugin_interface()
{
       CPluginBase  *result = new CZeroBoundaryPlugin();
       result->append_interface(new CRepeatBoundaryPlugin());
       result->append_interface(new CMirrorOnBoundaryPlugin());
       return result;
}


NS_MIA_END
