// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2021.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: Timo Sachsenberg $
// $Authors: Eva Lange $
// --------------------------------------------------------------------------
#include <algorithm>
#include <cmath>

#include <boost/math/special_functions/acosh.hpp>

#include <OpenMS/TRANSFORMATIONS/RAW2PEAK/OptimizePick.h>

using std::max;

namespace OpenMS
{

  OptimizePick::OptimizePick(
      const struct OptimizationFunctions::PenaltyFactors & penalties,
      const int max_iteration)
  {

    penalties_ = penalties;

    max_iteration_ = max_iteration;

#ifdef DEBUG_PEAK_PICKING
    std::cout << "max iteration " << max_iteration_
              << "\n penalty factor pos " << penalties.pos
              << "\n penalty factor left width " << penalties.lWidth
              << "\n penalty factor right width " << penalties.rWidth
              << std::endl;
#endif

  }

  OptimizePick::~OptimizePick()
  {
  }

  void OptimizePick::optimize(std::vector<PeakShape> & peaks, Data & data)
  {
    if (peaks.empty())
      return;

    size_t global_peak_number = 0;
    data.peaks.assign(peaks.begin(), peaks.end());

    size_t num_dimensions = 4 * data.peaks.size();
    Eigen::VectorXd x_init (num_dimensions);
    x_init.setZero();
    // We have to initialize the parameters for the optimization
    for (size_t i = 0; i < data.peaks.size(); i++)
    {
      PeakShape current_peak = data.peaks[i];
      double h  = current_peak.height;
      double wl = current_peak.left_width;
      double wr = current_peak.right_width;
      double p  = current_peak.mz_position;
      if (boost::math::isnan(wl))
      {
        data.peaks[i].left_width = 1;
        wl = 1.;
      }
      if (boost::math::isnan(wr))
      {
        data.peaks[i].right_width = 1;
        wr = 1.;
      }
      x_init(4 * i) = h;
      x_init(4 * i + 1) = wl;
      x_init(4 * i + 2) = wr;
      x_init(4 * i + 3) = p;
    }

    data.penalties = penalties_;

    unsigned num_data_points = std::max(data.positions.size() + 1, num_dimensions);
    OptPeakFunctor functor (num_dimensions, num_data_points, &data);
    Eigen::LevenbergMarquardt<OptPeakFunctor> lmSolver (functor);
    lmSolver.parameters.maxfev = max_iteration_;
    Eigen::LevenbergMarquardtSpace::Status status = lmSolver.minimize(x_init);
    //the states are poorly documented. after checking the source, we believe that
    //all states except NotStarted, Running and ImproperInputParameters are good
    //termination states.
    if (status <= Eigen::LevenbergMarquardtSpace::ImproperInputParameters)
    {
        throw Exception::UnableToFit(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, "UnableToFit-OptimizePeak:", "Could not fit the data: Error " + String(status));
    }

    // iterate over all peaks and store the optimized values in peaks
    for (size_t current_peak = 0; current_peak < data.peaks.size(); current_peak++)
    {
      // Store the current parameters for this peak
      peaks[global_peak_number + current_peak].height =  x_init(4 * current_peak);
      peaks[global_peak_number + current_peak].mz_position = x_init(4 * current_peak + 3);
      peaks[global_peak_number + current_peak].left_width = x_init(4 * current_peak + 1);
      peaks[global_peak_number + current_peak].right_width = x_init(4 * current_peak + 2);

      // compute the area
      // is it a Lorentz or a Sech - Peak?
      if (peaks[global_peak_number + current_peak].type == PeakShape::LORENTZ_PEAK)
      {
        PeakShape p = peaks[global_peak_number + current_peak];
        double x_left_endpoint = p.mz_position - 1 / p.left_width * sqrt(p.height / 1 - 1);
        double x_right_endpoint = p.mz_position + 1 / p.right_width * sqrt(p.height / 1 - 1);
        double area_left = -p.height / p.left_width * atan(p.left_width * (x_left_endpoint - p.mz_position));
        double area_right = -p.height / p.right_width * atan(p.right_width * (p.mz_position - x_right_endpoint));
        peaks[global_peak_number + current_peak].area = area_left + area_right;
#ifdef DEBUG_PEAK_PICKING
        std::cout << "Lorentz " << area_left << " " << area_right
                  << " " << peaks[global_peak_number + current_peak].area << std::endl;
#endif
      }
      else  //It's a Sech - Peak
      {
        PeakShape p = peaks[global_peak_number + current_peak];
        double x_left_endpoint = p.mz_position - 1 / p.left_width * boost::math::acosh(sqrt(p.height / 0.001));
        double x_right_endpoint = p.mz_position + 1 / p.right_width * boost::math::acosh(sqrt(p.height / 0.001));
        double area_left = p.height / p.left_width * (sinh(p.left_width * (p.mz_position - x_left_endpoint)) / cosh(p.left_width * (p.mz_position - x_left_endpoint)));
        double area_right = -p.height / p.right_width * (sinh(p.right_width * (p.mz_position - x_right_endpoint)) / cosh(p.right_width * (p.mz_position - x_right_endpoint)));
        peaks[global_peak_number + current_peak].area = area_left + area_right;
#ifdef DEBUG_PEAK_PICKING
        std::cout << "Sech " << area_left << " " << area_right
                  << " " << peaks[global_peak_number + current_peak].area << std::endl;
        std::cout << p.mz_position << " " << x_left_endpoint << " " << x_right_endpoint << std::endl;
#endif
      }
    }
    //global_peak_number += data.peaks.size();

  }

