#ifndef _COMMON_HPDDM_
#define _COMMON_HPDDM_

#include <math.h>
#if defined(__clang__)
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
#elif defined(__GNUC__) || defined(__GNUG__)
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif
#include <mpi.h>
#if defined(__clang__)
  #pragma clang diagnostic pop
#elif defined(__GNUC__) || defined(__GNUG__)
  #pragma GCC diagnostic pop
#endif
#include <ff++.hpp>
#include <AFunction_ext.hpp>
#include <array_tlp.hpp>
#include <unordered_map>
#define GENERATE_DEPRECATED_FUNCTIONS

#if defined(WITH_bemtool) && defined(PETSC_HAVE_HTOOL)
#if defined(__clang__)
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wunused-parameter"
  #pragma clang diagnostic ignored "-Wextra-semi"
  #pragma clang diagnostic ignored "-Wextra-semi-stmt"
  #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
  #pragma clang diagnostic ignored "-Wvla-cxx-extension"
  #pragma clang diagnostic ignored "-Wundef"
  #pragma clang diagnostic ignored "-Wdouble-promotion"
#elif defined(__GNUC__) || defined(__GNUG__)
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wunused-parameter"
  #pragma GCC diagnostic ignored "-Wextra-semi"
  #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
  #pragma GCC diagnostic ignored "-Wvla"
  #pragma GCC diagnostic ignored "-Wundef"
  #pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif
#include <bemtool/tools.hpp>
#include <bemtool/fem/dof.hpp>
#include <bemtool/operator/operator.hpp>
#include <bemtool/miscellaneous/htool_wrap.hpp>
#if defined(__clang__)
  #pragma clang diagnostic pop
#elif defined(__GNUC__) || defined(__GNUG__)
  #pragma GCC diagnostic pop
#endif
#include "common_bem.hpp"
#endif

#if defined(WITH_mkl) && !defined(HTOOL_HTOOL_HPP)
#define HPDDM_MKL 1
#define MKL_Complex8 std::complex<float>
#define MKL_Complex16 std::complex<double>
#include <mkl.h>
#endif

#if defined(HPDDM_SCHWARZ) && (HPDDM_SCHWARZ || HPDDM_FETI || HPDDM_BDD)
#ifdef WITH_mkl
#define MKL_PARDISOSUB
#elif defined(WITH_mumps)
#define MUMPSSUB
#else
#define SUITESPARSESUB
#endif

#ifdef WITH_mumps
#define DMUMPS
#else
#define DSUITESPARSE
#undef HPDDM_INEXACT_COARSE_OPERATOR
#define HPDDM_INEXACT_COARSE_OPERATOR 0
#endif
#if defined(HPDDM_PRECISION) && HPDDM_PRECISION == 2
#if defined(WITH_slepc) || defined(WITH_slepccomplex)
#define MU_SLEPC
#elif defined(HAVE_LIBARPACK)
#define MU_ARPACK
#else
#undef HPDDM_INEXACT_COARSE_OPERATOR
#define HPDDM_INEXACT_COARSE_OPERATOR 0
#endif
#endif
#endif

#define HPDDM_NUMBERING 'C'
#undef CBLAS_H

#include <HPDDM.hpp>

#if !defined(_HPDDM_)
#define HPDDM_n n_
#define HPDDM_m m_
#define HPDDM_sym sym_
#define HPDDM_ia ia_
#define HPDDM_ja ja_
#define HPDDM_a a_
#define HPDDM_cc cc_
#define HPDDM_nnz nnz_
#define HPDDM_dof dof_
#define HPDDM_numbering numbering_
#else
#define HPDDM_n _n
#define HPDDM_m _m
#define HPDDM_sym _sym
#define HPDDM_ia _ia
#define HPDDM_ja _ja
#define HPDDM_a _a
#define HPDDM_cc _cc
#define HPDDM_nnz _nnz
#define HPDDM_dof _dof
#define HPDDM_numbering _numbering
#endif

#include "common.hpp"

template<class T>
using upscaled_type = typename std::conditional<!HPDDM::Wrapper<T>::is_complex, double, std::complex<double>>::type;

template<class K> K* newCopy(bool mfree,K *p,int n)
{  if( !mfree) return p;
	K *q= new K[n];
    copy(p,p+n,q);
    return q;
}
template<class K>  using MatriceMorse=HashMatrix<int,K>;

