// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:

// start with including some headers
#include "config.h"

#include <iostream>               // for input/output to shell
#include <fstream>                // for input/output to files
#include <vector>                 // STL vector class
#include <complex>

#include <cmath>                 // Yes, we do some math here
#include <sys/times.h>            // for timing measurements

#include <dune/istl/istlexception.hh>
#include <dune/istl/basearray.hh>
#include <dune/common/fvector.hh>
#include <dune/common/fmatrix.hh>
#include <dune/istl/bvector.hh>
#include <dune/istl/vbvector.hh>
#include <dune/istl/bcrsmatrix.hh>
#include <dune/istl/io.hh>
#include <dune/istl/gsetc.hh>
#include <dune/istl/ilu.hh>
#include <dune/istl/operators.hh>
#include <dune/istl/solvers.hh>
#include <dune/istl/preconditioners.hh>
#include <dune/istl/scalarproducts.hh>

#include <dune/istl/multitypeblockvector.hh>
#include <dune/istl/multitypeblockmatrix.hh>

// a simple stop watch
class Timer
{
public:
  Timer ()
  {
    struct tms buf;
    cstart = times(&buf);
  }

  void start ()
  {
    struct tms buf;
    cstart = times(&buf);
  }

  double stop ()
  {
    struct tms buf;
    cend = times(&buf);
    return ((double)(cend-cstart))/100.0;
  }

  double gettime ()
  {
    return ((double)(cend-cstart))/100.0;
  }

private:
  clock_t cstart,cend;
};


// testing codes
void test_basearray ()
{
  // what you can do with base_array

  // allocation
  typedef double Type; // any type
  Dune::base_array<Type> a(20);

  // modifying iterator
  for (Dune::base_array<Type>::iterator i=a.begin(); i!=a.end(); ++i)
    *i = 1.0;

  // read only iterator
  Type sum=0;
  for (Dune::base_array<Type>::const_iterator i=a.begin(); i!=a.end(); ++i)
    sum += *i;

  // random access
  a[4] = 17;
  sum = a[3];

  // empty array
  Dune::base_array<Type> b;

  // assignment
  b = a;

  // window mode
  Type p[13];
  Dune::base_array_window<Type> c(p+4,3); // c contains p[4]...p[6]

  // copy window
  b = c;
  Dune::base_array<Type> d(c);

  // move window to p[6]...p[10]
  c.move(2,5);
}

template<class V>
void f (V& v)
{
  typedef typename V::Iterator iterator;
  for (iterator i=v.begin(); i!=v.end(); ++i)
    *i = i.index();

  typedef typename V::ConstIterator const_iterator;
  for (const_iterator i=v.begin(); i!=v.end(); ++i)
    std::cout << (*i).two_norm() << std::endl;
}

void test_BlockVector ()
{
  Dune::BlockVector<Dune::FieldVector<std::complex<double>,2> > v(20);

  v[1] = 3.14;
  v[3][0] = 2.56;
  v[3][1] = std::complex<double>(1,-1);

  f(v);

  typedef Dune::FieldVector<double,1> R1;

  const int n=480;

  // make two vectors of size n
  Dune::BlockVector<R1> x(n),y(n);

  // assign from scalar
  x = 1.0;
  y = 5.3435E-6;

  // use of Iterator
  int k = 0;
  for (Dune::BlockVector<R1>::Iterator i=x.begin(); i!=x.end(); ++i)
    *i = k++;

  // and ConstIterator
  R1 z;
  for (Dune::BlockVector<R1>::ConstIterator i=x.begin(); i!=x.end(); ++i)
    z += *i;

  // assignment
  y = x;

  // random access
  x[2] = y[7];

  // timing the axpy operation
  Timer watch;
  double t;
  int i;

  for (i=1; i<1000000000; i*=2)
  {
    watch.start();
    for (int j=0; j<i; ++j)
      x.axpy(1.001,y);
    t = watch.stop();
    if (t>1.0) break;
  }

  std::cout << "axpy:"
            << " n=" << x.dim()
            << " i=" << i
            << " t=" << t
            << " mflop=" << 2.0*x.dim()*((double)i)/t/1E6
            << std::endl;

  // timing the dot operation
  x = 1.234E-3;
  y = 4.938E-1;

  double sum = 0;
  for (i=1; i<1000000000; i*=2)
  {
    watch.start();
    sum = 0;
    for (int j=0; j<i; ++j)
      sum += x*y;
    t = watch.stop();
    if (t>1.0) break;
  }

  std::cout << " dot:"
            << " n=" << x.dim()
            << " i=" << i
            << " t=" << t
            << " mflop=" << 2.0*x.dim()*((double)i)/t/1E6
            << " sum=" << sum
            << std::endl;
}