  int OptimizePick::OptPeakFunctor::operator()(const Eigen::VectorXd &x, Eigen::VectorXd &fvec)
  {
    const std::vector<double> & signal = m_data->signal;
    const std::vector<double> & positions = m_data->positions;
    const std::vector<PeakShape> & peaks = m_data->peaks;
    const OptimizationFunctions::PenaltyFactors & penalties = m_data->penalties;
    // iterate over all points of the signal
    for (size_t current_point = 0; current_point < positions.size(); current_point++)
    {
      double computed_signal = 0.;
      double current_position = positions[current_point];
      double experimental_signal = signal[current_point];

      // iterate over all peaks
      for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
      {
        // Store the current parameters for this peak
        double p_height = x(4 * current_peak);
        double p_position = x(4 * current_peak + 3);
        double p_width
            = (current_position <= p_position) ? x(4 * current_peak + 1)
                                 : x(4 * current_peak + 2);

        // is it a Lorentz or a Sech - Peak?
        if (peaks[current_peak].type == PeakShape::LORENTZ_PEAK)
        {
          computed_signal += p_height / (1. + pow(p_width * (current_position - p_position), 2));
        }
        else // It's a Sech - Peak
        {
          computed_signal += p_height / pow(cosh(p_width * (current_position - p_position)), 2);
        }
      }
      fvec(current_point) = computed_signal - experimental_signal;
    }

    double penalty = 0.;
    double penalty_pos    = penalties.pos;
    double penalty_lwidth = penalties.lWidth;
    double penalty_rwidth = penalties.rWidth;

    // iterate over all peaks again to compute the penalties
    for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
    {
      double old_position = peaks[current_peak].mz_position;
      double old_width_l = peaks[current_peak].left_width;
      double old_width_r = peaks[current_peak].right_width;
      double p_position = x(4 * current_peak + 3);
      double p_width_l = x(4 * current_peak + 1);
      double p_width_r = x(4 * current_peak + 2);

      //penalty += pow(p_position - old_position, 2) + pow(p_width_l - old_width_l, 2) + pow(p_width_r - old_width_r, 2);
      penalty += penalty_pos * pow(p_position - old_position, 2)
        + penalty_lwidth * pow(p_width_l - old_width_l, 2)
        + penalty_rwidth * pow(p_width_r - old_width_r, 2);
    }

    fvec(positions.size()) = 100 * penalty;

    return 0;
  }
  // compute Jacobian matrix for the different parameters
  int OptimizePick::OptPeakFunctor::df(const Eigen::VectorXd &x, Eigen::MatrixXd &J)
  {
    std::cout << "rows: " << J.rows() << " columns: " << J.cols() << std::endl;//DEBUG
    const std::vector<double> & positions = m_data->positions;
    const std::vector<PeakShape> & peaks = m_data->peaks;
    const OptimizationFunctions::PenaltyFactors & penalties = m_data->penalties;
    // iterate over all points of the signal
    for (size_t current_point = 0; current_point < positions.size(); current_point++)
    {
      double current_position = positions[current_point];

      // iterate over all peaks
      for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
      {
        // Store the current parameters for this peak
        double p_height = x(4 * current_peak);
        double p_position = x(4 * current_peak + 3);
        double p_width = (current_position <= p_position) ? x(4 * current_peak + 1)
                          : x(4 * current_peak + 2);

        // is it a Lorentz or a Sech - Peak?
        if (peaks[current_peak].type == PeakShape::LORENTZ_PEAK)
        {
          double diff = current_position - p_position;
          double denom_inv = 1. / (1. + pow(p_width * diff, 2));

          double ddl_left = (current_position <= p_position)
              ? -2 * p_height * pow(diff, 2) * p_width * pow(denom_inv, 2) : 0;

          double ddl_right = (current_position  > p_position)
              ? -2 * p_height * pow(diff, 2) * p_width * pow(denom_inv, 2) : 0;

          double ddx0 = -2 * p_height * pow(p_width, 2) * diff * pow(denom_inv, 2);

          J(current_point, 4 * current_peak) = denom_inv;
          J(current_point, 4 * current_peak + 1) = ddl_left;
          J(current_point, 4 * current_peak + 2) = ddl_right;
          J(current_point, 4 * current_peak + 3) = ddx0;
        }
        else // It's a Sech - Peak
        {
          double diff = current_position - p_position;
          double denom_inv = 1. / cosh(p_width * diff);

          // The remaining computations are not stable if denom_inv == 0. In that case, we are far away from the peak
          // and can assume that all derivatives vanish
          double sinh_term = (fabs(denom_inv) < 1e-6) ? 0.0 : sinh(p_width * diff);
          double ddl_left  = (current_position <= p_position)
              ? -2 * p_height * sinh_term * diff * pow(denom_inv, 3) : 0;
          double ddl_right = (current_position  > p_position)
              ? -2 * p_height * sinh_term * diff * pow(denom_inv, 3) : 0;
          double ddx0      = 2 * p_height * p_width * sinh_term * pow(denom_inv, 3);

          J(current_point, 4 * current_peak) = pow(denom_inv, 2);
          J(current_point, 4 * current_peak + 1) = ddl_left;
          J(current_point, 4 * current_peak + 2) = ddl_right;
          J(current_point, 4 * current_peak + 3) = ddx0;
        }
      }
    }

    // Now iterate over all peaks again to compute the penalties.
    for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
    {
      double p_width_left = x(4 * current_peak + 1);
      double p_width_right = x(4 * current_peak + 2);
      double p_position = x(4 * current_peak + 3);

      double old_width_left = peaks[current_peak].left_width;
      double old_width_right = peaks[current_peak].right_width;
      double old_position = peaks[current_peak].mz_position;

      double penalty_l = 2. * penalties.lWidth * (p_width_left - old_width_left);
      double penalty_r = 2. * penalties.rWidth * (p_width_right - old_width_right);
      double penalty_p = 0;
      if (fabs(p_position - old_position) < 0.2)
      {
        penalty_p = 2. * penalties.pos * (p_position - old_position);
      }

      J(positions.size(), 4 * current_peak) = 0.;
      J(positions.size(), 4 * current_peak + 1) = 100 * penalty_l;
      J(positions.size(), 4 * current_peak + 2) = 100 * penalty_r;
      J(positions.size(), 4 * current_peak + 3) = 100 * penalty_p;
    }
    return 0;
  }
}//namespace


#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_SET_SIZE 40

#include "filter.hpp"

#include <svgpp/document_traversal.hpp>
#include <svgpp/utility/gil/blend.hpp>
#include <svgpp/utility/gil/composite.hpp>
#include <svgpp/utility/gil/color_matrix.hpp>
#include <svgpp/utility/gil/mask.hpp>
#include <boost/gil/gil_all.hpp>
#include <boost/mpl/set.hpp>
#include <boost/noncopyable.hpp>
#include <boost/variant.hpp>
#include <boost/math/constants/constants.hpp>

namespace mpl = boost::mpl;
namespace gil = boost::gil;

struct ElementWithRegion
{
  double x_, y_, width_, height_;
};

struct FilterInput
{
  enum source { fiNotSet, fiSourceGraphic, fiSourceAlpha, fiBackgroundImage,
    fiBackgroundAlpha, fiFillPaint, fiStrokePaint, fiReference };

  FilterInput()
    : source_(fiNotSet)
  {}

  source source_;
  svg_string_t reference_;
};

struct FilterElementBase: ElementWithRegion
{
  svg_string_t result_;
};