template<class K>
struct ff_HPDDM_MatrixCSR : public HPDDM::MatrixCSR<K>
{
    ff_HPDDM_MatrixCSR(MatriceMorse<upscaled_type<K>>* mA) :
    HPDDM::MatrixCSR<K>(mA->n, mA->m, mA->nnz, nullptr, mA->p, mA->j, mA->half > 0) {
        mA->CSR();
        HPDDM::MatrixCSR<K>::HPDDM_ia=mA->p;
        K* a = reinterpret_cast<K*>(mA->aij);
        HPDDM::MatrixCSR<K>::HPDDM_a=a;
        if(!std::is_same<upscaled_type<K>, K>::value) {
            for(int i = 0; i < mA->nnz; ++i)
                a[i] = mA->aij[i];
        }
    }
};

template<class K>
HPDDM::MatrixCSR<K> * new_HPDDM_MatrixCSR(MatriceMorse<upscaled_type<K>>* mA, bool mfree = false, K* s = nullptr, int* is = nullptr, int* js = nullptr) {
    if(mA) {
        int nnz = mA->nnz, n = mA->n;
        mA->CSR();
        K* a;
        if(!s) a = reinterpret_cast<K*>(newCopy(mfree, mA->aij, nnz));
        else a = reinterpret_cast<K*>(s);
        if(!std::is_same<upscaled_type<K>, K>::value) {
            for(int i = 0; i < nnz; ++i)
                a[i] = mA->aij[i];
        }
        if(!is) is = newCopy(mfree, mA->p, n+1);
        if(!js) js = newCopy(mfree, mA->j, nnz);

        return new HPDDM::MatrixCSR<K>(mA->n, mA->m, mA->nnz, a, is, js, mA->half > 0, false);
    }
    else
        return nullptr;
}
template<class K>
HPDDM::MatrixCSR<void> * new_HPDDM_MatrixCSRvoid(MatriceMorse<K   >* mA,bool mfree=false,int *is=nullptr,int *js=nullptr)
{ if(mA)
{
    mA->CSR();
    if(!js) js=mA->j;
    if(!is) is=mA->p;
    return new HPDDM::MatrixCSR<void>(mA->n, mA->m, mA->nnz, is, js , mA->half > 0,mfree);
}
else
    return nullptr;
}

template<class K>
void set_ff_matrix(MatriceMorse<upscaled_type<K>>* mA,const HPDDM::MatrixCSR<K> &dA)
{
    //void HashMatrix<I,R>::set(I nn,I mm,int hhalf,size_t nnnz, I *ii, I*jj, R *aa,,int f77,int tcsr)
    if(verbosity>99) cout << " set_ff_matrix " <<endl;
    // Warning this pointeur a change or not in hpddm => not del in HashMatrix
    mA->j=nullptr;
    mA->p=nullptr;
    mA->aij=nullptr;
    
    mA->set(dA.HPDDM_n,dA.HPDDM_m,dA.HPDDM_sym,dA.HPDDM_nnz,dA.HPDDM_ia,dA.HPDDM_ja,reinterpret_cast<upscaled_type<K>*>(dA.HPDDM_a),0,1);
}

template<typename T>
inline bool exist_type() {
    map<const string,basicForEachType*>::iterator ir = map_type.find(typeid(T).name());
    return ir != map_type.end();
}

template<class T>
class STL {
    T* const _it;
    const int _size;
    public:
        STL(const KN<T>& v) : _it(v), _size(v.size()) { }
        int size() const {
            return _size;
        }
        T* begin() const {
            return _it;
        }
        T* end() const {
            return _it + _size;
        }
        bool empty() const { return _size <= 0; }
        T& operator[](std::size_t idx) { return _it[idx]; }
        const T& operator[](std::size_t idx) const { return _it[idx]; }
        T& back() { return _it[_size - 1]; }
        const T& back() const { return _it[_size - 1]; }
};
template<class K>
class Pair {
    public:
        Pair() : p() { }
        std::pair<MPI_Request, const K*>* p;
        void init() { }
        void destroy() {
            delete p;
            p = nullptr;
        }
};

template<class A> inline AnyType DeleteDTOR(Stack, const AnyType& x) {
    A* a = PGetAny<A>(x);
    a->dtor();
    a = NULL;
    return Nothing;
}

extern KN<String>* pkarg;