void test_VariableBlockVector ()
{
  const int N=1;
  typedef Dune::FieldVector<double,N> RN;

  typedef Dune::VariableBlockVector<RN> Vector;

  Vector x(20);

  for (Vector::CreateIterator i=x.createbegin(); i!=x.createend(); ++i)
    i.setblocksize((i.index()%10)+1);

  x = 1.0;

  Vector::block_type xi;

  xi = x[13];

  RN b;

  b = x[13][1];
}

void test_FieldMatrix ()
{
  const int r=4, c=5;
  typedef Dune::FieldMatrix<double,r,c> Mrc;
  typedef Dune::FieldVector<double,r> Rr;
  typedef Dune::FieldVector<double,c> Rc;

  Mrc A,B;

  A[1][3] = 4.33;

  Rr b;
  Rc z;

  for (Mrc::RowIterator i=A.begin(); i!=A.end(); ++i)
    for (Mrc::ColIterator j=(*i).begin(); j!=(*i).end(); ++j)
      *j = i.index()*j.index();

  for (Mrc::RowIterator i=A.begin(); i!=A.end(); ++i)
    for (Mrc::ColIterator j=(*i).begin(); j!=(*i).end(); ++j)
      b[i.index()] = *j * z[j.index()];

  A = 1;
  B = 2;

  A += B;
  A -= B;
  A *= 3.14;
  A /= 3.14;

  A.umv(z,b);
  A.umtv(b,z);
  A.umhv(b,z);
  A.usmv(-1.0,z,b);
  A.usmtv(-1.0,b,z);
  A.usmhv(-1.0,b,z);

  std::cout << A.frobenius_norm() << " " << A.frobenius_norm2() << std::endl;
  std::cout << A.infinity_norm() << " " << A.infinity_norm_real() << std::endl;
}

void test_BCRSMatrix ()
{
  const int N=13,K=2;
  typedef Dune::FieldMatrix<double,2,2> LittleBlock;
  typedef Dune::BCRSMatrix<LittleBlock> BCRSMat;

  LittleBlock D;
  D = 2.56;

  BCRSMat C(N,N,N*(2*K+1),BCRSMat::row_wise);

  for (BCRSMat::CreateIterator i=C.createbegin(); i!=C.createend(); ++i)
    for (int j=-K; j<=K; ++j)
      i.insert((i.index()+N+j)%N);

  for (BCRSMat::RowIterator i=C.begin(); i!=C.end(); ++i)
    for (BCRSMat::ColIterator j=(*i).begin(); j!=(*i).end(); ++j)
      *j = D;
}

