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

#include <cmath>
#include <complex>
#include <iostream>
#include <memory>

#include "istlexception.hh"
#include "bvector.hh"

/** \file
 * \brief ???
 */

namespace Dune {

  /**
              @addtogroup ISTL_SPMV
              @{
   */

  /**
      \brief A Vector of blocks with different blocksizes.

          implements a vector consisting of a number of blocks (to
          be given at run-time) which themselves consist of a number
          of blocks (also given at run-time) of the given type B.

          VariableBlockVector is a container of containers!

   */
  template<class B, class A=std::allocator<B> >
  class VariableBlockVector : public block_vector_unmanaged<B,A>
                              // this derivation gives us all the blas level 1 and norms
                              // on the large array. However, access operators have to be
                              // overwritten.
  {
  public:

    //===== type definitions and constants

    //! export the type representing the field
    typedef typename B::field_type field_type;

    //! export the allocator type
    typedef A allocator_type;

    //! The size type for the index access
    typedef typename A::size_type size_type;

    /** export the type representing the components, note that this
            is *not* the type refered to by the iterators and random access.
            However, it can be used to copy blocks (which is its only purpose).
     */
    typedef BlockVector<B,A> block_type;

    /** increment block level counter, yes, it is two levels because
            VariableBlockVector is a container of containers
     */
    enum {
      //! The number of blocklevels this vector contains.
      blocklevel = B::blocklevel+2
    };

    // just a shorthand
    typedef BlockVectorWindow<B,A> window_type;


    //===== constructors and such

    /** constructor without arguments makes empty vector,
            object cannot be used yet
     */
    VariableBlockVector () : block_vector_unmanaged<B,A>()
    {
      // nothing is known ...
      nblocks = 0;
      block = 0;
      initialized = false;
    }

    /** make vector with given number of blocks, but size of each block is not yet known,
            object cannot be used yet
     */
    explicit VariableBlockVector (size_type _nblocks) : block_vector_unmanaged<B,A>()
    {
      // we can allocate the windows now
      nblocks = _nblocks;
      if (nblocks>0)
      {
        block = windowAllocator_.allocate(nblocks);
        new (block) window_type[nblocks];
      }
      else
      {
        nblocks = 0;
        block = 0;;
      }

      // Note: memory in base class still not allocated
      // the vector not usable
      initialized = false;
    }

    /** make vector with given number of blocks each having a constant size,
            object is fully usable then.

            \param _nblocks Number of blocks
            \param m Number of elements in each block
     */
    VariableBlockVector (size_type _nblocks, size_type m) : block_vector_unmanaged<B,A>()
    {
      // and we can allocate the big array in the base class
      this->n = _nblocks*m;
      if (this->n>0)
      {
        this->p = allocator_.allocate(this->n);
        new (this->p)B[this->n];
      }
      else
      {
        this->n = 0;
        this->p = 0;
      }

      // we can allocate the windows now
      nblocks = _nblocks;
      if (nblocks>0)
      {
        // allocate and construct the windows
        block = windowAllocator_.allocate(nblocks);
        new (block) window_type[nblocks];

        // set the windows into the big array
        for (size_type i=0; i<nblocks; ++i)
          block[i].set(m,this->p+(i*m));
      }
      else
      {
        nblocks = 0;
        block = 0;;
      }

      // and the vector is usable
      initialized = true;
    }

    //! copy constructor, has copy semantics
    VariableBlockVector (const VariableBlockVector& a)
    {
      // allocate the big array in the base class
      this->n = a.n;
      if (this->n>0)
      {
        // allocate and construct objects
        this->p = allocator_.allocate(this->n);
        new (this->p)B[this->n];

        // copy data
        for (size_type i=0; i<this->n; i++) this->p[i]=a.p[i];
      }
      else
      {
        this->n = 0;
        this->p = 0;
      }

      // we can allocate the windows now
      nblocks = a.nblocks;
      if (nblocks>0)
      {
        // alloc
        block = windowAllocator_.allocate(nblocks);
        new (block) window_type[nblocks];

        // and we must set the windows
        block[0].set(a.block[0].getsize(),this->p);           // first block
        for (size_type i=1; i<nblocks; ++i)                         // and the rest
          block[i].set(a.block[i].getsize(),block[i-1].getptr()+block[i-1].getsize());
      }
      else
      {
        nblocks = 0;
        block = 0;;
      }

      // and we have a usable vector
      initialized = true;
    }

    //! free dynamic memory
    ~VariableBlockVector ()
    {
      if (this->n>0) {
        size_type i=this->n;
        while (i)
          this->p[--i].~B();
        allocator_.deallocate(this->p,this->n);
      }
      if (nblocks>0) {
        size_type i=nblocks;
        while (i)
          block[--i].~window_type();
        windowAllocator_.deallocate(block,nblocks);
      }

    }


    //! same effect as constructor with same argument
    void resize (size_type _nblocks)
    {
      // deconstruct objects and deallocate memory if necessary
      if (this->n>0) {
        size_type i=this->n;
        while (i)
          this->p[--i].~B();
        allocator_.deallocate(this->p,this->n);
      }
      if (nblocks>0) {
        size_type i=nblocks;
        while (i)
          block[--i].~window_type();
        windowAllocator_.deallocate(block,nblocks);
      }
      this->n = 0;
      this->p = 0;

      // we can allocate the windows now
      nblocks = _nblocks;
      if (nblocks>0)
      {
        block = windowAllocator_.allocate(nblocks);
        new (block) window_type[nblocks];
      }
      else
      {
        nblocks = 0;
        block = 0;;
      }

      // and the vector not fully usable
      initialized = false;
    }

    //! same effect as constructor with same argument
    void resize (size_type _nblocks, size_type m)
    {
      // deconstruct objects and deallocate memory if necessary
      if (this->n>0) {
        size_type i=this->n;
        while (i)
          this->p[--i].~B();
        allocator_.deallocate(this->p,this->n);
      }
      if (nblocks>0) {
        size_type i=nblocks;
        while (i)
          block[--i].~window_type();
        windowAllocator_.deallocate(block,nblocks);
      }

      // and we can allocate the big array in the base class
      this->n = _nblocks*m;
      if (this->n>0)
      {
        this->p = allocator_.allocate(this->n);
        new (this->p)B[this->n];
      }
      else
      {
        this->n = 0;
        this->p = 0;
      }

      // we can allocate the windows now
      nblocks = _nblocks;
      if (nblocks>0)
      {
        // allocate and construct objects
        block = windowAllocator_.allocate(nblocks);
        new (block) window_type[nblocks];

        // set the windows into the big array
        for (size_type i=0; i<nblocks; ++i)
          block[i].set(m,this->p+(i*m));
      }
      else
      {
        nblocks = 0;
        block = 0;;
      }

      // and the vector is usable
      initialized = true;
    }

    //! assignment
    VariableBlockVector& operator= (const VariableBlockVector& a)
    {
      if (&a!=this)     // check if this and a are different objects
      {
        // reallocate arrays if necessary
        // Note: still the block sizes may vary !
        if (this->n!=a.n || nblocks!=a.nblocks)
        {
          // deconstruct objects and deallocate memory if necessary
          if (this->n>0) {
            size_type i=this->n;
            while (i)
              this->p[--i].~B();
            allocator_.deallocate(this->p,this->n);
          }
          if (nblocks>0) {
            size_type i=nblocks;
            while (i)
              block[--i].~window_type();
            windowAllocator_.deallocate(block,nblocks);
          }

          // allocate the big array in the base class
          this->n = a.n;
          if (this->n>0)
          {
            // allocate and construct objects
            this->p = allocator_.allocate(this->n);
            new (this->p)B[this->n];
          }
          else
          {
            this->n = 0;
            this->p = 0;
          }

          // we can allocate the windows now
          nblocks = a.nblocks;
          if (nblocks>0)
          {
            // alloc
            block = windowAllocator_.allocate(nblocks);
            new (block) window_type[nblocks];
          }
          else
          {
            nblocks = 0;
            block = 0;;
          }
        }

        // copy block structure, might be different although
        // sizes are the same !
        if (nblocks>0)
        {
          block[0].set(a.block[0].getsize(),this->p);                 // first block
          for (size_type i=1; i<nblocks; ++i)                               // and the rest
            block[i].set(a.block[i].getsize(),block[i-1].getptr()+block[i-1].getsize());
        }

        // and copy the data
        for (size_type i=0; i<this->n; i++) this->p[i]=a.p[i];
      }

      // and we have a usable vector
      initialized = true;

      return *this;     // Gebe Referenz zurueck damit a=b=c; klappt
    }


    //===== assignment from scalar

    //! assign from scalar
    VariableBlockVector& operator= (const field_type& k)
    {
      (static_cast<block_vector_unmanaged<B,A>&>(*this)) = k;
      return *this;
    }


    //===== the creation interface

    //! Iterator class for sequential creation of blocks
    class CreateIterator
    {
    public:
      //! constructor
      CreateIterator (VariableBlockVector& _v, int _i) : v(_v)
      {
        i = _i;
        k = 0;
        n = 0;
      }

      //! prefix increment
      CreateIterator& operator++()
      {
        // we are at block i and the blocks size is known

        // set the blocks size to current k
        v.block[i].setsize(k);

        // accumulate total size
        n += k;

        // go to next block
        ++i;

        // reset block size
        k = 0;

        // if we are past the last block, finish off
        if (i==v.nblocks)
        {
          // now we can allocate the big array in the base class of v
          v.n = n;
          if (n>0)
          {
            // allocate and construct objects
            v.p = v.allocator_.allocate(n);
            new (v.p)B[n];
          }
          else
          {
            v.n = 0;
            v.p = 0;
          }

          // and we set the window pointer
          if (v.nblocks>0)
          {
            v.block[0].setptr(v.p);                       // pointer tofirst block
            for (size_type j=1; j<v.nblocks; ++j)               // and the rest
              v.block[j].setptr(v.block[j-1].getptr()+v.block[j-1].getsize());
          }

          // and the vector is ready
          v.initialized = true;

          //std::cout << "made vbvector with " << v.n << " components" << std::endl;
        }

        return *this;
      }

      //! inequality
      bool operator!= (const CreateIterator& it) const
      {
        return (i!=it.i) || (&v!=&it.v);
      }

      //! equality
      bool operator== (const CreateIterator& it) const
      {
        return (i==it.i) && (&v==&it.v);
      }

      //! dereferencing
      size_type index () const
      {
        return i;
      }

      //! set size of current block
      void setblocksize (size_type _k)
      {
        k = _k;
      }

    private:
      VariableBlockVector& v;     // my vector
      size_type i;                      // current block to be defined
      size_type k;                      // size of current block to be defined
      size_type n;                      // total number of elements to be allocated
    };

    // CreateIterator wants to set all the arrays ...
    friend class CreateIterator;

    //! get initial create iterator
    CreateIterator createbegin ()
    {
#ifdef DUNE_ISTL_WITH_CHECKING
      if (initialized) DUNE_THROW(ISTLError,"no CreateIterator in initialized state");
#endif
      return CreateIterator(*this,0);
    }

    //! get create iterator pointing to one after the last block
    CreateIterator createend ()
    {
      return CreateIterator(*this,nblocks);
    }


    //===== access to components
    // has to be overwritten from base class because it must
    // return access to the windows

    //! random access to blocks
    window_type& operator[] (size_type i)
    {
#ifdef DUNE_ISTL_WITH_CHECKING
      if (i>=nblocks) DUNE_THROW(ISTLError,"index out of range");
#endif
      return block[i];
    }

    //! same for read only access
    const window_type& operator[] (size_type i) const
    {
#ifdef DUNE_ISTL_WITH_CHECKING
      if (i<0 || i>=nblocks) DUNE_THROW(ISTLError,"index out of range");
#endif
      return block[i];
    }

    // forward declaration
    class ConstIterator;

    //! Iterator class for sequential access
    class Iterator
    {
    public:
      //! constructor, no arguments
      Iterator ()
      {
        p = 0;
        i = 0;
      }

      //! constructor
      Iterator (window_type* _p, size_type _i) : p(_p), i(_i)
      {       }

      //! prefix increment
      Iterator& operator++()
      {
        ++i;
        return *this;
      }

      //! prefix decrement
      Iterator& operator--()
      {
        --i;
        return *this;
      }

      //! equality
      bool operator== (const Iterator& it) const
      {
        return (p+i)==(it.p+it.i);
      }

      //! inequality
      bool operator!= (const Iterator& it) const
      {
        return (p+i)!=(it.p+it.i);
      }

      //! equality
      bool operator== (const ConstIterator& it) const
      {
        return (p+i)==(it.p+it.i);
      }

      //! inequality
      bool operator!= (const ConstIterator& it) const
      {
        return (p+i)!=(it.p+it.i);
      }

      //! dereferencing
      window_type& operator* () const
      {
        return p[i];
      }

      //! arrow
      window_type* operator-> () const
      {
        return p+i;
      }

      // return index corresponding to pointer
      size_type index () const
      {
        return i;
      }

      friend class ConstIterator;

    private:
      window_type* p;
      size_type i;
    };

    //! begin Iterator
    Iterator begin ()
    {
      return Iterator(block,0);
    }

    //! end Iterator
    Iterator end ()
    {
      return Iterator(block,nblocks);
    }

    //! @returns an iterator that is positioned before
    //! the end iterator of the vector, i.e. at the last entry.
    Iterator beforeEnd ()
    {
      return Iterator(block,nblocks-1);
    }

    //! @returns an iterator that is positioned before
    //! the first entry of the vector.
    Iterator beforeBegin () const
    {
      return Iterator(block,-1);
    }

    //! random access returning iterator (end if not contained)
    Iterator find (size_type i)
    {
      if (i>=0 && i<nblocks)
        return Iterator(block,i);
      else
        return Iterator(block,nblocks);
    }

    //! random access returning iterator (end if not contained)
    ConstIterator find (size_type i) const
    {
      if (i>=0 && i<nblocks)
        return ConstIterator(block,i);
      else
        return ConstIterator(block,nblocks);
    }

    //! ConstIterator class for sequential access
    class ConstIterator
    {
    public:
      //! constructor
      ConstIterator ()
      {
        p = 0;
        i = 0;
      }

      //! constructor from pointer
      ConstIterator (const window_type* _p, size_type _i) : p(_p), i(_i)
      {       }

      //! constructor from non_const iterator
      ConstIterator (const Iterator& it) : p(it.p), i(it.i)
      {       }

      //! prefix increment
      ConstIterator& operator++()
      {
        ++i;
        return *this;
      }

      //! prefix decrement
      ConstIterator& operator--()
      {
        --i;
        return *this;
      }

      //! equality
      bool operator== (const ConstIterator& it) const
      {
        return (p+i)==(it.p+it.i);
      }

      //! inequality
      bool operator!= (const ConstIterator& it) const
      {
        return (p+i)!=(it.p+it.i);
      }

      //! equality
      bool operator== (const Iterator& it) const
      {
        return (p+i)==(it.p+it.i);
      }

      //! inequality
      bool operator!= (const Iterator& it) const
      {
        return (p+i)!=(it.p+it.i);
      }

      //! dereferencing
      const window_type& operator* () const
      {
        return p[i];
      }

      //! arrow
      const window_type* operator-> () const
      {
        return p+i;
      }

      // return index corresponding to pointer
      size_type index () const
      {
        return i;
      }

      friend class Iterator;

    private:
      const window_type* p;
      size_type i;
    };

    //! begin ConstIterator
    ConstIterator begin () const
    {
      return ConstIterator(block,0);
    }

    //! end ConstIterator
    ConstIterator end () const
    {
      return ConstIterator(block,nblocks);
    }

    //! @returns an iterator that is positioned before
    //! the end iterator of the vector. i.e. at the last element.
    ConstIterator beforeEnd() const
    {
      return ConstIterator(block,nblocks-1);
    }

    //! end ConstIterator
    ConstIterator rend () const
    {
      return ConstIterator(block,-1);
    }


    //===== sizes

    //! number of blocks in the vector (are of variable size here)
    size_type N () const
    {
      return nblocks;
    }


  private:
    size_type nblocks;            // number of blocks in vector
    window_type* block;     // array of blocks pointing to the array in the base class
    bool initialized;       // true if vector has been initialized

    A allocator_;

    typename A::template rebind<window_type>::other windowAllocator_;
  };



  /** @} end documentation */

} // end namespace

#endif
