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

#define BOOST_TEST_DYN_LINK

#include <mia/internal/autotest.hh>
#include <mia/core/sparse_solver.hh>
#include <numeric>

/**
   This test only tests the general idea of the unified solver class, and
   is a blue-print of how such a thing could be implemented
*/
namespace mia
{
using std::vector;

template <>
struct multiply<vector<double>> {
       static void apply(vector<double>& result, const typename TSparseSolver<vector<double>>::A_mult_x& A, const vector<double>& x)
       {
              assert(result.size() == x.size());
              const int size = result.size();
              const int boundary = A.get_boundary_size();
              auto ir = result.begin();
              auto ix = x.begin();

              for (int i = 0; i < size; ++i, ++ix, ++ir) {
                     if (i >= boundary && i <  size - boundary) {
                            *ir = A(ix);
                     } else {
                            *ir = *ix;
                     }
              }
       }
};
}
/**
   a simple fix-point iterator
*/
NS_MIA_USE;
using namespace std;

class CSolverMock: public TSparseSolver<vector<double>>
{
public:
       int solve(const Field& rhs, Field& x, const CSolverMock::A_mult_x& mult) const;
};

class CAMultX : public CSolverMock::A_mult_x
{
public:
       CAMultX(size_t size);
       CSolverMock::value_type operator ()(CSolverMock::const_field_iterator ix) const;
       int get_boundary_size() const;

};

BOOST_AUTO_TEST_CASE( test_fixpoint_sparse_solver )
{
       const vector<double> x = {1, 2, 3, 2, 4, 3, 4, 6, 4, 7, 4, 3, 2, 1, 5, 3, 9, 1, 2};
       vector<double> b(x.size());
       vector<double> y(x.size());
       CAMultX mult(x.size());
       const int boundary = mult.get_boundary_size();
       b[0] = x[0];
       b[x.size() - 1] = x[x.size() - 1];
       auto ix = x.begin() + boundary;

       for (auto ib = b.begin() + boundary;
            ix != x.end() - boundary; ++ix, ++ib) {
              *ib = mult(ix);
       }

       cvdebug() << "x = " << x << "\n";
       cvdebug() << "b = " << b << "\n";
       BOOST_CHECK_EQUAL(CSolverMock().solve(b, y, mult), 0);
       ix = x.begin();

       for (auto iy = y.begin(); ix != x.end(); ++ix, ++iy)
              BOOST_CHECK_CLOSE(*iy, *ix, 0.01);
}



CSolverMock::value_type CAMultX::operator () (CSolverMock::const_field_iterator ix) const
{
       return *ix - 0.2 * (ix[-1] + ix[1]);
}

CAMultX::CAMultX(size_t size):
       CSolverMock::A_mult_x(size)
{
}

int CAMultX::get_boundary_size() const
{
       return 1;
}






int CSolverMock::solve(const Field& b, Field& x, const CSolverMock::A_mult_x& A) const
{
       assert(b.size() == x.size());
       Field y(x.size());
       double res = 0.0;
       int iter = 0;
       Field d(x.size());

       do {
              res = 0.0;
              multiply<vector<double>>::apply(y, A, x);
              transform(b.begin(), b.end(), y.begin(), d.begin(),
              [](double lhs, double rhs) {
                     return lhs - rhs;
              });
              res = inner_product(d.begin(), d.end(), d.begin(), 0.0);
              transform(x.begin(), x.end(), d.begin(), x.begin(),
              [](double lhs, double rhs) {
                     return lhs + rhs;
              });
              cvdebug() << "[" << iter << "] res = " << res << ", x = " << x << "\n";
              ++iter;
       } while (res > 0.0000001 && iter < 100);

       return iter < 100 ? 0 : 1;
}