void test_IO ()
{
  typedef Dune::FieldVector<double,2> R;
  Dune::BlockVector<R> x(84);

  for (Dune::BlockVector<R>::Iterator i=x.begin(); i!=x.end(); ++i)
    *i = 0.01*i.index();

  printvector(std::cout,x,"a test","entry",11,9,1);

  Dune::VariableBlockVector<R> y(20);

  for (Dune::VariableBlockVector<R>::CreateIterator i=y.createbegin(); i!=y.createend(); ++i)
    i.setblocksize((i.index()%3)+1);

  for (Dune::VariableBlockVector<R>::Iterator i=y.begin(); i!=y.end(); ++i)
    *i = (i.index()%3)+1;

  printvector(std::cout,y,"a test","entry",11,9,1);

  typedef Dune::FieldMatrix<double,2,2> M;
  M A;
  A = 3.14;
  printmatrix(std::cout,A,"a fixed size block matrix","row",9,1);

  const int N=9,K=2;
  Dune::BCRSMatrix<M> C(N,N,N*(2*K+1),Dune::BCRSMatrix<M>::row_wise);

  for (Dune::BCRSMatrix<M>::CreateIterator i=C.createbegin(); i!=C.createend(); ++i)
    for (int j=-K; j<=K; ++j)
      i.insert((i.index()+N+j)%N);

  for (Dune::BCRSMatrix<M>::RowIterator i=C.begin(); i!=C.end(); ++i)
    for (Dune::BCRSMatrix<M>::ColIterator j=(*i).begin(); j!=(*i).end(); ++j)
      *j = A;

  Dune::BCRSMatrix<M> B(4,4,Dune::BCRSMatrix<M>::random);

  B.setrowsize(0,1);
  B.setrowsize(3,4);
  B.setrowsize(2,2);
  B.setrowsize(1,1);

  B.endrowsizes();

  B.addindex(0,0);
  B.addindex(3,1);
  B.addindex(2,2);
  B.addindex(1,1);
  B.addindex(2,0);
  B.addindex(3,2);
  B.addindex(3,0);
  B.addindex(3,3);

  B.endindices();

  B[0][0] = 1;
  B[1][1] = 2;
  B[2][0] = 3;
  B[2][2] = 4;
  B[3][1] = 5;
  B[3][2] = 6;
  B[3][0] = 7;
  B[3][3] = 8;

  printmatrix(std::cout,B,"a block compressed sparse matrix","row",9,1);
}

void test_Iter ()
{
  Timer t;

  // block types
  const int BlockSize = 6;
  typedef Dune::FieldVector<double,BlockSize> VB;
  typedef Dune::FieldMatrix<double,BlockSize,BlockSize> MB;

  // a fake discretization
  t.start();

  // build little blocks
  MB D(0.0);
  for (int i=0; i<BlockSize; i++)
    for (int j=0; j<BlockSize; j++)
      if (i==j) D[i][j] = 4+(BlockSize-1);else D[i][j] = -1;
  //   printmatrix(std::cout,D,"diagonal block","row",10,2);

  MB E(0.0);
  for (int i=0; i<BlockSize; i++)
    E[i][i] = -1;
  //   printmatrix(std::cout,E,"offdiagonal block","row",10,2);

  // make a block compressed row matrix with five point stencil
  const unsigned N=10000, BW1=1, BW2=100;
  Dune::BCRSMatrix<MB> A(N,N,5*N,Dune::BCRSMatrix<MB>::row_wise);
  for (Dune::BCRSMatrix<MB>::CreateIterator i=A.createbegin(); i!=A.createend(); ++i)
  {
    i.insert(i.index());
    if (i.index() >= BW1    ) i.insert(i.index()-BW1);
    if (i.index() +  BW1 < N) i.insert(i.index()+BW1);
    if (i.index() >= BW2    ) i.insert(i.index()-BW2);
    if (i.index() +  BW2 < N) i.insert(i.index()+BW2);
  }
  for (Dune::BCRSMatrix<MB>::RowIterator i=A.begin(); i!=A.end(); ++i)
    for (Dune::BCRSMatrix<MB>::ColIterator j=(*i).begin(); j!=(*i).end(); ++j)
      if (i.index()==j.index())
        (*j) = D;
      else
        (*j) = E;
  t.stop();
  std::cout << "time for build=" << t.gettime() << " seconds." << std::endl;
  //   printmatrix(std::cout,A,"system matrix","row",8,1);

  // set up system
  Dune::BlockVector<VB> x(N),b(N),d(N);
  x=0; x[0]=1; x[N-1]=2;
  //   printvector(std::cout,x,"exact solution","entry",10,10,2);
  b=0; A.umv(x,b); // set right hand side
  x=0;             // initial guess

  // solve in defect formulation
  std::cout.setf(std::ios_base::scientific, std::ios_base::floatfield);
  std::cout.precision(8);
  t.start();
  d=b; A.mmv(x,d); // compute defect
  std::cout << 0 << " " << d.two_norm() << std::endl;
  Dune::BlockVector<VB> v(x); // memory for update
  //  double w=1.0;               // damping factor
  //  printmatrix(std::cout,A,"system matrix","row",12,4);
  Dune::BCRSMatrix<MB> ILU(A);
  bilu0_decomposition(ILU);
  //  printmatrix(std::cout,ILU,"ilu decomposition","row",12,4);
  for (int k=1; k<=20; k++)
  {
    v=0;
    bilu_backsolve(ILU,v,d);
    //    dbgs(A,v,d,w);       // compute update
    //    dbjac(A,v,d,w);       // compute update
    //            bsorf(A,v,d,w);    //   compute update
    //            bsorb(A,v,d,w);    //   compute update
    x += v;                    // update solution
    A.mmv(v,d);                // update defect
    //    bltsolve(A,v,d,w);   // compute update
    //    x += v;              // update solution
    //    A.mmv(v,d);          // update defect
    //    butsolve(A,v,d,w);   // compute update
    //    x += v;              // update solution
    //    A.mmv(v,d);          // update defect
    std::cout << k << " " << d.two_norm() << std::endl;
    if (d.two_norm()<1E-4) break;
  }
  t.stop();
  std::cout << "time for solve=" << t.gettime() << " seconds." << std::endl;
}


