/*@HEADER
// ***********************************************************************
//
//       Ifpack2:  Templated Object-Oriented Algebraic Preconditioner Package
//                 Copyright (2009) Sandia Corporation
//
// Under terms of Contract DE-AC04-94AL85000, there is a non-exclusive
// license for use of this work by or on behalf of the U.S. Government.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Questions? Contact Michael A. Heroux (maherou@sandia.gov)
//
// ***********************************************************************
//@HEADER
*/


#ifndef IFPACK2_HYPRE_FUNCTIONPARAMETERS_HPP
#define IFPACK2_HYPRE_FUNCTIONPARAMETERS_HPP

#include "Ifpack2_ConfigDefs.hpp"
#if defined(HAVE_IFPACK2_HYPRE) && defined(HAVE_IFPACK2_MPI)

#include <sstream>
#include "HYPRE_utilities.h"
#include "HYPRE_IJ_mv.h"
#include "HYPRE_parcsr_ls.h"
#include "krylov.h"
#include "_hypre_parcsr_mv.h"
#include "_hypre_IJ_mv.h"
#include "HYPRE_parcsr_mv.h"
#include "HYPRE.h"

// Hypre forward declarations (to avoid downstream header pollution)
struct hypre_IJMatrix_struct;
typedef struct hypre_IJMatrix_struct *HYPRE_IJMatrix;
struct hypre_IJVector_struct;
typedef struct hypre_IJVector_struct *HYPRE_IJVector;
struct hypre_ParCSRMatrix_struct;
typedef struct hypre_ParCSRMatrix_struct* HYPRE_ParCSRMatrix;
struct hypre_ParVector_struct;
typedef struct hypre_ParVector_struct * HYPRE_ParVector;
struct hypre_Solver_struct;
typedef struct hypre_Solver_struct *HYPRE_Solver;
struct hypre_ParVector_struct;
typedef struct hypre_ParVector_struct hypre_ParVector;
//struct hypre_Vector;

#ifndef HYPRE_ENUMS
#define HYPRE_ENUMS
//! This enumerated type defines the allowed solvers and preconditioners in Hypre. Some can be used as both solver and preconditioner.
  enum Hypre_Solver{
    BoomerAMG,
    ParaSails,
    Euclid,
    AMS,
    Hybrid,
    PCG,
    GMRES,
    FlexGMRES,
    LGMRES,
    BiCGSTAB
  };

  //! This enumerated type defines the two options for applying inverse, either solve or apply the preconditioner.
  enum Hypre_Chooser{
    Hypre_Is_Solver,
    Hypre_Is_Preconditioner
  };
#endif //HYPRE_ENUMS

// The Python script that generates the ParameterMap needs to be after these typedefs
typedef HYPRE_Int (*int_func)(HYPRE_Solver, HYPRE_Int);
typedef HYPRE_Int (*double_func)(HYPRE_Solver, HYPRE_Real);
typedef HYPRE_Int (*double_int_func)(HYPRE_Solver, HYPRE_Real, HYPRE_Int);
typedef HYPRE_Int (*int_double_func)(HYPRE_Solver, HYPRE_Int, HYPRE_Real);
typedef HYPRE_Int (*int_int_func)(HYPRE_Solver, HYPRE_Int, HYPRE_Int);
typedef HYPRE_Int (*int_star_func)(HYPRE_Solver, HYPRE_Int*);
typedef HYPRE_Int (*int_star_star_func)(HYPRE_Solver, HYPRE_Int**);
typedef HYPRE_Int (*double_star_func)(HYPRE_Solver, HYPRE_Real*);
typedef HYPRE_Int (*int_int_double_double_func)(HYPRE_Solver, HYPRE_Int, HYPRE_Int, HYPRE_Real, HYPRE_Real);
typedef HYPRE_Int (*int_int_int_double_int_int_func)(HYPRE_Solver, HYPRE_Int, HYPRE_Int, HYPRE_Int, HYPRE_Real, HYPRE_Int, HYPRE_Int);
typedef HYPRE_Int (*char_star_func)(HYPRE_Solver, char*);


