// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
/**
 * \file
 * \brief Test the MultiTypeBlockMatrix data structure
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <iostream>

#include <dune/common/exceptions.hh>
#include <dune/common/fvector.hh>
#include <dune/common/fmatrix.hh>
#include <dune/common/indices.hh>

#include <dune/istl/matrix.hh>
#include <dune/istl/bvector.hh>
#include <dune/istl/io.hh>
#include <dune/istl/operators.hh>
#include <dune/istl/preconditioners.hh>
#include <dune/istl/solvers.hh>
#include <dune/istl/multitypeblockvector.hh>
#include <dune/istl/multitypeblockmatrix.hh>

using namespace Dune;

int main(int argc, char** argv) try
{
  // Import the static constants _0, _1, etc
  using namespace Indices;

  ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  //  First, we test a MultiTypeBlockMatrix consisting of an array of 2x2 dense matrices.
  //  The upper left dense matrix has dense 3x3 blocks, the lower right matrix has 1x1 blocks,
  //  the off-set diagonal matrix block sizes are set accordingly.
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////

  // set up the test matrix
  typedef MultiTypeBlockVector<Matrix<FieldMatrix<double,3,3> >, Matrix<FieldMatrix<double,3,1> > > RowType0;
  typedef MultiTypeBlockVector<Matrix<FieldMatrix<double,1,3> >, Matrix<FieldMatrix<double,1,1> > > RowType1;

  MultiTypeBlockMatrix<RowType0,RowType1> multiMatrix;

  multiMatrix[_0][_0].setSize(3,3);
  multiMatrix[_0][_1].setSize(3,2);
  multiMatrix[_1][_0].setSize(2,3);
  multiMatrix[_1][_1].setSize(2,2);

  // lazy solution: initialize the entire matrix with zeros
  multiMatrix = 0;

  printmatrix(std::cout, multiMatrix[_0][_0], "(0,0)", "--");
  printmatrix(std::cout, multiMatrix[_0][_1], "(0,1)", "--");
  printmatrix(std::cout, multiMatrix[_1][_0], "(1,0)", "--");
  printmatrix(std::cout, multiMatrix[_1][_1], "(1,1)", "--");

  // set up a test vector
  MultiTypeBlockVector<BlockVector<FieldVector<double,3> >, BlockVector<FieldVector<double,1> > > multiVector;

  multiVector[_0] = {{1,0,0},
                     {0,1,0},
                     {0,0,1}};

  multiVector[_1] = {3.14, 42};

  // Test matrix-vector products
  MultiTypeBlockVector<BlockVector<FieldVector<double,3> >, BlockVector<FieldVector<double,1> > > result;
  result[_0].resize(3);
  result[_1].resize(2);

  multiMatrix[_0][_0] = 4200;
  multiMatrix[_0][_1] = 4201;
  multiMatrix[_1][_0] = 4210;
  multiMatrix[_1][_1] = 4211;

  multiMatrix.mv(multiVector,result);
  std::cout << "mv result" << std::endl << result << std::endl;

  multiMatrix.umv(multiVector,result);
  std::cout << "umv result" << std::endl << result << std::endl;

  multiMatrix.mmv(multiVector,result);
  std::cout << "mmv result" << std::endl << result << std::endl;

  multiMatrix.usmv(3.14,multiVector,result);
  std::cout << "usmv result" << std::endl << result << std::endl;

  ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  //  Next test: Set up a linear system with a matrix consisting of 2x2 sparse scalar matrices.
  //  Solve the linear system with a simple iterative scheme.
  //  \todo What's the point if all four matrices have the same type?
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  std::cout << "Jacobi Solver Test on MultiTypeBlockMatrix<BCRSMatrix>";

  typedef Dune::FieldMatrix<double,1,1> LittleBlock;                    //matrix block type
  typedef Dune::BCRSMatrix<LittleBlock> BCRSMat;                        //matrix type

  const int X1=3;                                                       //index bounds of all four matrices
  const int X2=2;
  const int Y1=3;
  const int Y2=2;
  BCRSMat A11 = BCRSMat(X1,Y1,X1*Y1,BCRSMat::random);                   //A11 is 3x3
  BCRSMat A12 = BCRSMat(X1,Y2,X1*Y2,BCRSMat::random);                   //A12 is 2x3
  BCRSMat A21 = BCRSMat(X2,Y1,X2*Y1,BCRSMat::random);                   //A11 is 3x2
  BCRSMat A22 = BCRSMat(X2,Y2,X2*Y2,BCRSMat::random);                   //A12 is 2x2

  typedef Dune::MultiTypeBlockVector<Dune::BlockVector<Dune::FieldVector<double,1> >,Dune::BlockVector<Dune::FieldVector<double,1> > > TestVector;
  TestVector x, b;

  x[_0].resize(Y1);
  x[_1].resize(Y2);
  b[_0].resize(X1);
  b[_1].resize(X2);

  x = 1; b = 1;

  //set row sizes
  for (int i=0; i<Y1; i++)
  {
    A11.setrowsize(i,X1);
    A12.setrowsize(i,X2);
  }
  A11.endrowsizes();
  A12.endrowsizes();

  for (int i=0; i<Y2; i++)
  {
    A21.setrowsize(i,X1);
    A22.setrowsize(i,X2);
  }
  A21.endrowsizes();
  A22.endrowsizes();

  //set indices
  for (int i=0; i<X1+X2; i++)
  {
    for (int j=0; j<Y1+Y2; j++)
    {
      if (i<X1 && j<Y1)
        A11.addindex(i,j);
      if (i<X1 && j>=Y1)
        A12.addindex(i,j-Y1);
      if (i>=X1 && j<Y1)
        A21.addindex(i-X1,j);
      if (i>=X1 && j>=Y1)
        A22.addindex(i-X1,j-Y1);
    }
  }
  A11.endindices();
  A12.endindices();
  A21.endindices();
  A22.endindices();

  A11 = 0;
  A12 = 0;
  A21 = 0;
  A22 = 0;

  //fill in values (row-wise) in A11 and A22
  for (int i=0; i<Y1; i++)
  {
    if (i>0)
      A11[i][i-1]=-1;
    A11[i][i]=2;                                                        //diag
    if (i<Y1-1)
      A11[i][i+1]=-1;
    if (i<Y2)
    {                                                         //also in A22
      if (i>0)
        A22[i][i-1]=-1;
      A22[i][i]=2;
      if (i<Y2-1)
        A22[i][i+1]=-1;
    }
  }
  A12[2][0] = -1; A21[0][2] = -1;                                       //fill A12 & A21

  typedef Dune::MultiTypeBlockVector<BCRSMat,BCRSMat> BCRS_Row;
  typedef Dune::MultiTypeBlockMatrix<BCRS_Row,BCRS_Row> CM_BCRS;
  CM_BCRS A;
  A[_0][_0] = A11;
  A[_0][_1] = A12;
  A[_1][_0] = A21;
  A[_1][_1] = A22;

  x = 1;
  b = 1;

  // Set up a variety of solvers, just to make sure they compile
  MatrixAdapter<CM_BCRS,TestVector,TestVector> op(A);             // make linear operator from A
  SeqSOR<CM_BCRS,TestVector,TestVector,2> sor(A,1,1.9520932);        // SOR preconditioner
  SeqSSOR<CM_BCRS,TestVector,TestVector,2> ssor(A,1,1.0);      // SSOR preconditioner

  // Solve system using a Gauss-Seidel method
  SeqGS<CM_BCRS,TestVector,TestVector,2> gs(A,1,1);                  // GS preconditioner
  LoopSolver<TestVector> loop(op,gs,1E-4,18000,2);           // an inverse operator
  InverseOperatorResult r;

  loop.apply(x,b,r);

  // Solve system using a CG method with a Jacobi preconditioner
  SeqJac<CM_BCRS,TestVector,TestVector,2> jac(A,1,1);                // Jacobi preconditioner
  CGSolver<TestVector> cgSolver(op,jac,1E-4,18000,2);           // an inverse operator

  cgSolver.apply(x,b,r);

  // Solve system using a GMRes solver without preconditioner at all
  // Fancy (but only) way to not have a preconditioner at all
  Richardson<TestVector,TestVector> richardson(1.0);

  // Preconditioned conjugate-gradient solver
  RestartedGMResSolver<TestVector> gmres(op,
                                         richardson,
                                         1e-4,  // desired residual reduction factor
                                         5,     // number of iterations between restarts
                                         100,   // maximum number of iterations
                                         2);    // verbosity of the solver

  gmres.apply(x, b, r);

  printvector(std::cout,x[_0],"solution x1","entry",11,9,1);
  printvector(std::cout,x[_1],"solution x2","entry",11,9,1);

  return 0;
}
catch (Dune::Exception& e)
{
  std::cerr << "DUNE reported an exception: " << e << std::endl;
  return 1;
}
catch (std::exception& e)
{
  std::cerr << "C++ reported an exception: " << e.what() << std::endl;
  return 2;
} catch (...)
{
  std::cerr << "Unknown exception encountered!" << std::endl;
  return 3;
}