void test_Interface ()
{
  // define Types
  const int BlockSize = 1;
  typedef Dune::FieldVector<double,BlockSize> VB;
  typedef Dune::FieldMatrix<double,BlockSize,BlockSize> MB;
  typedef Dune::BlockVector<VB> Vector;
  typedef Dune::BCRSMatrix<MB> Matrix;

  // build little blocks
  MB D=0;
  for (int i=0; i<BlockSize; i++)
    for (int j=0; j<BlockSize; j++)
      if (i==j) D[i][j] = 4+(BlockSize-1);else D[i][j] = -1;

  MB E=0;
  for (int i=0; i<BlockSize; i++)
    E[i][i] = -1;

  // make a block compressed row matrix with five point stencil
  const int BW2=31, N=BW2*BW2;
  Matrix A(N,N,5*N,Dune::BCRSMatrix<MB>::row_wise);
  for (Matrix::CreateIterator i=A.createbegin(); i!=A.createend(); ++i)
  {
    int row=i.index()/BW2;
    int col=i.index()%BW2;
    i.insert(i.index());
    if (col-1>=0) i.insert(i.index()-1);
    if (col+1<BW2) i.insert(i.index()+1);
    if (row-1>=0) i.insert(i.index()-BW2);
    if (row+1<BW2) i.insert(i.index()+BW2);
  }
  for (Matrix::RowIterator i=A.begin(); i!=A.end(); ++i)
    for (Matrix::ColIterator j=(*i).begin(); j!=(*i).end(); ++j)
      if (i.index()==j.index())
        (*j) = D;
      else
        (*j) = E;

  //  printmatrix(std::cout,A,"system matrix","row",10,2);

  // set up system
  Vector x(N),b(N);
  x=0; x[0]=1; x[N-1]=2; // prescribe known solution
  b=0; A.umv(x,b);       // set right hand side accordingly
  x=1;                   // initial guess
  for (int i=0; i<N; i++)
    x[i] = i*0.1;

  // set up the high-level solver objects
  Dune::MatrixAdapter<Matrix,Vector,Vector> op(A);        // make linear operator from A
  Dune::SeqJac<Matrix,Vector,Vector> jac(A,1,1);          // Jacobi preconditioner
  Dune::SeqGS<Matrix,Vector,Vector> gs(A,1,1);            // GS preconditioner
  Dune::SeqSOR<Matrix,Vector,Vector> sor(A,1,1.9520932);  // SSOR preconditioner
  Dune::SeqSSOR<Matrix,Vector,Vector> ssor(A,1,1.0); // SSOR preconditioner
  Dune::SeqILU0<Matrix,Vector,Vector> ilu0(A,1.0);        // preconditioner object
  Dune::SeqILUn<Matrix,Vector,Vector> ilu1(A,1,0.92);     // preconditioner object

  Dune::LoopSolver<Vector> loop(op,jac,1E-4,18000,2);     // an inverse operator
  Dune::CGSolver<Vector> cg(op,ilu0,1E-4,8000,2);         // an inverse operator
  Dune::BiCGSTABSolver<Vector> bcgs(op,ilu1,1E-8,8000,2); // an inverse operator
  Dune::GradientSolver<Vector> gras(op,jac,1E-4,18000,2);         // an inverse operator

  // call the solver
  Dune::InverseOperatorResult r;
  loop.apply(x,b,r);
}