struct feBlend: FilterElementBase
{
  enum BlendMode { mNormal, mMultiply, mScreen, mDarken, mLighten };

  FilterInput input1_, input2_;
  BlendMode mode_;
};

struct feFunc
{
  enum Type { fIdentity, fTable, fDiscrete, fLinear, fGamma };

  feFunc()
    : type_(fIdentity)
  {}

  Type type_;
  double slope_, intercept_, amplitude_, exponent_, offset_;
  std::vector<double> tableValues_;
};

struct feComponentTransfer: FilterElementBase
{
  enum ARGBComponent { argbR, argbG, argbB, argbA }; // Must be the same order as channels in GIL color

  FilterInput input_;
  feFunc func_[4];
};

struct feOffset: FilterElementBase
{
  feOffset()
    : dx_(0), dy_(0)
  {}

  FilterInput input_;
  double dx_, dy_;
};

struct feComposite: FilterElementBase
{
  enum Operator { opOver, opIn, opOut, opAtop, opXor, opArithmetic };

  feComposite()
    : operator_(opOver)
    , k1_(0), k2_(0), k3_(0), k4_(0)
  {}

  FilterInput input1_, input2_;
  Operator operator_;
  double k1_, k2_, k3_, k4_;
};

struct feMerge: FilterElementBase
{
  std::vector<FilterInput> inputs_;
};

struct feFlood: FilterElementBase
{
  feFlood()
    : flood_color_(BlackColor())
    , flood_opacity_(1.0)
  {}

  color_t flood_color_;
  double flood_opacity_;
};

struct feColorMatrix: FilterElementBase
{
  enum Type { mMatrix, mSaturate, mHueRotate, mLuminanceToAlpha };

  feColorMatrix()
    : type_(mMatrix)
  {}

  FilterInput input_;
  Type type_;
  boost::optional<std::vector<double> > values_;
};

typedef boost::variant<feBlend, feComponentTransfer, feOffset, feComposite, feMerge, feFlood,
  feColorMatrix> FilterElement;

class ElementWithRegionContext
{
public:
  ElementWithRegionContext(ElementWithRegion & data)
    : data_(data)
  {}

  void set(svgpp::tag::attribute::x, double val)  { data_.x_ = val; }
  void set(svgpp::tag::attribute::y, double val)  { data_.y_ = val; }
  void set(svgpp::tag::attribute::width, double val)  { data_.width_ = val; }
  void set(svgpp::tag::attribute::height, double val) { data_.height_ = val; }

protected:
  ElementWithRegion & data_;
};

template<class InAttributeTag>
class ElementWithInputContext
{
  typedef mpl::map<
    mpl::pair<svgpp::tag::value::SourceGraphic,    mpl::integral_c<FilterInput::source, FilterInput::fiSourceGraphic> >,
    mpl::pair<svgpp::tag::value::SourceAlpha,      mpl::integral_c<FilterInput::source, FilterInput::fiSourceAlpha> >,
    mpl::pair<svgpp::tag::value::BackgroundImage,  mpl::integral_c<FilterInput::source, FilterInput::fiBackgroundImage> >,
    mpl::pair<svgpp::tag::value::BackgroundAlpha,  mpl::integral_c<FilterInput::source, FilterInput::fiBackgroundAlpha> >,
    mpl::pair<svgpp::tag::value::FillPaint,        mpl::integral_c<FilterInput::source, FilterInput::fiFillPaint> >,
    mpl::pair<svgpp::tag::value::StrokePaint,      mpl::integral_c<FilterInput::source, FilterInput::fiStrokePaint> >
  > filter_input_to_enum;

public:
  ElementWithInputContext(FilterInput & data)
    : data_(data)
  {
  }

  template<class Input>
  void set(InAttributeTag, Input)
  {
    data_.source_ = mpl::at<filter_input_to_enum, Input>::type::value;
  }

  void set(InAttributeTag, boost::iterator_range<svg_string_t::const_pointer> const & reference)
  {
    data_.source_ = FilterInput::fiReference;
    data_.reference_.assign(boost::begin(reference), boost::end(reference));
  }

protected:
  FilterInput & data_;
};

class FilterElementBaseContext: public ElementWithRegionContext
{
public:
  FilterElementBaseContext(FilterElementBase & data)
    : ElementWithRegionContext(data)
    , data_(data)
  {}

  using ElementWithRegionContext::set;

  template<class Range>
  void set(svgpp::tag::attribute::result, Range const & r)
  {
    data_.result_.assign(boost::begin(r), boost::end(r));
  }

protected:
  FilterElementBase & data_;
};

class FilterContext: public ElementWithRegionContext
{
public:
  FilterContext()
    : ElementWithRegionContext(region_)
  {}

  void on_enter_element(svgpp::tag::element::filter) const
  {}

  void on_exit_element()
  {}

  using ElementWithRegionContext::set;

  void set(svgpp::tag::attribute::filterUnits, svgpp::tag::value::userSpaceOnUse)
  {
    filterUnitsUseObjectBoundingBox_ = false;
  }

  void set(svgpp::tag::attribute::filterUnits, svgpp::tag::value::objectBoundingBox)
  {
    filterUnitsUseObjectBoundingBox_ = true;
  }

  void addElement(FilterElement const & el)
  {
    elements_.push_back(el);
  }

  ElementWithRegion region_;
  bool filterUnitsUseObjectBoundingBox_;
  std::vector<FilterElement> elements_;
};

class feBlendContext:
  public FilterElementBaseContext,
  public ElementWithInputContext<svgpp::tag::attribute::in>,
  public ElementWithInputContext<svgpp::tag::attribute::in2>
{
  typedef mpl::map<
    mpl::pair< svgpp::tag::value::normal,   mpl::integral_c<feBlend::BlendMode, feBlend::mNormal> >,
    mpl::pair< svgpp::tag::value::multiply, mpl::integral_c<feBlend::BlendMode, feBlend::mMultiply> >,
    mpl::pair< svgpp::tag::value::screen,   mpl::integral_c<feBlend::BlendMode, feBlend::mScreen> >,
    mpl::pair< svgpp::tag::value::darken,   mpl::integral_c<feBlend::BlendMode, feBlend::mDarken> >,
    mpl::pair< svgpp::tag::value::lighten,  mpl::integral_c<feBlend::BlendMode, feBlend::mLighten> >
  > mode_to_enum;

public:
  feBlendContext(FilterContext & parent)
    : FilterElementBaseContext(data_)
    , ElementWithInputContext<svgpp::tag::attribute::in>(data_.input1_)
    , ElementWithInputContext<svgpp::tag::attribute::in2>(data_.input2_)
    , parent_(parent)
  {
  }

  void on_exit_element() const
  {
    parent_.addElement(data_);
  }

  using FilterElementBaseContext::set;
  using ElementWithInputContext<svgpp::tag::attribute::in>::set;
  using ElementWithInputContext<svgpp::tag::attribute::in2>::set;

  template<class Mode>
  void set(svgpp::tag::attribute::mode, Mode)
  {
    data_.mode_ = mpl::at<mode_to_enum, Mode>::type::value;
  }

private:
  FilterContext & parent_;
  feBlend data_;
};