template<class Type, class K, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value == 1>::type* = nullptr>
void exchange(Type* const& pA, K* pin, unsigned short mu, bool allocate) {
    if(allocate)
        pA->template exchange<true>(pin, mu);
    else
        pA->template exchange<false>(pin, mu);
}
template<class Type, class K, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value != 1>::type* = nullptr>
void exchange(Type* const&, K*, unsigned short, bool) { }
template<class U, class Type, class K>
void exchange_dispatched(Type* const& pA, KN<K>* pin, bool scaled) {
    if(pA) {
        unsigned short mu = pA->getDof() ? pin->n / pA->getDof() : 1;
        const auto& map = pA->getMap();
        bool allocate = map.size() > 0 && pA->getBuffer()[0] == nullptr ? pA->setBuffer() : false;
        U* x = reinterpret_cast<U*>((upscaled_type<U>*)*pin);
        if(!std::is_same<upscaled_type<U>, U>::value) {
            for(int i = 0; i < pin->n; ++i)
                x[i] = pin->operator[](i);
        }
        if(scaled)
            exchange(pA, x, mu, false);
        else
            pA->HPDDM::template Subdomain<U>::exchange(x, mu);
        pA->clearBuffer(allocate);
        if(!std::is_same<upscaled_type<U>, U>::value) {
            for(int i = pin->n - 1; i >= 0; --i)
                pin->operator[](i) = x[i];
        }
    }
}
template<class U, class Type, class K, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value != 0>::type* = nullptr>
void exchange(Type* const& pA, KN<K>* pin, bool scaled) {
    exchange_dispatched<U>(pA, pin, scaled);
}
template<class U, class Type, class K, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value == 0>::type* = nullptr>
void exchange(Type* const& pA, KN<K>* pin, bool scaled) {
    if(pA)
        exchange_dispatched<U>(pA->_A, pin, scaled);
}
template<class U, class Type, class K, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value != 0>::type* = nullptr>
void exchange_restriction(Type* const&, KN<K>*, KN<K>*, MatriceMorse<double>*) { }
namespace PETSc {
template<class Type, class K>
    void changeNumbering_func(Type*, KN<K>*, KN<K>*, bool){ ffassert(0); }
}
template<class U, class Type, class K, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value == 0>::type* = nullptr>
void exchange_restriction(Type* const& pA, KN<K>* pin, KN<K>* pout, MatriceMorse<double>* mR) {
    if(pA->_exchange && !pA->_exchange[1]) {
        ffassert((!mR && pA->_exchange[0]->getDof() == pout->n) || (mR && mR->n == pin->n && mR->m == pout->n));
        PETSc::changeNumbering_func(pA, pin, pout, false);
        PETSc::changeNumbering_func(pA, pin, pout, true);
        pout->resize(pA->_exchange[0]->getDof());
        *pout = K();

        if(mR) {
            // out += A^t in
            for(int k = 0; k < mR->nnz; ++k)
                pout->operator[](mR->j[k]) += mR->aij[k] * pin->operator[](mR->i[k]);
        }
        exchange_dispatched<U>(pA->_exchange[0], pout, false);
    }
}
template<class Type, class K>
class exchangeIn_Op : public E_F0mps {
    public:
        Expression A;
        Expression in;
        static const int n_name_param = 1;
        static basicAC_F0::name_and_type name_param[];
        Expression nargs[n_name_param];
        exchangeIn_Op<Type, K>(const basicAC_F0& args, Expression param1, Expression param2) : A(param1), in(param2) {
            args.SetNameParam(n_name_param, name_param, nargs);
        }

        AnyType operator()(Stack stack) const;
};
template<class Type, class K>
basicAC_F0::name_and_type exchangeIn_Op<Type, K>::name_param[] = {
    {"scaled", &typeid(bool)}
};
template<class Type, class K>
class exchangeIn : public OneOperator {
    public:
        exchangeIn() : OneOperator(atype<long>(), atype<Type*>(), atype<KN<upscaled_type<K>>*>()) { }

