#ifndef VECTORCOMMUNICATOR_HH
#define VECTORCOMMUNICATOR_HH

#include <vector>

#include <dune/grid/utility/globalindexset.hh>
#include <dune/gfe/parallel/mpifunctions.hh>


template<typename GUIndex, typename Communicator, typename VectorType>
class VectorCommunicator {

  struct TransferVectorTuple {
    typedef typename VectorType::value_type EntryType;

    size_t globalIndex_;
    EntryType value_;

    TransferVectorTuple() {}
    TransferVectorTuple(const size_t& r, const EntryType& e)
    : globalIndex_(r),
      value_(e) {}
  };

private:
  void transferVector(const VectorType& localVector) {
    // Create vector for transfer data
    std::vector<TransferVectorTuple> localVectorEntries;

    // Translate vector entries
    for (size_t k=0; k<localVector.size(); k++)
        localVectorEntries.push_back(TransferVectorTuple(guIndex.index(k), localVector[k]));

    // Get number of vector entries on each process
    localVectorEntriesSizes = MPIFunctions::shareSizes(communicator_, localVectorEntries.size());

    // Get vector entries from every process
    globalVectorEntries = MPIFunctions::gatherv(communicator_, localVectorEntries, localVectorEntriesSizes, root_rank);
  }

public:
  VectorCommunicator(const GUIndex& gi,
                     const Communicator& communicator,
                     const int& root)
  : guIndex(gi), communicator_(communicator), root_rank(root)
  {}

  VectorType reduceAdd(const VectorType& localVector)
  {
    transferVector(localVector);

    VectorType globalVector(guIndex.nGlobalEntity());
    globalVector = 0;

    for (size_t k = 0; k < globalVectorEntries.size(); ++k)
      globalVector[globalVectorEntries[k].globalIndex_] += globalVectorEntries[k].value_;

    return globalVector;
  }

  VectorType reduceCopy(const VectorType& localVector)
  {
    transferVector(localVector);

    VectorType globalVector(guIndex.nGlobalEntity());

    for (size_t k = 0; k < globalVectorEntries.size(); ++k)
      globalVector[globalVectorEntries[k].globalIndex_] = globalVectorEntries[k].value_;

    return globalVector;
  }

  VectorType scatter(const VectorType& global)
  {
    for (size_t k = 0; k < globalVectorEntries.size(); ++k)
      globalVectorEntries[k].value_ = global[globalVectorEntries[k].globalIndex_];

    const int localSize = localVectorEntriesSizes[communicator_.rank()];

    // Create vector for transfer data
    std::vector<TransferVectorTuple> localVectorEntries(localSize);

    MPIFunctions::scatterv(communicator_, localVectorEntries, globalVectorEntries, localVectorEntriesSizes, root_rank);

    // Create vector for local solution
    VectorType x(localSize);

    // And translate solution again
    for (size_t k = 0; k < localVectorEntries.size(); ++k)
      x[k] = localVectorEntries[k].value_;

    return x;
  }

private:
  const GUIndex& guIndex;
  const Communicator& communicator_;
  int root_rank;

  std::vector<int> localVectorEntriesSizes;
  std::vector<TransferVectorTuple> globalVectorEntries;
};

#endif