class feComponentTransferContext;

template<feComponentTransfer::ARGBComponent ComponentArg>
class feFuncContext
{
  typedef mpl::map<
    mpl::pair<svgpp::tag::value::identity,   mpl::integral_c<feFunc::Type, feFunc::fIdentity> >,
    mpl::pair<svgpp::tag::value::table,      mpl::integral_c<feFunc::Type, feFunc::fTable> >,
    mpl::pair<svgpp::tag::value::discrete,   mpl::integral_c<feFunc::Type, feFunc::fDiscrete> >,
    mpl::pair<svgpp::tag::value::linear,     mpl::integral_c<feFunc::Type, feFunc::fLinear> >,
    mpl::pair<svgpp::tag::value::gamma,      mpl::integral_c<feFunc::Type, feFunc::fGamma> >
  > type_to_enum;

public:
  feFuncContext(feComponentTransferContext & parent)
    : parent_(parent)
  {}

  void on_exit_element() const;

  template<class Type>
  void set(svgpp::tag::attribute::type, Type)
  {
    data_.type_ = mpl::at<type_to_enum, Type>::type::value;
  }

  template<class Range>
  void set(svgpp::tag::attribute::tableValues, Range const & range)
  {
    data_.tableValues_.assign(boost::begin(range), boost::end(range));
  }

  void set(svgpp::tag::attribute::slope, double value) { data_.slope_ = value; }
  void set(svgpp::tag::attribute::intercept, double value) { data_.intercept_ = value; }
  void set(svgpp::tag::attribute::amplitude, double value) { data_.amplitude_ = value; }
  void set(svgpp::tag::attribute::exponent, double value) { data_.exponent_ = value; }
  void set(svgpp::tag::attribute::offset, double value) { data_.offset_ = value; }

protected:
  feComponentTransferContext & parent_;
  feFunc data_;
};

class feComponentTransferContext:
  public FilterElementBaseContext,
  public ElementWithInputContext<svgpp::tag::attribute::in>
{
public:
  feComponentTransferContext(FilterContext & parent)
    : FilterElementBaseContext(data_)
    , ElementWithInputContext<svgpp::tag::attribute::in>(data_.input_)
    , parent_(parent)
  {
  }

  void on_exit_element() const
  {
    parent_.addElement(data_);
  }

  void setComponentFunction(feComponentTransfer::ARGBComponent component, feFunc const & func)
  {
    data_.func_[component] = func;
  }

  using FilterElementBaseContext::set;
  using ElementWithInputContext<svgpp::tag::attribute::in>::set;

private:
  FilterContext & parent_;
  feComponentTransfer data_;
};

template<feComponentTransfer::ARGBComponent ComponentArg>
void feFuncContext<ComponentArg>::on_exit_element() const
{
  parent_.setComponentFunction(ComponentArg, data_);
}

class feOffsetContext:
  public FilterElementBaseContext,
  public ElementWithInputContext<svgpp::tag::attribute::in>
{
public:
  feOffsetContext(FilterContext & parent)
    : FilterElementBaseContext(data_)
    , ElementWithInputContext<svgpp::tag::attribute::in>(data_.input_)
    , parent_(parent)
  {
  }

  void on_exit_element() const
  {
    parent_.addElement(data_);
  }

  using FilterElementBaseContext::set;
  using ElementWithInputContext<svgpp::tag::attribute::in>::set;

  void set(svgpp::tag::attribute::dx, double val)
  { data_.dx_ = val; }

  void set(svgpp::tag::attribute::dy, double val)
  { data_.dy_ = val; }

private:
  FilterContext & parent_;
  feOffset data_;
};

class feCompositeContext:
  public FilterElementBaseContext,
  public ElementWithInputContext<svgpp::tag::attribute::in>,
  public ElementWithInputContext<svgpp::tag::attribute::in2>
{
  typedef mpl::map<
    mpl::pair< svgpp::tag::value::over,       mpl::integral_c<feComposite::Operator, feComposite::opOver> >,
    mpl::pair< svgpp::tag::value::in,         mpl::integral_c<feComposite::Operator, feComposite::opIn> >,
    mpl::pair< svgpp::tag::value::out,        mpl::integral_c<feComposite::Operator, feComposite::opOut> >,
    mpl::pair< svgpp::tag::value::atop,       mpl::integral_c<feComposite::Operator, feComposite::opAtop> >,
    mpl::pair< svgpp::tag::value::xor_,       mpl::integral_c<feComposite::Operator, feComposite::opXor> >,
    mpl::pair< svgpp::tag::value::arithmetic, mpl::integral_c<feComposite::Operator, feComposite::opArithmetic> >
  > op_to_enum;

public:
  feCompositeContext(FilterContext & parent)
    : FilterElementBaseContext(data_)
    , ElementWithInputContext<svgpp::tag::attribute::in>(data_.input1_)
    , ElementWithInputContext<svgpp::tag::attribute::in2>(data_.input2_)
    , parent_(parent)
  {
  }

  void on_exit_element() const
  {
    parent_.addElement(data_);
  }

  using FilterElementBaseContext::set;
  using ElementWithInputContext<svgpp::tag::attribute::in>::set;
  using ElementWithInputContext<svgpp::tag::attribute::in2>::set;

  template<class Op>
  void set(svgpp::tag::attribute::operator_, Op)
  {
    data_.operator_ = mpl::at<op_to_enum, Op>::type::value;
  }

  void set(svgpp::tag::attribute::k1, double val)
  { data_.k1_ = val; }

  void set(svgpp::tag::attribute::k2, double val)
  { data_.k2_ = val; }

  void set(svgpp::tag::attribute::k3, double val)
  { data_.k3_ = val; }

  void set(svgpp::tag::attribute::k4, double val)
  { data_.k4_ = val; }

private:
  FilterContext & parent_;
  feComposite data_;
};

