#include <petsc/private/vecimpl.h> /*I "petscvec.h"  I*/

PetscFunctionList VecList = NULL;

/* compare a vector type against a list of target vector types */
static inline PetscErrorCode VecTypeCompareAny_Private(VecType srcType, PetscBool *match, const char tgtTypes[], ...)
{
  PetscBool flg = PETSC_FALSE;
  va_list   Argp;

  PetscFunctionBegin;
  PetscAssertPointer(match, 2);
  *match = PETSC_FALSE;
  va_start(Argp, tgtTypes);
  while (tgtTypes && tgtTypes[0]) {
    PetscCall(PetscStrcmp(srcType, tgtTypes, &flg));
    if (flg) {
      *match = PETSC_TRUE;
      break;
    }
    tgtTypes = va_arg(Argp, const char *);
  }
  va_end(Argp);
  PetscFunctionReturn(PETSC_SUCCESS);
}

#define PETSC_MAX_VECTYPE_LEN 64

/*@
  VecSetType - Builds a vector, for a particular vector implementation.

  Collective

  Input Parameters:
+ vec     - The vector object
- newType - The name of the vector type

  Options Database Key:
. -vec_type type - Sets the vector type; see `VecType`

  Level: intermediate

  Notes:
  See `VecType` for available vector types (for instance, `VECSEQ` or `VECMPI`)
  Changing a vector to a new type will retain its old value if any.

  Use `VecDuplicate()` or `VecDuplicateVecs()` to form additional vectors of the same type as an existing vector.

.seealso: [](ch_vectors), `Vec`, `VecType`, `VecGetType()`, `VecCreate()`, `VecDuplicate()`, `VecDuplicateVecs()`
@*/
PetscErrorCode VecSetType(Vec vec, VecType newType)
{
  PetscErrorCode (*r)(Vec);
  VecType      curType;
  PetscBool    match;
  PetscMPIInt  size;
  PetscBool    dstSeq = PETSC_FALSE; // type info of the new type
  MPI_Comm     comm;
  char         seqType[PETSC_MAX_VECTYPE_LEN] = {0};
  char         mpiType[PETSC_MAX_VECTYPE_LEN] = {0};
  PetscScalar *oldValue;
  PetscBool    srcStandard, dstStandard;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 1);

  PetscCall(VecGetType(vec, &curType));
  if (!curType) goto newvec; // vec's type is not set yet

  /* return if exactly the same type */
  PetscCall(PetscObjectTypeCompare((PetscObject)vec, newType, &match));
  if (match) PetscFunctionReturn(PETSC_SUCCESS);

  /* error on illegal mpi to seq conversion */
  PetscCall(PetscObjectGetComm((PetscObject)vec, &comm));
  PetscCallMPI(MPI_Comm_size(comm, &size));

  PetscCall(PetscStrbeginswith(newType, VECSEQ, &dstSeq));
  PetscCheck(!(size > 1 && dstSeq), comm, PETSC_ERR_ARG_WRONG, "Cannot convert MPI vectors to sequential ones");

  /* return if standard => standard */
  if (size == 1) PetscCall(PetscObjectTypeCompare((PetscObject)vec, VECSEQ, &srcStandard));
  else PetscCall(PetscObjectTypeCompare((PetscObject)vec, VECMPI, &srcStandard));
  PetscCall(VecTypeCompareAny_Private(newType, &dstStandard, VECSTANDARD, VECSEQ, VECMPI, ""));
  if (srcStandard && dstStandard) PetscFunctionReturn(PETSC_SUCCESS);

  /* return if curType = "seq" | "mpi" + newType */
  PetscCall(PetscStrncpy(mpiType, "mpi", 4));
  PetscCall(PetscStrlcat(mpiType, newType, PETSC_MAX_VECTYPE_LEN));
  PetscCall(PetscStrncpy(seqType, "seq", 4));
  PetscCall(PetscStrlcat(seqType, newType, PETSC_MAX_VECTYPE_LEN));
  PetscCall(PetscObjectTypeCompareAny((PetscObject)vec, &match, seqType, mpiType, ""));
  if (match) PetscFunctionReturn(PETSC_SUCCESS);

  /* downcast VECSTANDARD to VECCUDA/HIP/KOKKOS in place. We don't do in-place upcasting
  for those vectors. At least, it is not always possible to upcast a VECCUDA to VECSTANDARD
  in place, since the host array might be pinned (i.e., allocated by cudaMallocHost()). If
  we upcast it to VECSTANDARD, we could not free the pinned array with PetscFree(), which
  is assumed for VECSTANDARD. Thus we just create a new vector, though it is expensive.
  Upcasting is rare and users are not recommended to use it.
  */
#if defined(PETSC_HAVE_CUDA)
  {
    PetscBool dstCUDA = PETSC_FALSE;
    if (!dstStandard) PetscCall(VecTypeCompareAny_Private(newType, &dstCUDA, VECCUDA, VECSEQCUDA, VECMPICUDA, ""));
    if (srcStandard && dstCUDA) {
      if (size == 1) PetscCall(VecConvert_Seq_SeqCUDA_inplace(vec));
      else PetscCall(VecConvert_MPI_MPICUDA_inplace(vec));
      PetscFunctionReturn(PETSC_SUCCESS);
    }
  }