        E_F0* code(const basicAC_F0& args) const {
            return new exchangeIn_Op<Type, K>(args, t[0]->CastTo(args[0]), t[1]->CastTo(args[1]));
        }
};
template<class Type, class K>
AnyType exchangeIn_Op<Type, K>::operator()(Stack stack) const {
    Type* pA = GetAny<Type*>((*A)(stack));
    KN<upscaled_type<K>>* pin = GetAny<KN<upscaled_type<K>>*>((*in)(stack));
    const bool scaled = mpisize > 1 && nargs[0] && GetAny<bool>((*nargs[0])(stack));
    exchange<K>(pA, pin, scaled);
    return 0L;
}
template<class Type, class K>
class exchangeInOut_Op : public E_F0mps {
    public:
        Expression A;
        Expression in;
        Expression out;
        static const int n_name_param = 2;
        static basicAC_F0::name_and_type name_param[];
        Expression nargs[n_name_param];
        exchangeInOut_Op<Type, K>(const basicAC_F0& args, Expression param1, Expression param2, Expression param3) : A(param1), in(param2), out(param3) {
            args.SetNameParam(n_name_param, name_param, nargs);
        }

        AnyType operator()(Stack stack) const;
};
template<class Type, class K>
basicAC_F0::name_and_type exchangeInOut_Op<Type, K>::name_param[] = {
    {"scaled", &typeid(bool)},
    {"restriction", &typeid(Matrice_Creuse<double>*)}
};
template<class Type, class K>
class exchangeInOut : public OneOperator {
    public:
        exchangeInOut() : OneOperator(atype<long>(), atype<Type*>(), atype<KN<upscaled_type<K>>*>(), atype<KN<upscaled_type<K>>*>()) { }

        E_F0* code(const basicAC_F0& args) const {
            return new exchangeInOut_Op<Type, K>(args, t[0]->CastTo(args[0]), t[1]->CastTo(args[1]), t[2]->CastTo(args[2]));
        }
};
template<class Type, class K>
AnyType exchangeInOut_Op<Type, K>::operator()(Stack stack) const {
    Type* pA = GetAny<Type*>((*A)(stack));
    KN<upscaled_type<K>>* pin = GetAny<KN<upscaled_type<K>>*>((*in)(stack));
    KN<upscaled_type<K>>* pout = GetAny<KN<upscaled_type<K>>*>((*out)(stack));
    const bool scaled = mpisize > 1 && nargs[0] && GetAny<bool>((*nargs[0])(stack));
    Matrice_Creuse<double>* pR = nargs[1] ? GetAny<Matrice_Creuse<double>*>((*nargs[1])(stack)) : nullptr;
    MatriceMorse<double>* mR = pR ? static_cast<MatriceMorse<double>*>(&(*pR->A)) : nullptr;
    if(pR) {
        ffassert(!scaled);
        exchange_restriction<K>(pA, pin, pout, mR);
    }
    else if(pin->n == pout->n) {
        *pout = *pin;
        exchange<K>(pA, pout, scaled);
    }
    return 0L;
}

#if !HPDDM_PETSC || !defined(HPDDM_SLEPC)
double getOpt(string* const& ss) {
    return HPDDM::Option::get()->val(*ss);
}
bool isSetOpt(string* const& ss) {
    return HPDDM::Option::get()->set(*ss);
}
#endif
template<class Type, class K>
bool destroyRecycling(Type* const& Op) {
#ifndef HPDDM_SLEPC
    HPDDM::Recycling<K>::get()->destroy(Op->prefix());
#else
    Op->destroy();
#endif
    return false;
}
template<class Type>
bool statistics(Type* const& Op) {
    Op->statistics();
    return false;
}