class feMergeContext:
  public FilterElementBaseContext
{
public:
  feMergeContext(FilterContext & parent)
    : FilterElementBaseContext(data_)
    , parent_(parent)
  {
  }

  void on_exit_element() const
  {
    parent_.addElement(data_);
  }

private:
  FilterContext & parent_;
  feMerge data_;

  friend class feMergeNodeContext;
};

class feMergeNodeContext:
  public ElementWithInputContext<svgpp::tag::attribute::in>
{
public:
  feMergeNodeContext(feMergeContext & parent)
    : ElementWithInputContext<svgpp::tag::attribute::in>(data_)
    , parent_(parent)
  {}

  void on_exit_element() const
  {
    parent_.data_.inputs_.push_back(data_);
  }

private:
  feMergeContext & parent_;
  FilterInput data_;
};

class feFloodContext:
  public FilterElementBaseContext
{
public:
  feFloodContext(FilterContext & parent)
    : FilterElementBaseContext(data_)
    , parent_(parent)
  {
  }

  void on_exit_element() const
  {
    parent_.addElement(data_);
  }

  using FilterElementBaseContext::set;

  // TODO: inheriting and 'style' handling for flood-color and flood-opacity
  void set(svgpp::tag::attribute::flood_color, svgpp::tag::value::inherit)
  {}

  void set(svgpp::tag::attribute::flood_color, svgpp::tag::value::currentColor)
  {}

  void set(svgpp::tag::attribute::flood_color, color_t color, svgpp::tag::skip_icc_color = svgpp::tag::skip_icc_color())
  { data_.flood_color_ = color; }

  void set(svgpp::tag::attribute::flood_opacity, double val)
  { data_.flood_opacity_ = val; }

  void set(svgpp::tag::attribute::flood_opacity, svgpp::tag::value::inherit)
  {}

private:
  FilterContext & parent_;
  feFlood data_;
};

class feColorMatrixContext:
  public FilterElementBaseContext,
  public ElementWithInputContext<svgpp::tag::attribute::in>
{
  typedef mpl::map<
    mpl::pair< svgpp::tag::value::matrix,           mpl::integral_c<feColorMatrix::Type, feColorMatrix::mMatrix> >,
    mpl::pair< svgpp::tag::value::saturate,         mpl::integral_c<feColorMatrix::Type, feColorMatrix::mSaturate> >,
    mpl::pair< svgpp::tag::value::hueRotate,        mpl::integral_c<feColorMatrix::Type, feColorMatrix::mHueRotate> >,
    mpl::pair< svgpp::tag::value::luminanceToAlpha, mpl::integral_c<feColorMatrix::Type, feColorMatrix::mLuminanceToAlpha> >
  > type_to_enum;

public:
  feColorMatrixContext(FilterContext & parent)
    : FilterElementBaseContext(data_)
    , ElementWithInputContext<svgpp::tag::attribute::in>(data_.input_)
    , parent_(parent)
  {
  }

  void on_exit_element() const
  {
    parent_.addElement(data_);
  }

  using FilterElementBaseContext::set;
  using ElementWithInputContext<svgpp::tag::attribute::in>::set;

  template<class Type>
  void set(svgpp::tag::attribute::type, Type)
  {
    data_.type_ = mpl::at<type_to_enum, Type>::type::value;
  }

  template<class Values>
  void set(svgpp::tag::attribute::values, Values const & values)
  {
    data_.values_ = std::vector<double>(boost::begin(values), boost::end(values));
  }

private:
  FilterContext & parent_;
  feColorMatrix data_;
};

struct context_factories
{
  template<class ParentContext, class ElementTag>
  struct apply;
};

template<>
struct context_factories::apply<FilterContext, svgpp::tag::element::feBlend>
{
  typedef svgpp::factory::context::on_stack<feBlendContext> type;
};

template<>
struct context_factories::apply<FilterContext, svgpp::tag::element::feOffset>
{
  typedef svgpp::factory::context::on_stack<feOffsetContext> type;
};

template<>
struct context_factories::apply<FilterContext, svgpp::tag::element::feComposite>
{
  typedef svgpp::factory::context::on_stack<feCompositeContext> type;
};

template<>
struct context_factories::apply<FilterContext, svgpp::tag::element::feComponentTransfer>
{
  typedef svgpp::factory::context::on_stack<feComponentTransferContext> type;
};

template<>
struct context_factories::apply<FilterContext, svgpp::tag::element::feMerge>
{
  typedef svgpp::factory::context::on_stack<feMergeContext> type;
};

template<>
struct context_factories::apply<feMergeContext, svgpp::tag::element::feMergeNode>
{
  typedef svgpp::factory::context::on_stack<feMergeNodeContext> type;
};

template<>
struct context_factories::apply<FilterContext, svgpp::tag::element::feFlood>
{
  typedef svgpp::factory::context::on_stack<feFloodContext> type;
};

template<>
struct context_factories::apply<FilterContext, svgpp::tag::element::feColorMatrix>
{
  typedef svgpp::factory::context::on_stack<feColorMatrixContext> type;
};

template<>
struct context_factories::apply<feComponentTransferContext, svgpp::tag::element::feFuncA>
{
  typedef svgpp::factory::context::on_stack<feFuncContext<feComponentTransfer::argbA> > type;
};

template<>
struct context_factories::apply<feComponentTransferContext, svgpp::tag::element::feFuncR>
{
  typedef svgpp::factory::context::on_stack<feFuncContext<feComponentTransfer::argbR> > type;
};

template<>
struct context_factories::apply<feComponentTransferContext, svgpp::tag::element::feFuncG>
{
  typedef svgpp::factory::context::on_stack<feFuncContext<feComponentTransfer::argbG> > type;
};

template<>
struct context_factories::apply<feComponentTransferContext, svgpp::tag::element::feFuncB>
{
  typedef svgpp::factory::context::on_stack<feFuncContext<feComponentTransfer::argbB> > type;
};

class BlendView: public IFilterView
{
public:
  BlendView(feBlend const & fe, IFilterViewPtr const & in1, IFilterViewPtr const & in2)
    : fe_(fe)
    , in1_(in1)
    , in2_(in2)
  {}

  virtual gil::rgba8c_view_t view()
  {
    if (in1_)
    {
      image_.recreate(in1_->view().dimensions());
      switch(fe_.mode_)
      {
      case feBlend::mNormal:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::blend_pixel<svgpp::tag::value::normal>());
        break;
      case feBlend::mMultiply:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::blend_pixel<svgpp::tag::value::multiply>());
        break;
      case feBlend::mScreen:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::blend_pixel<svgpp::tag::value::screen>());
        break;
      case feBlend::mDarken:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::blend_pixel<svgpp::tag::value::darken>());
        break;
      case feBlend::mLighten:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::blend_pixel<svgpp::tag::value::lighten>());
        break;
      default:
        BOOST_ASSERT(false);
      }
      in1_.reset();
      in2_.reset();
    }
    return gil::const_view(image_);
  }