#endif
#if defined(PETSC_HAVE_HIP)
  {
    PetscBool dstHIP = PETSC_FALSE;
    if (!dstStandard) PetscCall(VecTypeCompareAny_Private(newType, &dstHIP, VECHIP, VECSEQHIP, VECMPIHIP, ""));
    if (srcStandard && dstHIP) {
      if (size == 1) PetscCall(VecConvert_Seq_SeqHIP_inplace(vec));
      else PetscCall(VecConvert_MPI_MPIHIP_inplace(vec));
      PetscFunctionReturn(PETSC_SUCCESS);
    }
  }
#endif
#if defined(PETSC_HAVE_KOKKOS_KERNELS)
  {
    PetscBool dstKokkos = PETSC_FALSE;
    if (!dstStandard) PetscCall(VecTypeCompareAny_Private(newType, &dstKokkos, VECKOKKOS, VECSEQKOKKOS, VECMPIKOKKOS, ""));
    if (srcStandard && dstKokkos) {
      if (size == 1) PetscCall(VecConvert_Seq_SeqKokkos_inplace(vec, NULL));
      else PetscCall(VecConvert_MPI_MPIKokkos_inplace(vec));
      PetscFunctionReturn(PETSC_SUCCESS);
    }
  }
#endif

  /* Other conversion scenarios: create a new vector but retain old value */
newvec:
  PetscCall(PetscFunctionListFind(VecList, newType, &r));
  PetscCheck(r, PetscObjectComm((PetscObject)vec), PETSC_ERR_ARG_UNKNOWN_TYPE, "Unknown vector type: %s", newType);
  if (curType) { /* no need to destroy a vec without type */
    const PetscScalar *array;
    PetscCall(VecGetArrayRead(vec, &array));
    if (array) {                                       /* record the old value if any before destroy */
      PetscCall(PetscMalloc1(vec->map->n, &oldValue)); /* no need to free since we'll drop it into vec */
      PetscCall(PetscArraycpy(oldValue, array, vec->map->n));
    } else {
      oldValue = NULL;
    }
    PetscCall(VecRestoreArrayRead(vec, &array));
    PetscTryTypeMethod(vec, destroy);
    PetscCall(PetscMemzero(vec->ops, sizeof(struct _VecOps)));
    PetscCall(PetscFree(vec->defaultrandtype));
    PetscCall(PetscFree(((PetscObject)vec)->type_name)); /* free type_name to make vec clean to use, as we might call VecSetType() again */
  }

  if (vec->map->n < 0 && vec->map->N < 0) {
    vec->ops->create = r;
    vec->ops->load   = VecLoad_Default;
  } else {
    PetscCall((*r)(vec));
  }

  /* drop in the old value */
  if (curType && vec->map->n) PetscCall(VecReplaceArray(vec, oldValue));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  VecGetType - Gets the vector type name (as a string) from a `Vec`.

  Not Collective

  Input Parameter:
. vec - The vector

  Output Parameter:
. type - The `VecType` of the vector

  Level: intermediate

  Note:
  `type` should not be retained for later use as it will be an invalid pointer if the `VecType` of `vec` is changed.

.seealso: [](ch_vectors), `Vec`, `VecType`, `VecCreate()`, `VecDuplicate()`, `VecDuplicateVecs()`, `PetscObjectTypeCompare()`, `PetscObjectTypeCompareAny()`
@*/
PetscErrorCode VecGetType(Vec vec, VecType *type)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 1);
  PetscAssertPointer(type, 2);
  PetscCall(VecRegisterAll());
  *type = ((PetscObject)vec)->type_name;
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode VecGetRootType_Private(Vec vec, VecType *vtype)
{
  PetscBool iscuda, iship, iskokkos, isvcl;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 1);
  PetscAssertPointer(vtype, 2);
  PetscCall(PetscObjectTypeCompareAny((PetscObject)vec, &iscuda, VECCUDA, VECMPICUDA, VECSEQCUDA, ""));
  PetscCall(PetscObjectTypeCompareAny((PetscObject)vec, &iship, VECHIP, VECMPIHIP, VECSEQHIP, ""));
  PetscCall(PetscObjectTypeCompareAny((PetscObject)vec, &iskokkos, VECKOKKOS, VECMPIKOKKOS, VECSEQKOKKOS, ""));
  PetscCall(PetscObjectTypeCompareAny((PetscObject)vec, &isvcl, VECVIENNACL, VECMPIVIENNACL, VECSEQVIENNACL, ""));
  if (iscuda) {
    *vtype = VECCUDA;
  } else if (iship) {
    *vtype = VECHIP;
  } else if (iskokkos) {
    *vtype = VECKOKKOS;
  } else if (isvcl) {
    *vtype = VECVIENNACL;
  } else {
    *vtype = VECSTANDARD;
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  VecRegister -  Adds a new vector component implementation

  Not Collective, No Fortran Support

  Input Parameters:
+ sname    - The name of a new user-defined creation routine
- function - The creation routine

  Notes:
  `VecRegister()` may be called multiple times to add several user-defined vectors

  Example Usage:
.vb
    VecRegister("my_vec",MyVectorCreate);
.ve

  Then, your vector type can be chosen with the procedural interface via
.vb
    VecCreate(MPI_Comm, Vec *);
    VecSetType(Vec,"my_vector_name");
.ve
  or at runtime via the option
.vb
    -vec_type my_vector_name
.ve

  Level: advanced

.seealso: `VecRegisterAll()`, `VecRegisterDestroy()`
@*/
PetscErrorCode VecRegister(const char sname[], PetscErrorCode (*function)(Vec))
{
  PetscFunctionBegin;
  PetscCall(VecInitializePackage());
  PetscCall(PetscFunctionListAdd(&VecList, sname, function));
  PetscFunctionReturn(PETSC_SUCCESS);
}