namespace Ifpack2 {

  void IFPACK2_CHK_ERRV(int code);

  void IFPACK2_CHK_ERR(int code);

  //! This class is used to help with passing parameters in the SetParameter() function. Use this class to call Hypre's internal parameters.
  class FunctionParameter {
  public:
    //! Single int constructor.
    FunctionParameter(Hypre_Chooser chooser, int_func funct, HYPRE_Int param1) :
      chooser_(chooser),
      option_(0),
      int_func_(funct),
      int_param1_(param1) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Int param1) :
      chooser_(chooser),
      option_(0),
      int_func_(hypreMapIntFunc_.at(funct_name)),
      int_param1_(param1) {}

    //! Single HYPRE_Real constructor.
    FunctionParameter(Hypre_Chooser chooser, double_func funct, HYPRE_Real param1):
      chooser_(chooser),
      option_(1),
      double_func_(funct),
      double_param1_(param1) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Real param1):
      chooser_(chooser),
      option_(1),
      double_func_(hypreMapDoubleFunc_.at(funct_name)),
      double_param1_(param1) {}

    //! Single double, single int constructor.
    FunctionParameter(Hypre_Chooser chooser, double_int_func funct, HYPRE_Real param1, HYPRE_Int param2):
      chooser_(chooser),
      option_(2),
      double_int_func_(funct),
      int_param1_(param2),
      double_param1_(param1) {}

    //! Single int, single HYPRE_Real constructor.
    FunctionParameter(Hypre_Chooser chooser, int_double_func funct, HYPRE_Int param1, HYPRE_Real param2):
      chooser_(chooser),
      option_(10),
      int_double_func_(funct),
      int_param1_(param1),
      double_param1_(param2) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Real param1, HYPRE_Int param2):
      chooser_(chooser),
      option_(2),
      double_int_func_(hypreMapDoubleIntFunc_.at(funct_name)),
      int_param1_(param2),
      double_param1_(param1) {}

    //! Two ints constructor.
    FunctionParameter(Hypre_Chooser chooser, int_int_func funct, HYPRE_Int param1, HYPRE_Int param2):
      chooser_(chooser),
      option_(3),
      int_int_func_(funct),
      int_param1_(param1),
      int_param2_(param2) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Int param1, HYPRE_Int param2):
      chooser_(chooser),
      option_(3),
      int_int_func_(hypreMapIntIntFunc_.at(funct_name)),
      int_param1_(param1),
      int_param2_(param2) {}

    //! Int pointer constructor.
    FunctionParameter(Hypre_Chooser chooser, int_star_func funct, HYPRE_Int *param1):
      chooser_(chooser),
      option_(4),
      int_star_func_(funct),
      int_star_param_(param1) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Int *param1):
      chooser_(chooser),
      option_(4),
      int_star_func_(hypreMapIntStarFunc_.at(funct_name)),
      int_star_param_(param1) {}

    //! HYPRE_Real pointer constructor.
    FunctionParameter(Hypre_Chooser chooser, double_star_func funct, double* param1):
      chooser_(chooser),
      option_(5),
      double_star_func_(funct),
      double_star_param_(param1) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, double* param1):
      chooser_(chooser),
      option_(5),
      double_star_func_(hypreMapDoubleStarFunc_.at(funct_name)),
      double_star_param_(param1) {}

    //! Two ints, two doubles constructor.
    FunctionParameter(Hypre_Chooser chooser, int_int_double_double_func funct, HYPRE_Int param1, HYPRE_Int param2, HYPRE_Real param3, HYPRE_Real param4):
      chooser_(chooser),
      option_(6),
      int_int_double_double_func_(funct),
      int_param1_(param1),
      int_param2_(param2),
      double_param1_(param3),
      double_param2_(param4) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Int param1, HYPRE_Int param2, HYPRE_Real param3, HYPRE_Real param4):
      chooser_(chooser),
      option_(6),
      int_int_double_double_func_(hypreMapIntIntDoubleDoubleFunc_.at(funct_name)),
      int_param1_(param1),
      int_param2_(param2),
      double_param1_(param3),
      double_param2_(param4) {}

    //! Integer pointer to list of integer pointers
    FunctionParameter(Hypre_Chooser chooser, int_star_star_func funct, HYPRE_Int ** param1):
      chooser_(chooser),
      option_(7),
      int_star_star_func_(funct),
      int_star_star_param_(param1) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Int** param1):
      chooser_(chooser),
      option_(7),
      int_star_star_func_(hypreMapIntStarStarFunc_.at(funct_name)),
      int_star_star_param_(param1) {}

    //! Five ints, one HYPRE_Real constructor.
    FunctionParameter(Hypre_Chooser chooser, int_int_int_double_int_int_func funct, HYPRE_Int param1, HYPRE_Int param2, HYPRE_Int param3, HYPRE_Real param4, HYPRE_Int param5, HYPRE_Int param6):
      chooser_(chooser),
      option_(8),
      int_int_int_double_int_int_func_(funct),
      int_param1_(param1),
      int_param2_(param2),
      int_param3_(param3),
      int_param4_(param5),
      int_param5_(param6),
      double_param1_(param4) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, HYPRE_Int param1, HYPRE_Int param2, HYPRE_Int param3, HYPRE_Real param4, HYPRE_Int param5, HYPRE_Int param6):
      chooser_(chooser),
      option_(8),
      int_int_int_double_int_int_func_(hypreMapIntIntIntDoubleIntIntFunc_.at(funct_name)),
      int_param1_(param1),
      int_param2_(param2),
      int_param3_(param3),
      int_param4_(param5),
      int_param5_(param6),
      double_param1_(param4) {}

    //! Char pointer constructor.
    FunctionParameter(Hypre_Chooser chooser, char_star_func funct, char *param1):
      chooser_(chooser),
      option_(9),
      char_star_func_(funct),
      char_star_param_(param1) {}

    FunctionParameter(Hypre_Chooser chooser, std::string funct_name, char *param1):
      chooser_(chooser),
      option_(9),
      char_star_func_(hypreMapCharStarFunc_.at(funct_name)),
      char_star_param_(param1) {}

    //! Only method of this class. Calls the function pointer with the passed in HYPRE_Solver
    int CallFunction(HYPRE_Solver solver, HYPRE_Solver precond) {
      if(chooser_ == Hypre_Is_Solver){
        if(option_ == 0){
          return int_func_(solver, int_param1_);
        } else if(option_ == 1){
          return double_func_(solver, double_param1_);
        } else if(option_ == 2){
          return double_int_func_(solver, double_param1_, int_param1_);
        } else if (option_ == 3){
          return int_int_func_(solver, int_param1_, int_param2_);
        } else if (option_ == 4){
          return int_star_func_(solver, int_star_param_);
        } else if (option_ == 5){
          return double_star_func_(solver, double_star_param_);
        } else if (option_ == 6) {
          return int_int_double_double_func_(solver, int_param1_, int_param2_, double_param1_, double_param2_);
        } else if (option_ == 7) {
          return int_star_star_func_(solver, int_star_star_param_);
        } else if (option_ == 8) {
          return int_int_int_double_int_int_func_(solver, int_param1_, int_param2_, int_param3_, double_param1_, int_param4_, int_param5_);
        } else if (option_ == 9) {
          return char_star_func_(solver, char_star_param_);
        } else if (option_ == 10) {
          return int_double_func_(solver, int_param1_, double_param1_);
        } else {
          IFPACK2_CHK_ERR(-2);
        }
      } else {
        if(option_ == 0){
          return int_func_(precond, int_param1_);
        } else if(option_ == 1){
          return double_func_(precond, double_param1_);
        } else if(option_ == 2){
          return double_int_func_(precond, double_param1_, int_param1_);
        } else if(option_ == 3) {
          return int_int_func_(precond, int_param1_, int_param2_);
        } else if(option_ == 4) {
          return int_star_func_(precond, int_star_param_);
        } else if(option_ == 5) {
          return double_star_func_(precond, double_star_param_);
        } else if (option_ == 6) {
          return int_int_double_double_func_(precond, int_param1_, int_param2_, double_param1_, double_param2_);
        } else if (option_ == 7) {
          return int_star_star_func_(precond, int_star_star_param_);
        } else if (option_ == 8) {
          return int_int_int_double_int_int_func_(precond, int_param1_, int_param2_, int_param3_, double_param1_, int_param4_, int_param5_);
        } else if (option_ == 9) {
          return char_star_func_(solver, char_star_param_);
        } else if (option_ == 10) {
          return int_double_func_(precond, int_param1_, double_param1_);
        } else {
          IFPACK2_CHK_ERR(-2);
        }
      }
      return 0;
    }

    static bool isFuncIntInt(std::string funct_name) {
      return (hypreMapIntIntFunc_.find(funct_name) != hypreMapIntIntFunc_.end());
    }

    static bool isFuncIntIntDoubleDouble(std::string funct_name) {
      return (hypreMapIntIntDoubleDoubleFunc_.find(funct_name) != hypreMapIntIntDoubleDoubleFunc_.end());
    }

    static bool isFuncIntIntIntDoubleIntInt(std::string funct_name) {
      return (hypreMapIntIntIntDoubleIntIntFunc_.find(funct_name) != hypreMapIntIntIntDoubleIntIntFunc_.end());
    }

    static bool isFuncIntStarStar(std::string funct_name) {
      return (hypreMapIntStarStarFunc_.find(funct_name) != hypreMapIntStarStarFunc_.end());
    }

  private:
    Hypre_Chooser chooser_;
    int option_;
    int_func int_func_;
    double_func double_func_;
    double_int_func double_int_func_;
    int_double_func int_double_func_;
    int_int_func int_int_func_;
    int_star_func int_star_func_;
    double_star_func double_star_func_;
    int_int_double_double_func int_int_double_double_func_;
    int_int_int_double_int_int_func int_int_int_double_int_int_func_;
    int_star_star_func int_star_star_func_;
    char_star_func char_star_func_;
    HYPRE_Int int_param1_;
    HYPRE_Int int_param2_;
    HYPRE_Int int_param3_;
    HYPRE_Int int_param4_;
    HYPRE_Int int_param5_;
    HYPRE_Real double_param1_;
    HYPRE_Real double_param2_;
    HYPRE_Int *int_star_param_;
    HYPRE_Int **int_star_star_param_;
    HYPRE_Real *double_star_param_;
    char *char_star_param_;

    static const std::map<std::string, int_func> hypreMapIntFunc_;
    static const std::map<std::string, double_func> hypreMapDoubleFunc_;
    static const std::map<std::string, double_int_func> hypreMapDoubleIntFunc_;
    static const std::map<std::string, int_double_func> hypreMapIntDoubleFunc_;
    static const std::map<std::string, int_int_func> hypreMapIntIntFunc_;
    static const std::map<std::string, int_star_func> hypreMapIntStarFunc_;
    static const std::map<std::string, double_star_func> hypreMapDoubleStarFunc_;
    static const std::map<std::string, int_int_double_double_func> hypreMapIntIntDoubleDoubleFunc_;
    static const std::map<std::string, int_int_int_double_int_int_func> hypreMapIntIntIntDoubleIntIntFunc_;
    static const std::map<std::string, int_star_star_func> hypreMapIntStarStarFunc_;
    static const std::map<std::string, char_star_func> hypreMapCharStarFunc_;

  };

}

#endif // HAVE_IFPACK2_HYPRE && HAVE_IFPACK2_MPI

#endif /* IFPACK2_HYPRE_FUNCTIONPARAMETERS_HPP */