private:
  feBlend const fe_;
  IFilterViewPtr in1_, in2_;
  boost::gil::rgba8_image_t image_;
};

class CompositeView: public IFilterView
{
public:
  CompositeView(feComposite const & fe, IFilterViewPtr const & in1, IFilterViewPtr const & in2)
    : fe_(fe)
    , in1_(in1)
    , in2_(in2)
  {}

  virtual gil::rgba8c_view_t view()
  {
    if (in1_)
    {
      image_.recreate(in1_->view().dimensions());
      switch(fe_.operator_)
      {
      case feComposite::opOver:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::composite_pixel<svgpp::tag::value::over>());
        break;
      case feComposite::opIn:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::composite_pixel<svgpp::tag::value::in>());
        break;
      case feComposite::opOut:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::composite_pixel<svgpp::tag::value::out>());
        break;
      case feComposite::opAtop:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::composite_pixel<svgpp::tag::value::atop>());
        break;
      case feComposite::opXor:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::composite_pixel<svgpp::tag::value::xor_>());
        break;
      case feComposite::opArithmetic:
        gil::transform_pixels(in1_->view(), in2_->view(), gil::view(image_),
          svgpp::gil_utility::composite_pixel_arithmetic<boost::gil::rgba8c_view_t::value_type>(fe_.k1_, fe_.k2_, fe_.k3_, fe_.k4_));
        break;
      default:
        BOOST_ASSERT(false);
      }
      in1_.reset();
      in2_.reset();
    }
    return gil::const_view(image_);
  }

private:
  feComposite const fe_;
  IFilterViewPtr in1_, in2_;
  boost::gil::rgba8_image_t image_;
};

typedef boost::array<gil::bits8, 256> ChannelTransferTable;

struct ComponentTransferPixel
{
  ComponentTransferPixel(ChannelTransferTable const * tables)
    : tables_(tables)
  {}

  gil::rgba8_pixel_t operator()(const gil::rgba8_pixel_t & src) const
  {
    gil::rgba8_pixel_t dst;
    for(size_t ch = 0; ch < 4; ++ch)
      dst[ch] = tables_[ch][src[ch]];

    return dst;
  }

private:
  ChannelTransferTable const * tables_;
};

class ComponentTransferView: public IFilterView
{
public:
  ComponentTransferView(feComponentTransfer const & fe, IFilterViewPtr const & in)
    : fe_(fe)
    , in_(in)
  {}

  virtual gil::rgba8c_view_t view()
  {
    if (in_)
    {
      image_.recreate(in_->view().dimensions());
      ChannelTransferTable tables[4];
      for(int ch = 0; ch < 4; ++ch)
      {
        ChannelTransferTable & table = tables[ch];
        feFunc const & func = fe_.func_[ch];
        switch(func.type_)
        {
        case feFunc::fTable:
        case feFunc::fDiscrete: // TODO
        case feFunc::fIdentity:
          for(int i=0; i<256; ++i)
            table[i] = i;
          break;
        case feFunc::fLinear:
          // C' = slope * C + intercept
          for(int i=0; i<256; ++i)
            table[i] = svgpp::gil_detail::clamp_channel_bits8(static_cast<int>(func.slope_ * i + func.intercept_ * 255 + 0.49));
          break;
        case feFunc::fGamma:
          for(int i=0; i<256; ++i)
            // C' = amplitude * pow(C, exponent) + offset
            table[i] = svgpp::gil_detail::clamp_channel_bits8(static_cast<int>(
              (func.amplitude_ * std::pow(i / 255.0, func.exponent_) + func.offset_) * 255.0 + 0.49));
          break;
        default:
          BOOST_ASSERT(false);
        }
      }
      gil::transform_pixels(in_->view(), gil::view(image_), ComponentTransferPixel(tables));
      in_.reset();
    }
    return gil::const_view(image_);
  }

private:
  feComponentTransfer const fe_;
  IFilterViewPtr in_;
  boost::gil::rgba8_image_t image_;
};

class OffsetView: public IFilterView
{
public:
  OffsetView(feOffset const & fe, IFilterViewPtr const & in)
    : fe_(fe)
    , in_(in)
  {}

  virtual gil::rgba8c_view_t view()
  {
    // TODO: use transform
    if (in_)
    {
      image_.recreate(in_->view().dimensions(), gil::rgba8_pixel_t(0, 0, 0, 0), 0);
      int w = image_.width() - std::abs(fe_.dx_);
      int h = image_.height() - std::abs(fe_.dy_);
      if (w > 0 && h > 0)
      {
        gil::copy_pixels(
          gil::subimage_view(in_->view(),
            std::max(0.0, -fe_.dx_), std::max(0.0, -fe_.dy_), w, h),
          gil::subimage_view(gil::view(image_),
            std::max(0.0, fe_.dx_), std::max(0.0, fe_.dy_), w, h));
      }
      in_.reset();
    }
    return gil::const_view(image_);
  }

private:
  feOffset const fe_;
  IFilterViewPtr in_;
  boost::gil::rgba8_image_t image_;
};

class MergeView: public IFilterView
{
public:
  void addNode(IFilterViewPtr const & in)
  {
    nodes_.push_back(in);
  }

  virtual gil::rgba8c_view_t view()
  {
    // TODO: what if empty?
    if (!nodes_.empty())
    {
      image_.recreate(nodes_.front()->view().dimensions());
      if (nodes_.size() == 1)
        gil::copy_pixels(nodes_.front()->view(), gil::view(image_));
      else
      {
        gil::transform_pixels(nodes_[0]->view(), nodes_[1]->view(), gil::view(image_),
          svgpp::gil_utility::composite_pixel<svgpp::tag::value::over>());
        for(size_t i = 2; i < nodes_.size(); ++i)
          gil::transform_pixels(gil::view(image_), nodes_[i]->view(), gil::view(image_),
            svgpp::gil_utility::composite_pixel<svgpp::tag::value::over>());
      }
      nodes_.clear();
    }
    return gil::const_view(image_);
  }

private:
  std::vector<IFilterViewPtr> nodes_;
  boost::gil::rgba8_image_t image_;
};

class FloodView: public IFilterView
{
public:
  FloodView(feFlood const & fe)
    : fe_(fe)
  {}