template<class K, class Type, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value == 1>::type* = nullptr>
const K* getScaling(Type* const& pA) {
    if(pA)
        return pA->getScaling();
    else
        return nullptr;
}
template<class K, class Type, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value != 1>::type* = nullptr>
const K* getScaling(Type* const& pA) {
    if(pA)
        return getScaling<K>(pA->_A);
    else
        return nullptr;
}
template<class Type, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value == 1>::type* = nullptr>
MPI_Comm getCommunicator(Type* const& pA) {
    if(pA)
        return pA->getCommunicator();
    else
        return MPI_COMM_WORLD;
}
template<class Type, typename std::enable_if<HPDDM::hpddm_method_id<Type>::value != 1>::type* = nullptr>
MPI_Comm getCommunicator(Type* const& pA) {
    if(pA)
        return getCommunicator(pA->_A);
    else
        return MPI_COMM_WORLD;
}
template<class K, typename std::enable_if<!std::is_same<K, HPDDM::underlying_type<K>>::value>::type* = nullptr>
inline K prod(K u, HPDDM::underlying_type<K> d, K v) {
    return std::conj(u) * d * v;
}
template<class K, typename std::enable_if<std::is_same<K, HPDDM::underlying_type<K>>::value>::type* = nullptr>
inline K prod(K u, HPDDM::underlying_type<K> d, K v) {
    return u * d * v;
}
template<class Type, class K>
K scalarProduct(Type* const& Op, KN<K>* const& u, KN<K>* const& v) {
    const HPDDM::underlying_type<K>* const d = getScaling<HPDDM::underlying_type<K>>(Op);
    MPI_Comm comm = getCommunicator(Op);
    K val = 0;
    if(d) {
        for(int i = 0; i < u->n; ++i)
            val += prod(u->operator[](i), d[i], v->operator[](i));
    }
    else
        val = (*u, *v);
    MPI_Allreduce(MPI_IN_PLACE, &val, 1, HPDDM::Wrapper<K>::mpi_type(), MPI_SUM, comm);
    return val;
}
template<class Op, class K>
void addScalarProduct() {
    atype<Op*>()->Add("(", "", new OneOperator3_<K, Op*, KN<K>*, KN<K>*>(scalarProduct<Op, K>));
}

template<class T>
T* resizeClean(const Resize<T>& t, const long &m) {
    long M = t.v->N();
    if(M != m) {
      M = std::min(M, m);
      typename T::K* array = new typename T::K[M]();
      for(long j = 0; j < M; ++j) {
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          if((*t.v)[j]._petsc) {
#endif
            array[j] = (*t.v)[j];
            (*t.v)[j] = typename T::K();
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          }
#endif
      }
      t.v->resize(m);
      for(long j = 0; j < M; ++j) {
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          if(array[j]._petsc) {
#endif
            (*t.v)[j] = array[j];
            array[j] = typename T::K();
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          }
#endif
      }
      delete [] array;
    }
    return t.v;
}

template<class T>
T* resizeClean(const Resize<T>& t, const long &n, const long &m) {
    long N = t.v->N();
    long M = t.v->M();
    if (N != n || M != m) {
      N = std::min(N, n);
      M = std::min(M, m);
      typename T::K* array = new typename T::K[N * M]();
      for(long i = 0; i < N; ++i)
        for(long j = 0; j < M; ++j) {
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          if((*t.v)(i, j)._petsc) {
#endif
            array[i * M + j] = (*t.v)(i, j);
            (*t.v)(i, j) = typename T::K();
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          }
#endif
        }
      t.v->resize(n, m);
      for(long i = 0; i < N; ++i)
        for(long j = 0; j < M; ++j) {
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          if(array[i * M + j]._petsc) {
#endif
            (*t.v)(i, j) = array[i * M + j];
            array[i * M + j] = typename T::K();
#if defined(WITH_petsc) || defined(WITH_petsccomplex)
          }
#endif
        }
      delete [] array;
    }
    return t.v;
}

template<class Op>
void addArray() {
    Dcl_Type<KN<Op>*>(InitP<KN<Op>>, Destroy<KN<Op>>);
    TheOperators->Add("<-", new OneOperator2_<KN<Op>*, KN<Op>*, long>(&set_init));
    atype<KN<Op>*>()->Add("[", "", new OneOperator2_<Op*, KN<Op>*, long>(get_elementp_<Op, KN<Op>*, long>));
    Dcl_Type<Resize<KN<Op>>>();
    Add<KN<Op>*>("resize", ".", new OneOperator1<Resize<KN<Op>>, KN<Op>*>(to_Resize));
    Add<Resize<KN<Op>>>("(", "", new OneOperator2_<KN<Op>*, Resize<KN<Op>>, long>(resizeClean));
    map_type_of_map[make_pair(atype<long>(), atype<Op*>())] = atype<KN<Op>*>();
    Add<KN<Op>*>("n", ".", new OneOperator1<long, KN<Op>*>(get_n));

    Dcl_Type<KNM<Op>*>(InitP<KNM<Op>>, Destroy<KN<Op>>);
    TheOperators->Add("<-", new OneOperator3_<KNM<Op>*, KNM<Op>*, long, long>(&set_init2));
    atype<KNM<Op>*>()->Add("(", "", new OneOperator3_<Op*, KNM<Op>*, long, long>(get_elementp2_<Op, KNM<Op>*, long, long>));
    Dcl_Type<Resize<KNM<Op>>>();
    Add<KNM<Op>*>("resize", ".", new OneOperator1<Resize<KNM<Op>>, KNM<Op>*>(to_Resize));
    Add<Resize<KNM<Op>>>("(", "", new OneOperator3_<KNM<Op>*, Resize<KNM<Op>>, long, long>(resizeClean));
    map_type_of_map[make_pair(atype<pair<long, long>>(), atype<Op*>())] = atype<KNM<Op>*>();
    Add<KNM<Op>*>("n", ".", new OneOperator1<long, KNM<Op>*>(get_n));
    Add<KNM<Op>*>("m", ".", new OneOperator1<long, KNM<Op>*>(get_m));
}