#ifdef HAVE_BOOST_FUSION

void test_MultiTypeBlockVector_MultiTypeBlockMatrix() {                           //Jacobi Solver Test MultiTypeBlockMatrix_Solver::dbjac on MultiTypeBlockMatrix<BCRSMatrix>

  std::cout << "\n\n\nJacobi Solver Test on MultiTypeBlockMatrix<BCRSMatrix>\n";

  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;

  fusion::at_c<0>(x).resize(Y1);
  fusion::at_c<1>(x).resize(Y2);
  fusion::at_c<0>(b).resize(X1);
  fusion::at_c<1>(b).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;
  fusion::at_c<0>(fusion::at_c<0>(A)) = A11;
  fusion::at_c<1>(fusion::at_c<0>(A)) = A12;
  fusion::at_c<0>(fusion::at_c<1>(A)) = A21;
  fusion::at_c<1>(fusion::at_c<1>(A)) = A22;

  printmatrix(std::cout,A11,"matrix A11","row",9,1);
  printmatrix(std::cout,A12,"matrix A12","row",9,1);
  printmatrix(std::cout,A21,"matrix A21","row",9,1);
  printmatrix(std::cout,A22,"matrix A22","row",9,1);

  x = 1;
  b = 1;

  Dune::MatrixAdapter<CM_BCRS,TestVector,TestVector> op(A);             // make linear operator from A
  Dune::SeqJac<CM_BCRS,TestVector,TestVector,2> jac(A,1,1);                // Jacobi preconditioner
  Dune::SeqGS<CM_BCRS,TestVector,TestVector,2> gs(A,1,1);                  // GS preconditioner
  Dune::SeqSOR<CM_BCRS,TestVector,TestVector,2> sor(A,1,1.9520932);        // SSOR preconditioner
  Dune::SeqSSOR<CM_BCRS,TestVector,TestVector,2> ssor(A,1,1.0);      // SSOR preconditioner

  Dune::LoopSolver<TestVector> loop(op,gs,1E-4,18000,2);           // an inverse operator
  Dune::InverseOperatorResult r;

  loop.apply(x,b,r);

  printvector(std::cout,fusion::at_c<0>(x),"solution x1","entry",11,9,1);
  printvector(std::cout,fusion::at_c<1>(x),"solution x2","entry",11,9,1);

}
#endif




int main (int /*argc*/, char ** /*argv*/)
{
  try {
    test_basearray();
    test_BlockVector();
    test_VariableBlockVector();
    test_FieldMatrix();
    test_BCRSMatrix();
    test_IO();
    test_Iter();
    test_Interface();
#ifdef HAVE_BOOST_FUSION
    test_MultiTypeBlockVector_MultiTypeBlockMatrix();
#endif
  }
  catch (Dune::ISTLError& error)
  {
    std::cout << error << std::endl;
  }
  catch (Dune::Exception& error)
  {
    std::cout << error << std::endl;
  }
  catch (const std::bad_alloc& e)
  {
    std::cout << "memory exhausted" << std::endl;
  }
  catch (...)
  {
    std::cout << "unknown exception caught" << std::endl;
  }

  return 0;
}
