#include <petsc/private/matimpl.h> /*I "petscmat.h" I*/

/* Logging support */
PetscClassId MAT_COARSEN_CLASSID;

PetscFunctionList MatCoarsenList              = NULL;
PetscBool         MatCoarsenRegisterAllCalled = PETSC_FALSE;

/*@C
  MatCoarsenRegister - Adds a new sparse matrix coarsening algorithm to the matrix package.

  Logically Collective, No Fortran Support

  Input Parameters:
+ sname    - name of coarsen (for example `MATCOARSENMIS`)
- function - function pointer that creates the coarsen type

  Level: developer

  Example Usage:
.vb
   MatCoarsenRegister("my_agg", MyAggCreate);
.ve

  Then, your aggregator can be chosen with the procedural interface via `MatCoarsenSetType(agg, "my_agg")` or at runtime via the option `-mat_coarsen_type my_agg`

.seealso: `MatCoarsen`, `MatCoarsenType`, `MatCoarsenSetType()`, `MatCoarsenCreate()`, `MatCoarsenRegisterDestroy()`, `MatCoarsenRegisterAll()`
@*/
PetscErrorCode MatCoarsenRegister(const char sname[], PetscErrorCode (*function)(MatCoarsen))
{
  PetscFunctionBegin;
  PetscCall(MatInitializePackage());
  PetscCall(PetscFunctionListAdd(&MatCoarsenList, sname, function));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenGetType - Gets the Coarsen method type and name (as a string)
  from the coarsen context.

  Not Collective

  Input Parameter:
. coarsen - the coarsen context

  Output Parameter:
. type - coarsener type

  Level: advanced

.seealso: `MatCoarsen`, `MatCoarsenCreate()`, `MatCoarsenType`, `MatCoarsenSetType()`, `MatCoarsenRegister()`
@*/
PetscErrorCode MatCoarsenGetType(MatCoarsen coarsen, MatCoarsenType *type)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarsen, MAT_COARSEN_CLASSID, 1);
  PetscAssertPointer(type, 2);
  *type = ((PetscObject)coarsen)->type_name;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenApply - Gets a coarsen for a matrix.

  Collective

  Input Parameter:
. coarser - the coarsen

  Options Database Keys:
+ -mat_coarsen_type mis|hem|misk - mis: maximal independent set based; misk: distance k MIS; hem: heavy edge matching
- -mat_coarsen_view              - view the coarsening object

  Level: advanced

  Notes:
  When the coarsening is used inside `PCGAMG` then the options database keys are prefixed with `-pc_gamg_`

  Use `MatCoarsenGetData()` to access the results of the coarsening

  The user can define additional coarsens; see `MatCoarsenRegister()`.

.seealso: `MatCoarsen`, `MatCoarsenSetFromOptions()`, `MatCoarsenSetType()`, `MatCoarsenRegister()`, `MatCoarsenCreate()`,
          `MatCoarsenDestroy()`, `MatCoarsenSetAdjacency()`,
          `MatCoarsenGetData()`
@*/
PetscErrorCode MatCoarsenApply(MatCoarsen coarser)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarser, MAT_COARSEN_CLASSID, 1);
  PetscAssertPointer(coarser, 1);
  PetscCheck(coarser->graph->assembled, PetscObjectComm((PetscObject)coarser), PETSC_ERR_ARG_WRONGSTATE, "Not for unassembled matrix");
  PetscCheck(!coarser->graph->factortype, PetscObjectComm((PetscObject)coarser), PETSC_ERR_ARG_WRONGSTATE, "Not for factored matrix");
  PetscCall(PetscLogEventBegin(MAT_Coarsen, coarser, 0, 0, 0));
  PetscUseTypeMethod(coarser, apply);
  PetscCall(PetscLogEventEnd(MAT_Coarsen, coarser, 0, 0, 0));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetAdjacency - Sets the adjacency graph (matrix) of the thing to be coarsened.

  Collective

  Input Parameters:
+ agg - the coarsen context
- adj - the adjacency matrix

  Level: advanced

.seealso: `MatCoarsen`, `MatCoarsenSetFromOptions()`, `Mat`, `MatCoarsenCreate()`, `MatCoarsenApply()`
@*/
PetscErrorCode MatCoarsenSetAdjacency(MatCoarsen agg, Mat adj)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(agg, MAT_COARSEN_CLASSID, 1);
  PetscValidHeaderSpecific(adj, MAT_CLASSID, 2);
  agg->graph = adj;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetStrictAggs - Set whether to keep strict (non overlapping) aggregates in the linked list of aggregates for a coarsen context

  Logically Collective

  Input Parameters:
+ agg - the coarsen context
- str - `PETSC_TRUE` keep strict aggregates, `PETSC_FALSE` allow overlap

  Level: advanced

.seealso: `MatCoarsen`, `MatCoarsenCreate()`, `MatCoarsenSetFromOptions()`
@*/
PetscErrorCode MatCoarsenSetStrictAggs(MatCoarsen agg, PetscBool str)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(agg, MAT_COARSEN_CLASSID, 1);
  agg->strict_aggs = str;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenDestroy - Destroys the coarsen context.

  Collective

  Input Parameter:
. agg - the coarsen context

  Level: advanced

.seealso: `MatCoarsen`, `MatCoarsenCreate()`
@*/
PetscErrorCode MatCoarsenDestroy(MatCoarsen *agg)
{
  PetscFunctionBegin;
  if (!*agg) PetscFunctionReturn(PETSC_SUCCESS);
  PetscValidHeaderSpecific(*agg, MAT_COARSEN_CLASSID, 1);
  if (--((PetscObject)*agg)->refct > 0) {
    *agg = NULL;
    PetscFunctionReturn(PETSC_SUCCESS);
  }

  PetscTryTypeMethod(*agg, destroy);
  if ((*agg)->agg_lists) PetscCall(PetscCDDestroy((*agg)->agg_lists));
  PetscCall(PetscObjectComposeFunction((PetscObject)*agg, "MatCoarsenSetMaximumIterations_C", NULL));
  PetscCall(PetscObjectComposeFunction((PetscObject)*agg, "MatCoarsenSetThreshold_C", NULL));
  PetscCall(PetscObjectComposeFunction((PetscObject)*agg, "MatCoarsenSetStrengthIndex_C", NULL));

  PetscCall(PetscHeaderDestroy(agg));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenViewFromOptions - View the coarsener from the options database

  Collective

  Input Parameters:
+ A    - the coarsen context
. obj  - Optional object that provides the prefix for the option name
- name - command line option (usually `-mat_coarsen_view`)

  Options Database Key:
. -name [viewertype][:...] - option name and values. See `PetscObjectViewFromOptions()` for the possible arguments

  Level: intermediate

.seealso: `MatCoarsen`, `MatCoarsenView()`, `PetscObjectViewFromOptions()`, `MatCoarsenCreate()`
@*/
PetscErrorCode MatCoarsenViewFromOptions(MatCoarsen A, PetscObject obj, const char name[])
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(A, MAT_COARSEN_CLASSID, 1);
  PetscCall(PetscObjectViewFromOptions((PetscObject)A, obj, name));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenView - Prints the coarsen data structure.

  Collective

  Input Parameters:
+ agg    - the coarsen context
- viewer - optional visualization context

   For viewing the options database see `MatCoarsenViewFromOptions()`

  Level: advanced

.seealso: `MatCoarsen`, `PetscViewer`, `PetscViewerASCIIOpen()`, `MatCoarsenViewFromOptions`
@*/
PetscErrorCode MatCoarsenView(MatCoarsen agg, PetscViewer viewer)
{
  PetscBool isascii;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(agg, MAT_COARSEN_CLASSID, 1);
  if (!viewer) PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)agg), &viewer));
  PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
  PetscCheckSameComm(agg, 1, viewer, 2);

  PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &isascii));
  PetscCall(PetscObjectPrintClassNamePrefixType((PetscObject)agg, viewer));
  if (agg->ops->view) {
    PetscCall(PetscViewerASCIIPushTab(viewer));
    PetscUseTypeMethod(agg, view, viewer);
    PetscCall(PetscViewerASCIIPopTab(viewer));
  }
  if (agg->strength_index_size > 0) PetscCall(PetscViewerASCIIPrintf(viewer, " Using scalar strength-of-connection index[%" PetscInt_FMT "] = {%" PetscInt_FMT ", ..}\n", agg->strength_index_size, agg->strength_index[0]));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetType - Sets the type of aggregator to use

  Collective

  Input Parameters:
+ coarser - the coarsen context.
- type    - a known coarsening method

  Options Database Key:
. -mat_coarsen_type  type - maximal independent set based; distance k MIS; heavy edge matching

  Level: advanced

.seealso: `MatCoarsen`, `MatCoarsenCreate()`, `MatCoarsenApply()`, `MatCoarsenType`, `MatCoarsenGetType()`
@*/
PetscErrorCode MatCoarsenSetType(MatCoarsen coarser, MatCoarsenType type)
{
  PetscBool match;
  PetscErrorCode (*r)(MatCoarsen);

  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarser, MAT_COARSEN_CLASSID, 1);
  PetscAssertPointer(type, 2);

  PetscCall(PetscObjectTypeCompare((PetscObject)coarser, type, &match));
  if (match) PetscFunctionReturn(PETSC_SUCCESS);

  PetscTryTypeMethod(coarser, destroy);
  coarser->ops->destroy = NULL;
  PetscCall(PetscMemzero(coarser->ops, sizeof(struct _MatCoarsenOps)));

  PetscCall(PetscFunctionListFind(MatCoarsenList, type, &r));
  PetscCheck(r, PetscObjectComm((PetscObject)coarser), PETSC_ERR_ARG_UNKNOWN_TYPE, "Unknown coarsen type %s", type);
  PetscCall((*r)(coarser));

  PetscCall(PetscFree(((PetscObject)coarser)->type_name));
  PetscCall(PetscStrallocpy(type, &((PetscObject)coarser)->type_name));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetGreedyOrdering - Sets the ordering of the vertices to use with a greedy coarsening method

  Logically Collective

  Input Parameters:
+ coarser - the coarsen context
- perm    - vertex ordering of (greedy) algorithm

  Level: advanced

  Note:
  The `IS` weights is freed by PETSc, the user should not destroy it or change it after this call

.seealso: `MatCoarsen`, `MatCoarsenType`, `MatCoarsenCreate()`, `MatCoarsenSetType()`
@*/
PetscErrorCode MatCoarsenSetGreedyOrdering(MatCoarsen coarser, const IS perm)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarser, MAT_COARSEN_CLASSID, 1);
  coarser->perm = perm;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  MatCoarsenGetData - Gets the weights for vertices for a coarsener.

  Logically Collective, No Fortran Support

  Input Parameter:
. coarser - the coarsen context

  Output Parameter:
. llist - linked list of aggregates

  Level: advanced

  Note:
  This passes ownership to the caller and nullifies the value of weights (`PetscCoarsenData`) within the `MatCoarsen`

.seealso: `MatCoarsen`, `MatCoarsenApply()`, `MatCoarsenCreate()`, `MatCoarsenSetType()`, `PetscCoarsenData`
@*/
PetscErrorCode MatCoarsenGetData(MatCoarsen coarser, PetscCoarsenData **llist)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarser, MAT_COARSEN_CLASSID, 1);
  PetscCheck(coarser->agg_lists, PetscObjectComm((PetscObject)coarser), PETSC_ERR_ARG_WRONGSTATE, "No linked list - generate it or call ApplyCoarsen");
  *llist             = coarser->agg_lists;
  coarser->agg_lists = NULL; /* giving up ownership */
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetFromOptions - Sets various coarsen options from the options database.

  Collective

  Input Parameter:
. coarser - the coarsen context.

  Options Database Key:
+ -mat_coarsen_type  type - see `MatCoarsenType`
- -mat_coarsen_max_it its - number of iterations to use in the coarsening process, see `MatCoarsenSetMaximumIterations()`

  Level: advanced

  Notes:
  When the coarsening is used inside `PCGAMG` then the options database keys are prefixed with `-pc_gamg_`

  Sets the `MatCoarsenType` to `MATCOARSENMISK` if has not been set previously

.seealso: `MatCoarsen`, `MatCoarsenType`, `MatCoarsenApply()`, `MatCoarsenCreate()`, `MatCoarsenSetType()`,
          `MatCoarsenSetMaximumIterations()`
@*/
PetscErrorCode MatCoarsenSetFromOptions(MatCoarsen coarser)
{
  PetscBool   flag;
  char        type[256];
  const char *def;

  PetscFunctionBegin;
  PetscObjectOptionsBegin((PetscObject)coarser);
  if (!((PetscObject)coarser)->type_name) {
    def = MATCOARSENMISK;
  } else {
    def = ((PetscObject)coarser)->type_name;
  }
  PetscCall(PetscOptionsFList("-mat_coarsen_type", "Type of aggregator", "MatCoarsenSetType", MatCoarsenList, def, type, 256, &flag));
  if (flag) PetscCall(MatCoarsenSetType(coarser, type));

  PetscCall(PetscOptionsInt("-mat_coarsen_max_it", "Number of iterations (for HEM)", "MatCoarsenSetMaximumIterations", coarser->max_it, &coarser->max_it, NULL));
  PetscCall(PetscOptionsInt("-mat_coarsen_threshold", "Threshold (for HEM)", "MatCoarsenSetThreshold", coarser->max_it, &coarser->max_it, NULL));
  coarser->strength_index_size = MAT_COARSEN_STRENGTH_INDEX_SIZE;
  PetscCall(PetscOptionsIntArray("-mat_coarsen_strength_index", "Array of indices to use strength of connection measure (default is all indices)", "MatCoarsenSetStrengthIndex", coarser->strength_index, &coarser->strength_index_size, NULL));
  /*
   Set the type if it was never set.
   */
  if (!((PetscObject)coarser)->type_name) PetscCall(MatCoarsenSetType(coarser, def));

  PetscTryTypeMethod(coarser, setfromoptions, PetscOptionsObject);
  PetscOptionsEnd();
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetMaximumIterations - Maximum `MATCOARSENHEM` iterations to use

  Logically Collective

  Input Parameters:
+ coarse - the coarsen context
- n      - number of HEM iterations

  Options Database Key:
. -mat_coarsen_max_it n - Maximum `MATCOARSENHEM` iterations to use

  Level: intermediate

  Note:
  When the coarsening is used inside `PCGAMG` then the options database keys are prefixed with `-pc_gamg_`

.seealso: `MatCoarsen`, `MatCoarsenType`, `MatCoarsenApply()`, `MatCoarsenCreate()`, `MatCoarsenSetType()`
@*/
PetscErrorCode MatCoarsenSetMaximumIterations(MatCoarsen coarse, PetscInt n)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarse, MAT_COARSEN_CLASSID, 1);
  PetscValidLogicalCollectiveInt(coarse, n, 2);
  PetscTryMethod(coarse, "MatCoarsenSetMaximumIterations_C", (MatCoarsen, PetscInt), (coarse, n));
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode MatCoarsenSetMaximumIterations_MATCOARSEN(MatCoarsen coarse, PetscInt b)
{
  PetscFunctionBegin;
  coarse->max_it = b;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetStrengthIndex -  Index array to use for index to use for strength of connection

  Logically Collective

  Input Parameters:
+ coarse - the coarsen context
. n      - number of indices
- idx    - array of indices

  Options Database Key:
. -mat_coarsen_strength_index - array of subset of variables per vertex to use for strength norm, -1 for using all (default)

  Level: intermediate

  Note:
  When the coarsening is used inside `PCGAMG` then the options database keys are prefixed with `-pc_gamg_`

.seealso: `MatCoarsen`, `MatCoarsenType`, `MatCoarsenApply()`, `MatCoarsenCreate()`, `MatCoarsenSetType()`
@*/
PetscErrorCode MatCoarsenSetStrengthIndex(MatCoarsen coarse, PetscInt n, PetscInt idx[])
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarse, MAT_COARSEN_CLASSID, 1);
  PetscValidLogicalCollectiveInt(coarse, n, 2);
  PetscTryMethod(coarse, "MatCoarsenSetStrengthIndex_C", (MatCoarsen, PetscInt, PetscInt[]), (coarse, n, idx));
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode MatCoarsenSetStrengthIndex_MATCOARSEN(MatCoarsen coarse, PetscInt n, PetscInt idx[])
{
  PetscFunctionBegin;
  coarse->strength_index_size = n;
  for (int iii = 0; iii < n; iii++) coarse->strength_index[iii] = idx[iii];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenSetThreshold - Set the threshold for HEM

  Logically Collective

  Input Parameters:
+ coarse - the coarsen context
- b      - threshold value, default is -1

  Options Database Key:
. -mat_coarsen_threshold b - threshold

  Level: intermediate

  Note:
  When the coarsening is used inside `PCGAMG` then the options database keys are prefixed with `-pc_gamg_`

  Developer Note:
  It is not documented how this threshold is used

.seealso: `MatCoarsen`, `MatCoarsenType`, `MatCoarsenApply()`, `MatCoarsenCreate()`, `MatCoarsenSetType()`
@*/
PetscErrorCode MatCoarsenSetThreshold(MatCoarsen coarse, PetscReal b)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecific(coarse, MAT_COARSEN_CLASSID, 1);
  PetscValidLogicalCollectiveReal(coarse, b, 2);
  PetscTryMethod(coarse, "MatCoarsenSetThreshold_C", (MatCoarsen, PetscReal), (coarse, b));
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode MatCoarsenSetThreshold_MATCOARSEN(MatCoarsen coarse, PetscReal b)
{
  PetscFunctionBegin;
  coarse->threshold = b;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  MatCoarsenCreate - Creates a coarsen context.

  Collective

  Input Parameter:
. comm - MPI communicator

  Output Parameter:
. newcrs - location to put the context

  Level: advanced

.seealso: `MatCoarsen`, `MatCoarsenSetType()`, `MatCoarsenApply()`, `MatCoarsenDestroy()`,
          `MatCoarsenSetAdjacency()`, `MatCoarsenGetData()`
@*/
PetscErrorCode MatCoarsenCreate(MPI_Comm comm, MatCoarsen *newcrs)
{
  MatCoarsen agg;

  PetscFunctionBegin;
  PetscAssertPointer(newcrs, 2);
  PetscCall(MatInitializePackage());

  PetscCall(PetscHeaderCreate(agg, MAT_COARSEN_CLASSID, "MatCoarsen", "Matrix/graph coarsen", "MatCoarsen", comm, MatCoarsenDestroy, MatCoarsenView));
  PetscCall(PetscObjectComposeFunction((PetscObject)agg, "MatCoarsenSetMaximumIterations_C", MatCoarsenSetMaximumIterations_MATCOARSEN));
  PetscCall(PetscObjectComposeFunction((PetscObject)agg, "MatCoarsenSetThreshold_C", MatCoarsenSetThreshold_MATCOARSEN));
  PetscCall(PetscObjectComposeFunction((PetscObject)agg, "MatCoarsenSetStrengthIndex_C", MatCoarsenSetStrengthIndex_MATCOARSEN));
  agg->strength_index_size = 0;
  *newcrs                  = agg;
  PetscFunctionReturn(PETSC_SUCCESS);
}