void parallelIO(string*& name, MPI_Comm* const& comm, bool const& append, int& T, int& size, std::string& base_filename) {
    base_filename = *name;
    std::string::size_type p(base_filename.find_last_of('.'));
    std::string file_without_extension = base_filename.substr(0, p);
    std::string extension;
    if(p == std::string::npos)
        extension = "vtu";
    else
        extension = base_filename.substr(p + 1, std::string::npos);
    p = base_filename.find_last_of("/\\");
    if(p == std::string::npos)
        base_filename = file_without_extension;
    else
        base_filename = file_without_extension.substr(p + 1, std::string::npos);
    int rank;
    MPI_Comm_rank(comm ? *comm : MPI_COMM_WORLD, &rank);
    MPI_Comm_size(comm ? *comm : MPI_COMM_WORLD, &size);
    std::ostringstream str[3];
    str[2] << size;
    str[1] << std::setw(str[2].str().length()) << std::setfill('0') << rank;
    ofstream pvd;
    T = 0;
    if(append) {
        if(rank == 0) {
            ifstream input;
            input.open(file_without_extension + (size > 1 ? "_" + str[2].str() : "") + ".pvd");
            if(input.peek() != std::ifstream::traits_type::eof()) {
                std::string line;
                std::getline(input, line);
                std::getline(input, line);
                std::string delimiter = "\"";
                p = line.find(delimiter);
                line = line.substr(p + 1, std::string::npos);
                p = line.find(delimiter);
                T = std::stoi(line.substr(0, p)) + 1;
            }
        }
        MPI_Bcast(&T, 1, MPI_INT, 0, comm ? *comm : MPI_COMM_WORLD);
    }
    str[0] << std::setw(4) << std::setfill('0') << T;
    *name = file_without_extension + "_" + (size > 1 ? str[2].str() + "_" : "") + str[0].str() + (size > 1 ? "_" + str[1].str() : "") + "." + extension;
}

long periodicity(Matrice_Creuse<double>* const& R, KN< KN< long > >* const& interaction, KN< double >* const& D) {
    if(R->A) {
        MatriceMorse< double >* mR = static_cast< MatriceMorse< double >* >(&*(R->A));
        mR->COO();
        KN< double > restriction(mR->n);
        std::unordered_map<int, int> map;
        map.reserve(mR->nnz);
        for(int k = 0; k < mR->nnz; ++k) {
            if(std::abs(mR->aij[k]) > 1e-12) {
                restriction[mR->i[k]] = D->operator[](mR->j[k]);
                map[mR->j[k]] = mR->i[k];
            }
        }
        D->resize(mR->n);
        *D = restriction;
        if(interaction->N() > 0) {
            for(int i = 0; i < interaction->operator[](0).N(); ++i) {
                int nnz = 0;
                for(int j = 0; j < interaction->operator[](1 + i).N(); ++j) {
                    std::unordered_map<int, int>::iterator it = map.find(interaction->operator[](1 + i)[j]);
                    if(it != map.end()) {
                        interaction->operator[](1 + i)[nnz] = it->second;
                        ++nnz;
                    }
                }
                interaction->operator[](1 + i).resize(nnz);
            }
        }
    }
    return 0L;
}

#define COMMON_HPDDM_PARALLEL_IO
#if defined(__clang__)
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wdouble-promotion"
#elif defined(__GNUC__) || defined(__GNUG__)
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif
#include "../seq/iovtk.cpp"
#if defined(__clang__)
  #pragma clang diagnostic pop
#elif defined(__GNUC__) || defined(__GNUG__)
  #pragma GCC diagnostic pop
#endif

