#include <petsc/private/linesearchimpl.h>
#include <petsc/private/snesimpl.h>

typedef struct {
  SNESLineSearchUserFunc func;
  void                  *ctx;
} SNESLineSearch_Shell;

// PetscClangLinter pragma disable: -fdoc-param-list-func-parameter-documentation
/*@C
  SNESLineSearchShellSetUserFunc - Sets the user function for the `SNESLINESEARCHSHELL` implementation.

  Not Collective

  Input Parameters:
+ linesearch - `SNESLineSearch` context
. func       - function implementing the linesearch shell.
- ctx        - context for func

  Calling sequence of `func`:
$  PetscErrorCode func(SNESLinesearch ls, void *ctx)
+ ls  - the linesearch instance
- ctx - the above mentioned context

  Usage\:
.vb
  PetscErrorCode shellfunc(SNESLineSearch linesearch,void * ctx)
  {
     Vec  X,Y,F,W,G;
     SNES snes;
     PetscFunctionBegin;
     PetscCall(SNESLineSearchGetSNES(linesearch,&snes));
     PetscCall(SNESLineSearchSetReason(linesearch,SNES_LINESEARCH_SUCCEEDED));
     PetscCall(SNESLineSearchGetVecs(linesearch,&X,&F,&Y,&W,&G));
     // determine lambda using W and G as work vecs..
     PetscCall(VecAXPY(X,-lambda,Y));
     PetscCall(SNESComputeFunction(snes,X,F));
     PetscCall(SNESLineSearchComputeNorms(linesearch));
     PetscFunctionReturn(PETSC_SUCCESS);
  }

  PetscCall(SNESGetLineSearch(snes, &linesearch));
  PetscCall(SNESLineSearchSetType(linesearch, SNESLINESEARCHSHELL));
  PetscCall(SNESLineSearchShellSetUserFunc(linesearch, shellfunc, NULL));
.ve

  Level: advanced

.seealso: [](ch_snes), `SNESLineSearchShellGetUserFunc()`, `SNESLINESEARCHSHELL`, `SNESLineSearchType`, `SNESLineSearch`
@*/
PetscErrorCode SNESLineSearchShellSetUserFunc(SNESLineSearch linesearch, SNESLineSearchUserFunc func, void *ctx)
{
  PetscBool             flg;
  SNESLineSearch_Shell *shell = (SNESLineSearch_Shell *)linesearch->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(linesearch, SNESLINESEARCH_CLASSID, 1);
  PetscCall(PetscObjectTypeCompare((PetscObject)linesearch, SNESLINESEARCHSHELL, &flg));
  if (flg) {
    shell->ctx  = ctx;
    shell->func = func;
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  SNESLineSearchShellGetUserFunc - Gets the user function and context for the  `SNESLINESEARCHSHELL`

  Not Collective

  Input Parameter:
. linesearch - the line search object

  Output Parameters:
+ func - the user function; can be `NULL` if it is not needed
- ctx  - the user function context; can be `NULL` if it is not needed

  Level: advanced

.seealso: [](ch_snes), `SNESLineSearchShellSetUserFunc()`, `SNESLINESEARCHSHELL`, `SNESLineSearchType`, `SNESLineSearch`
@*/
PetscErrorCode SNESLineSearchShellGetUserFunc(SNESLineSearch linesearch, SNESLineSearchUserFunc *func, void **ctx)
{
  PetscBool             flg;
  SNESLineSearch_Shell *shell = (SNESLineSearch_Shell *)linesearch->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(linesearch, SNESLINESEARCH_CLASSID, 1);
  if (func) PetscAssertPointer(func, 2);
  if (ctx) PetscAssertPointer(ctx, 3);
  PetscCall(PetscObjectTypeCompare((PetscObject)linesearch, SNESLINESEARCHSHELL, &flg));
  if (flg) {
    if (func) *func = shell->func;
    if (ctx) *ctx = shell->ctx;
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode SNESLineSearchApply_Shell(SNESLineSearch linesearch)
{
  SNESLineSearch_Shell *shell = (SNESLineSearch_Shell *)linesearch->data;

  PetscFunctionBegin;
  /* apply the user function */
  if (shell->func) {
    PetscCall((*shell->func)(linesearch, shell->ctx));
  } else SETERRQ(PetscObjectComm((PetscObject)linesearch), PETSC_ERR_USER, "SNESLineSearchShell needs to have a shell function set with SNESLineSearchShellSetUserFunc");
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode SNESLineSearchDestroy_Shell(SNESLineSearch linesearch)
{
  SNESLineSearch_Shell *shell = (SNESLineSearch_Shell *)linesearch->data;

  PetscFunctionBegin;
  PetscCall(PetscFree(shell));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*MC
   SNESLINESEARCHSHELL - Provides context for a user-provided line search routine.

  The user routine has one argument, the `SNESLineSearch` context.  The user uses the interface to
  extract line search parameters and set them accordingly when the computation is finished.

  Any of the other line searches may serve as a guide to how this is to be done.  There is also a basic
  template in the documentation for `SNESLineSearchShellSetUserFunc()`.

  Level: advanced

.seealso: [](ch_snes), `SNESLineSearch`, `SNES`, `SNESLineSearchCreate()`, `SNESLineSearchSetType()`
M*/

PETSC_EXTERN PetscErrorCode SNESLineSearchCreate_Shell(SNESLineSearch linesearch)
{
  SNESLineSearch_Shell *shell;

  PetscFunctionBegin;
  linesearch->ops->apply          = SNESLineSearchApply_Shell;
  linesearch->ops->destroy        = SNESLineSearchDestroy_Shell;
  linesearch->ops->setfromoptions = NULL;
  linesearch->ops->reset          = NULL;
  linesearch->ops->view           = NULL;
  linesearch->ops->setup          = NULL;

  PetscCall(PetscNew(&shell));

  linesearch->data = (void *)shell;
  PetscFunctionReturn(PETSC_SUCCESS);
}