  virtual gil::rgba8c_view_t view()
  {
    if (image_.width() == 0)
    {
      //image_.recreate(in_->view().dimensions(), gil::rgba8_pixel_t(0, 0, 0, 0), 0);
    }
    return gil::const_view(image_);
  }

private:
  feFlood const fe_;
  boost::gil::rgba8_image_t image_;
};

class ColorMatrixView: public IFilterView
{
public:
  ColorMatrixView(feColorMatrix const & fe, IFilterViewPtr const & input)
    : fe_(fe)
    , in_(input)
  {}

  virtual gil::rgba8c_view_t view()
  {
    if (in_)
    {
      typedef svgpp::gil_utility::color_matrix_transform<boost::gil::rgba8c_view_t::value_type> transform_t;
      image_.recreate(in_->view().dimensions());
      switch(fe_.type_)
      {
      case feColorMatrix::mMatrix:
      {
        if (fe_.values_ && fe_.values_->size() != 20)
          throw std::runtime_error("For feColorMatrix type=\"matrix\", 'values' must be a list of 20 values");
        boost::multi_array<double, 2> m(boost::extents[4][5]);
        if (fe_.values_)
          m.assign(fe_.values_->begin(), fe_.values_->end());
        else
          for(int i=0; i<4; ++i)
            m[i][i] = 1;
        gil::transform_pixels(in_->view(), gil::view(image_), transform_t(m));
      }
      break;
      case feColorMatrix::mSaturate:
      {
        if (fe_.values_ && fe_.values_->size() != 1)
          throw std::runtime_error("For feColorMatrix type=\"saturate\", 'values' must be single real number");
        double saturate = fe_.values_ ? fe_.values_->front() : 1;
        saturate = std::min(1.0, std::max(0.0, saturate));
        gil::transform_pixels(in_->view(), gil::view(image_),
          transform_t(svgpp::gil_utility::get_saturate_matrix(saturate)));
      }
      break;
      case feColorMatrix::mHueRotate:
      {
        if (fe_.values_ && fe_.values_->size() != 1)
          throw std::runtime_error("For feColorMatrix type=\"hueRotate\", 'values' must be single real number");
        double angle = fe_.values_ ? fe_.values_->front() : 0;
        gil::transform_pixels(in_->view(), gil::view(image_),
          transform_t(svgpp::gil_utility::get_hue_rotate_matrix(angle * boost::math::constants::degree<double>())));
      }
      break;
      case feColorMatrix::mLuminanceToAlpha:
      {
        gil::copy_pixels(
          gil::color_converted_view<gil::rgba8_pixel_t>(
            in_->view(),
            svgpp::gil_utility::rgba_to_mask_color_converter<boost::gil::alpha_t>()
          ),
          gil::view(image_));
      }
      break;
      }
    }
    return gil::const_view(image_);
  }

private:
  IFilterViewPtr in_;
  feColorMatrix const fe_;
  boost::gil::rgba8_image_t image_;
};

class AlphaChannelView: public IFilterView
{
public:
  AlphaChannelView(IFilterViewPtr const & in)
    : in_(in)
  {}

  virtual gil::rgba8c_view_t view()
  {
    if (in_)
    {
      image_.recreate(in_->view().dimensions(), gil::rgba8_pixel_t(0, 0, 0, 0), 0);
      gil::copy_pixels(
        gil::kth_channel_view<gil::color_index_type<gil::rgba8_pixel_t, gil::alpha_t>::value>(in_->view()),
        gil::kth_channel_view<gil::color_index_type<gil::rgba8_pixel_t, gil::alpha_t>::value>(gil::view(image_)));
      in_.reset();
    }
    return gil::const_view(image_);
  }

private:
  IFilterViewPtr in_;
  boost::gil::rgba8_image_t image_;
};

struct FilterElementVisitor:
  public boost::static_visitor<>,
  boost::noncopyable
{
  FilterElementVisitor(Filters::Input const & input)
    : input_(input)
  {}

  void operator()(feBlend const & fe)
  {
    boost::shared_ptr<BlendView> feView(new BlendView(fe, findInput(fe.input1_), findInput(fe.input2_)));
    if (!fe.result_.empty())
      namedFilters_[fe.result_] = feView;
    lastFilter_ = feView;
  }

  void operator()(feComponentTransfer const & fe)
  {
    boost::shared_ptr<ComponentTransferView> feView(new ComponentTransferView(fe, findInput(fe.input_)));
    if (!fe.result_.empty())
      namedFilters_[fe.result_] = feView;
    lastFilter_ = feView;
  }

  void operator()(feOffset const & fe)
  {
    boost::shared_ptr<OffsetView> feView(new OffsetView(fe, findInput(fe.input_)));
    if (!fe.result_.empty())
      namedFilters_[fe.result_] = feView;
    lastFilter_ = feView;
  }

  void operator()(feComposite const & fe)
  {
    boost::shared_ptr<CompositeView> feView(new CompositeView(fe, findInput(fe.input1_), findInput(fe.input2_)));
    if (!fe.result_.empty())
      namedFilters_[fe.result_] = feView;
    lastFilter_ = feView;
  }

  void operator()(feMerge const & fe)
  {
    boost::shared_ptr<MergeView> feView(new MergeView);
    for(std::vector<FilterInput>::const_iterator f = fe.inputs_.begin();
      f != fe.inputs_.end(); ++f)
      feView->addNode(findInput(*f));
    if (!fe.result_.empty())
      namedFilters_[fe.result_] = feView;
    lastFilter_ = feView;
  }

  void operator()(feFlood const & fe)
  {
    boost::shared_ptr<FloodView> feView(new FloodView(fe));
    if (!fe.result_.empty())
      namedFilters_[fe.result_] = feView;
    lastFilter_ = feView;
  }

  void operator()(feColorMatrix const & fe)
  {
    boost::shared_ptr<ColorMatrixView> feView(new ColorMatrixView(fe, findInput(fe.input_)));
    if (!fe.result_.empty())
      namedFilters_[fe.result_] = feView;
    lastFilter_ = feView;
  }

  IFilterViewPtr const & lastFilter() { return lastFilter_; }

private:
  Filters::Input const & input_;
  typedef std::map<svg_string_t, IFilterViewPtr> NamedFilters;
  NamedFilters namedFilters_;
  IFilterViewPtr lastFilter_;
  mutable IFilterViewPtr sourceAlphaView_, backgroundAlphaView_;

  IFilterViewPtr findInput(FilterInput const & in) const
  {
    switch (in.source_)
    {
    case FilterInput::fiReference:
    {
      NamedFilters::const_iterator f = namedFilters_.find(in.reference_);
      if (f == namedFilters_.end())
        throw std::runtime_error("Can't find filter element");
      return f->second;
    }
    case FilterInput::fiNotSet:
      if (lastFilter_)
        return lastFilter_;
      else
        return input_.sourceGraphic_;
    case FilterInput::fiSourceGraphic:
      return input_.sourceGraphic_;
    case FilterInput::fiSourceAlpha:
      if (!sourceAlphaView_)
        sourceAlphaView_.reset(new AlphaChannelView(input_.sourceGraphic_));
      return sourceAlphaView_;
    case FilterInput::fiBackgroundImage:
      return input_.backgroundImage_;
    case FilterInput::fiBackgroundAlpha:
      if (!backgroundAlphaView_)
        backgroundAlphaView_.reset(new AlphaChannelView(input_.backgroundImage_));
      return backgroundAlphaView_;
    case FilterInput::fiFillPaint:
      return input_.fillPaint_;
    case FilterInput::fiStrokePaint:
      return input_.strokePaint_;
    default:
      BOOST_ASSERT(false);
    }
    return IFilterViewPtr();
  }
};

