/*
 * Copyright (C) 2008-2012 Delft University of Technology - All rights reserved.
 *
 * Author: Reijer Idema
 */

/**
 * @file NonLinearSolver.h
 * NonLinearSolver class header.
 */
#ifndef NonLinearSolver_NonLinearSolver_h
#define NonLinearSolver_NonLinearSolver_h

#include "../PetscUtils/PetscUtils.h"	// USES PetscUtils
#include "NonLinearSolver.def"

NONLINEARSOLVER_NAMESPACE_BEGIN

// FORWARD DECLARATIONS
class NonLinearProblem;		// HASA NonLinearProblem (not owned)
class PreconditionerShell;	// HASA PreconditionerShell (owned)
class Preconditioner;		// HASA Preconditioner (owned)
class ForcingTerms;			// HASA ForcingTerms (owned)

/**
 * %NonLinearSolver class.
 * Implements a Newton-Raphson solver using PETSc.
 */
class NonLinearSolver
{
public:
	// LIFECYCLE

	/**
	 * Default constructor.
	 */
	NonLinearSolver(void);

	/**
	 * Destructor.
	 */
	~NonLinearSolver(void);

	// OPERATIONS

	/**
	 * Clears the object data.
	 */
	void Clear(void);

	/**
	 * Initializes the object data.
	 * The solver environment is initialized based on defaults and command line options.
	 * @return \c true on success, \c false on failure.
	 */
	bool Initialize(void);

	/**
	 * Set the preconditioner.
	 * @param rPreco preconditioner for the linear solves.
	 * @return \c true on success, \c false on failure.
	 */
	bool SetPreconditioner(Preconditioner& rPreco);

	/**
	 * Set the forcing term strategy.
	 * @param rForcing forcing terms for the linear solves.
	 * @return \c true on success, \c false on failure.
	 */
	bool SetForcingTerms(ForcingTerms& rForcing);

	/**
	 * Solves a non-linear problem.
	 * Preconditioner and forcing terms are read from the command line options,
	 * and set to their defaults if not provided on the command line.
	 * @param rProblem non-linear problem to solve.
	 * @return \c true on success, \c false on failure.
	 */
	bool Solve(NonLinearProblem& rProblem);

	/**
	 * Creates a preconditioner object according to the command line options.
	 * The calling function is responsible for deleting the created object.
	 * @param rProblem non-linear problem that is to be solved.
	 * @return created preconditioner object.
	 */
	static Preconditioner* CreatePreconditionerFromOptions(void);

	/**
	 * Creates a forcing terms object according to the command line options.
	 * The calling function is responsible for deleting the created object.
	 * @return created forcing terms object.
	 */
	static ForcingTerms* CreateForcingTermsFromOptions(void);

	// ACCESS

	bool IsConverged(void);
	double GetFunctionNorm(void);
	int GetNonlinearIterationCount(void);
	int GetLinearIterationCount(void);
	int GetFunctionEvaluationCount(void);

private:

	/**
	 * Function wrapper called by PETSc SNES to calculate the function vector.
	 * @param[in] snes PETSc non-linear equation solver object.
	 * @param[in] pX current non-linear solution iterate.
	 * @param[in,out] pF function vector.
	 * @param[in] pCtx pointer to the NonLinearSolver object calling the wrapper.
	 * @return non-zero error code on failure, 0 on success.
	 */
	static PetscErrorCode CalculateFunctionWrapper(SNES snes, Vec pX, Vec pF, void* pCtx);

	/**
	 * Function wrapper called by PETSc SNES to calculate the Jacobian matrix.
	 * @param[in] snes PETSc non-linear equation solver object.
	 * @param[in] pX current non-linear solution iterate.
	 * @param[in,out] ppJ current Jacobian matrix.
	 * @param[in,out] ppM preconditioner matrix.
	 * @param[in,out] pFlag preconditioner matrix structure flag.
	 * @param[in] pCtx pointer to the NonLinearSolver object calling the wrapper.
	 * @return non-zero error code on failure, 0 on success.
	 */
	static PetscErrorCode CalculateJacobianWrapper(SNES snes, Vec pX, Mat* ppJ, Mat* ppM, MatStructure* pFlag, void* pCtx);

	/**
	 * Function wrapper called by PETSc SNES to test linear solver convergence.
	 * @param[in] ksp PETSc linear equation solver object.
	 * @param[in] it current linear iteration within the current non-linear iteration.
	 * @param[in] rnorm linear residual norm.
	 * @param[out] pReason reason for convergence or non-convergence.
	 * @param[in] pCtx pointer to the NonLinearSolver object calling the wrapper.
	 * @return non-zero error code on failure, 0 on success.
	 */
	static PetscErrorCode ConvergenceTestWrapper(KSP ksp, PetscInt it, PetscReal rnorm, KSPConvergedReason* pReason, void* pCtx);

	/**
	 * Function wrapper called by PETSc SNES to set up a shell preconditioner.
	 * @param[in] pc PETSc preconditioner object.
	 * @return non-zero error code on failure, 0 on success.
	 */
	static PetscErrorCode PCSetUpWrapper(PC pc);

	/**
	 * Function wrapper called by PETSc SNES to apply a shell preconditioner.
	 * @param[in] pc PETSc preconditioner object.
	 * @param[in] x vector to apply the shell preconditioner on.
	 * @param[out] y result of the application of the shell precondtioner.
	 * @return non-zero error code on failure, 0 on success.
	 */
	static PetscErrorCode PCApplyWrapper(PC pc, Vec x, Vec y);

	static const int msPetscOptionMaxLength = 64;			///< Maximum number of characters in a command line option string.
	static const std::string msPetscOptionPrecoType;		///< Preconditioner type option identifier.
	static const std::string msPetscOptionPrecoMatrixType;	///< Preconditioner matrix type option identifier.
	static const std::string msPetscOptionForcingTermsType;	///< Forcing terms type option identifier.

	PetscErrorCode mErr;				///< Error code of the most recent PETSc error.
	SNES mPetscSnes;					///< PETSc non-linear solver object.

	NonLinearProblem* mpProblem;		///< Non-linear problem to solve (not owned).

	PreconditionerShell* mpPrecoShell;	///< Preconditioner shell for custom preconditioning types.
	Preconditioner* mpPreco;			///< Preconditioner for the linear solves (not owned).
	ForcingTerms* mpForcing;			///< Forcing terms for the linear solves (not owned).

	PreconditionerShell* mpOwnedPrecoShell;
	Preconditioner* mpOwnedPreco;
	ForcingTerms* mpOwnedForcing;

	PetscReal mKspInitialRelativeTolerance;
	PetscReal mKspInitialAbsoluteTolerance;
	PetscReal mKspInitialDivergenceTolerance;
	PetscInt mKspInitialMaxIterationCount;

	SNESConvergedReason mConvergedReason;	///< 0 before starting, >0 if converged, <0 if diverged.
	PetscReal mFunctionNorm;				///< Nonlinear residual norm.
	PetscInt mNonLinearIterationCount;
	PetscInt mLinearIterationCount;
	PetscInt mFunctionEvaluationCount;

	bool mIsInitialized;			///< \c true if the object is initialized, \c false otherwise.
};

NONLINEARSOLVER_NAMESPACE_END

#endif // NonLinearSolver_NonLinearSolver_h