#if defined(PETSCSUB) || HPDDM_PETSC
namespace PETSc {
  void finalizePETSc( ) {
    PetscBool isFinalized;
    PetscFinalized(&isFinalized);
    if (!isFinalized) PetscFinalize( );
  }
}
#endif
#if defined(MU_SLEPC)
namespace PETSc {
  void finalizeSLEPc( ) {
    SlepcFinalize( );
  }
}
#endif

template<class K>
MatriceMorse<K>* kron_impl(Matrice_Creuse<K>* const& A, Matrice_Creuse<K>* const& B) {
    ffassert(A->A && B->A);
    MatriceMorse<K>* mA = static_cast<MatriceMorse<K>*>(&(*A->A));
    MatriceMorse<K>* mB = static_cast<MatriceMorse<K>*>(&(*B->A));
    MatriceMorse<K>& C = *new MatriceMorse<K>(A->A->n * B->A->n, A->A->m * B->A->m, mA->nnz * mB->nnz, 0);
    mA->CSR();
    mB->CSR();
    C.CSR();
    C.p[0] = 0;
    for(int i = 0; i < mA->n; ++i)
        for(int j = 0; j < mB->n; ++j)
            C.p[i * mB->n + j + 1] = C.p[i * mB->n + j] + (mA->p[i + 1] - mA->p[i]) * (mB->p[j + 1] - mB->p[j]);
    C.nnz = mA->nnz * mB->nnz;
    int k = 0;
    for(int i = 0; i < mA->n; ++i)
        for(int p = 0; p < mB->n; ++p) {
            for(int j = mA->p[i]; j < mA->p[i + 1]; ++j) {
                for(int q = mB->p[p]; q < mB->p[p + 1]; ++q) {
                    C.j[k] = mA->j[j] * mB->m + mB->j[q];
                    C.aij[k] = mA->aij[j] * mB->aij[q];
                    ++k;
                }
            }
        }
    return &C;
}
template<class K>
newpMatrice_Creuse<K> kron(Stack stack, Matrice_Creuse<K>* const& A, Matrice_Creuse<K>* const& B) {
    return newpMatrice_Creuse<K>(stack, kron_impl(A, B));
}

static void Init_Common() {
    if(!Global.Find("savevtk").NotNull()) {
        Global.Add("savevtk", "(", new OneOperatorCode<VTK_WriteMesh_Op> );
        Global.Add("savevtk", "(", new OneOperatorCode<VTK_WriteMesh3_Op> );
        Global.Add("savevtk", "(", new OneOperatorCode< VTK_WriteMeshT_Op< MeshS > >);
    }
    if(!Global.Find("periodicity").NotNull()) {
        Global.Add("periodicity", "(", new OneOperator3_<long, Matrice_Creuse<double>*, KN< KN< long > >*, KN< double >*>(periodicity));
        Global.Add("kron", "(", new OneOperator2s_<newpMatrice_Creuse<double>, Matrice_Creuse<double>*, Matrice_Creuse<double>*>(kron));
        Global.Add("kron", "(", new OneOperator2s_<newpMatrice_Creuse<std::complex<double>>, Matrice_Creuse<std::complex<double>>*, Matrice_Creuse<std::complex<double>>*>(kron));
    }
#if defined(HPDDM_SCHWARZ) && (HPDDM_SCHWARZ || HPDDM_FETI || HPDDM_BDD)
    aType t;
    int r;
    if(!zzzfff->InMotClef("pair", t, r)) {
        Global.Add("getOption", "(", new OneOperator1_<double, string*>(getOpt));
        Global.Add("isSetOption", "(", new OneOperator1_<bool, string*>(isSetOpt));
        int argc = pkarg->n;
        const char** argv = new const char*[argc];
        for(int i = 0; i < argc; ++i)
            argv[i] = (*((*pkarg)[i].getap()))->data();
        HPDDM::Option::get()->parse(argc, argv, mpirank == 0);
#if defined(MU_SLEPC)
        SlepcInitialize(&argc, const_cast<char***>(&argv), 0, "");
        ff_atend(PETSc::finalizeSLEPc);
#elif defined(PETSCSUB)
        PetscInitialize(&argc, const_cast<char***>(&argv), 0, "");
        ff_atend(PETSc::finalizePETSc);
#endif
        delete [] argv;
    }
#endif
}
#endif // _COMMON_HPDDM_