namespace
{
  length_factory_t length_factory_instance;

  struct length_policy_t
  {
    typedef length_factory_t const length_factory_type;

    static length_factory_type & length_factory(ElementWithRegionContext const &)
    {
      return length_factory_instance;
    }
  };
}

IFilterViewPtr Filters::get(svg_string_t const & id, length_factory_t const &, Input const & input)
{
  try
  {
    if (XMLElement node = xml_document_.findElementById(id))
    {
      FilterContext filterContext;
      svgpp::document_traversal<
        svgpp::context_factories<context_factories>,
        svgpp::color_factory<color_factory_t>,
        svgpp::length_policy<length_policy_t>,
        svgpp::processed_elements<
          boost::mpl::set<
            svgpp::tag::element::filter,
            svgpp::tag::element::feBlend,
            svgpp::tag::element::feMerge,
            svgpp::tag::element::feMergeNode,
            svgpp::tag::element::feDistantLight,
            svgpp::tag::element::fePointLight,
            svgpp::tag::element::feSpotLight,
            svgpp::tag::element::feColorMatrix,
            svgpp::tag::element::feComponentTransfer,
            svgpp::tag::element::feComposite,
            svgpp::tag::element::feFlood,
            svgpp::tag::element::feFuncA,
            svgpp::tag::element::feFuncB,
            svgpp::tag::element::feFuncG,
            svgpp::tag::element::feFuncR,
            svgpp::tag::element::feOffset
          >::type
        >,
        svgpp::processed_attributes<
          boost::mpl::set<
            boost::mpl::pair<svgpp::tag::element::filter, svgpp::tag::attribute::x>,
            boost::mpl::pair<svgpp::tag::element::filter, svgpp::tag::attribute::y>,
            boost::mpl::pair<svgpp::tag::element::filter, svgpp::tag::attribute::width>,
            boost::mpl::pair<svgpp::tag::element::filter, svgpp::tag::attribute::height>,
            boost::mpl::pair<svgpp::tag::element::filter, svgpp::tag::attribute::filterUnits>,
            //boost::mpl::pair<svgpp::tag::element::filter, svgpp::tag::attribute::primitiveUnits>,

            svgpp::tag::attribute::result,
            svgpp::tag::attribute::in,
            svgpp::tag::attribute::in2,
            boost::mpl::pair<svgpp::tag::element::feBlend, svgpp::tag::attribute::x>,
            boost::mpl::pair<svgpp::tag::element::feBlend, svgpp::tag::attribute::y>,
            boost::mpl::pair<svgpp::tag::element::feBlend, svgpp::tag::attribute::width>,
            boost::mpl::pair<svgpp::tag::element::feBlend, svgpp::tag::attribute::height>,
            boost::mpl::pair<svgpp::tag::element::feBlend, svgpp::tag::attribute::mode>,
            boost::mpl::pair<svgpp::tag::element::feColorMatrix, svgpp::tag::attribute::type>,
            boost::mpl::pair<svgpp::tag::element::feColorMatrix, svgpp::tag::attribute::values>,
            boost::mpl::pair<svgpp::tag::element::feOffset, svgpp::tag::attribute::dx>,
            boost::mpl::pair<svgpp::tag::element::feOffset, svgpp::tag::attribute::dy>,
            boost::mpl::pair<svgpp::tag::element::feComposite, svgpp::tag::attribute::operator_>,
            boost::mpl::pair<svgpp::tag::element::feComposite, svgpp::tag::attribute::k1>,
            boost::mpl::pair<svgpp::tag::element::feComposite, svgpp::tag::attribute::k2>,
            boost::mpl::pair<svgpp::tag::element::feComposite, svgpp::tag::attribute::k3>,
            boost::mpl::pair<svgpp::tag::element::feComposite, svgpp::tag::attribute::k4>,
            boost::mpl::pair<svgpp::tag::element::feFlood, svgpp::tag::attribute::flood_color>,
            boost::mpl::pair<svgpp::tag::element::feFlood, svgpp::tag::attribute::flood_opacity>,

            // transfer function element attributes
            boost::mpl::pair<svgpp::tag::element::feFuncA, svgpp::tag::attribute::type>,
            boost::mpl::pair<svgpp::tag::element::feFuncR, svgpp::tag::attribute::type>,
            boost::mpl::pair<svgpp::tag::element::feFuncG, svgpp::tag::attribute::type>,
            boost::mpl::pair<svgpp::tag::element::feFuncB, svgpp::tag::attribute::type>,
            svgpp::tag::attribute::tableValues,
            svgpp::tag::attribute::slope,
            svgpp::tag::attribute::intercept,
            svgpp::tag::attribute::amplitude,
            svgpp::tag::attribute::exponent,
            svgpp::tag::attribute::offset
          >::type
        >
      >::load_expected_element(node, filterContext, svgpp::tag::element::filter());

      FilterElementVisitor v(input);
      for(std::vector<FilterElement>::const_iterator fe = filterContext.elements_.begin();
        fe != filterContext.elements_.end(); ++fe)
      {
        boost::apply_visitor(v, *fe);
      }
      if (!v.lastFilter())
        throw std::runtime_error("No filter elements in filter definition");
      return v.lastFilter();
    }
    else
      throw std::runtime_error("Filter not found");
  }
  catch (std::exception const & e)
  {
    // Not all filters implemented yet, we will skip such cases
    std::cerr << e.what() << "\n";
    return input.sourceGraphic_;
  }
}