/******************************************************************************
 * Copyright 1998-2019 Lawrence Livermore National Security, LLC and other
 * HYPRE Project Developers. See the top-level COPYRIGHT file for details.
 *
 * SPDX-License-Identifier: (Apache-2.0 OR MIT)
 ******************************************************************************/

#include "./HYPRE_parcsr_ls.h"
#include "../matrix_matrix/HYPRE_matrix_matrix_protos.h"
#include "../utilities/mpistubs.h"

/* Must include implementation definition for ParVector since no data access
  functions are publically provided. AJC, 5/99 */
/* Likewise for Vector. AJC, 5/99 */
#include "../seq_mv/vector.h"

/* AB 8/06 - replace header file */
/* #include "../parcsr_mv/par_vector.h" */
#include "../parcsr_mv/_hypre_parcsr_mv.h"

/* These are what we need from Euclid */
#include "distributed_ls/Euclid/_hypre_Euclid.h"
/* #include "../distributed_ls/Euclid/Mem_dh.h" */
/* #include "../distributed_ls/Euclid/io_dh.h" */
/* #include "../distributed_ls/Euclid/TimeLog_dh.h" */
/* #include "../distributed_ls/Euclid/Parser_dh.h" */
/* #include "../distributed_ls/Euclid/Euclid_dh.h" */

/*------------------------------------------------------------------
 * Error checking
 *------------------------------------------------------------------*/

#define HYPRE_EUCLID_ERRCHKA \
          if (errFlag_dh) {  \
            setError_dh("", __FUNC__, __FILE__, __LINE__); \
            printErrorMsg(stderr);  \
            hypre_MPI_Abort(comm_dh, -1); \
          }

  /* What is best to do here?  
   * What is HYPRE's error checking strategy?  
   * The shadow knows . . .
   *
   * Note: HYPRE_EUCLID_ERRCHKA macro is only used within this file.
   *
   * Note: "printErrorMsg(stderr)" is O.K. for debugging and
   *        development, possibly not for production.  This
   *        call causes Euclid to print a function call stack
   *        trace that led to the error.  (Potentially, each
   *        MPI task could print a trace.)
   *
   * Note: the __FUNC__ defines at the beginning of the function
   *       calls are used in Euclid's internal error-checking scheme.
   *       The "START_FUNC_DH" and "END_FUNC_VAL" macros are
   *       used for debugging: when "logFuncsToStderr == true"
   *       a function call trace is force-written to stderr;
   *       (useful for debugging over dial-up lines!)  See
   *       src/distributed_ls/Euclid/macros_dh.h and
   *       src/distributed_ls/Euclid/src/globalObjects.c
   *       for further info.
   */


/*--------------------------------------------------------------------------
 * debugging: if ENABLE_EUCLID_LOGGING is defined, each MPI task will open 
 * "logFile.id" for writing; also, function-call tracing is operational
 * (ie, you can set logFuncsToFile = true, logFuncsToSterr = true).
 *
 *--------------------------------------------------------------------------*/
#undef ENABLE_EUCLID_LOGGING

#if !defined(ENABLE_EUCLID_LOGGING)
#undef START_FUNC_DH
#undef END_FUNC_VAL
#undef END_FUNC_DH
#define START_FUNC_DH     /**/
#define END_FUNC_DH       /**/
#define END_FUNC_VAL(a)   return(a);
#endif


/*--------------------------------------------------------------------------
 * HYPRE_EuclidCreate - Return a Euclid "solver".  
 *--------------------------------------------------------------------------*/

#undef __FUNC__
#define __FUNC__ "HYPRE_EuclidCreate"
HYPRE_Int 
HYPRE_EuclidCreate( MPI_Comm comm,
                    HYPRE_Solver *solver )
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  START_FUNC_DH
  Euclid_dh eu; 

  /*----------------------------------------------------------- 
   * create a few global objects (yuck!) for Euclid's use;
   * these  are all pointers, are initially NULL, and are be set 
   * back to NULL in HYPRE_EuclidDestroy()
   * Global objects are defined in 
   * src/distributed_ls/Euclid/src/globalObjects.c
   *-----------------------------------------------------------*/

  comm_dh = comm;
  hypre_MPI_Comm_size(comm_dh, &np_dh);    HYPRE_EUCLID_ERRCHKA;
  hypre_MPI_Comm_rank(comm_dh, &myid_dh);  HYPRE_EUCLID_ERRCHKA;

#ifdef ENABLE_EUCLID_LOGGING
  openLogfile_dh(0, NULL); HYPRE_EUCLID_ERRCHKA;
#endif

  if (mem_dh == NULL) {
    Mem_dhCreate(&mem_dh);  HYPRE_EUCLID_ERRCHKA;
  }

  if (tlog_dh == NULL) {
    TimeLog_dhCreate(&tlog_dh); HYPRE_EUCLID_ERRCHKA;
  }

  if (parser_dh == NULL) {
    Parser_dhCreate(&parser_dh); HYPRE_EUCLID_ERRCHKA;
  }
  Parser_dhInit(parser_dh, 0, NULL); HYPRE_EUCLID_ERRCHKA;

  /*----------------------------------------------------------- 
   * create and return a Euclid object
   *-----------------------------------------------------------*/
  Euclid_dhCreate(&eu); HYPRE_EUCLID_ERRCHKA;
  *solver = (HYPRE_Solver) eu;

  END_FUNC_VAL(0)
#endif
}

/*--------------------------------------------------------------------------
 * HYPRE_EuclidDestroy - Destroy a Euclid object.
 *--------------------------------------------------------------------------*/

#undef __FUNC__
#define __FUNC__ "HYPRE_EuclidDestroy"
HYPRE_Int 
HYPRE_EuclidDestroy( HYPRE_Solver solver )
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  START_FUNC_DH
  Euclid_dh eu = (Euclid_dh)solver;
  bool printMemReport = false;
  bool printStats = false;
  bool logging = eu->logging;

  /*---------------------------------------------------------------- 
     this block is for printing test data; this is used
     for diffing in autotests.
   *---------------------------------------------------------------- */
  if (Parser_dhHasSwitch(parser_dh, "-printTestData")) {
    FILE *fp;

    /* get filename to which to write report */
    char fname[] = "test_data_dh.temp", *fnamePtr = fname;
    Parser_dhReadString(parser_dh, "-printTestData", &fnamePtr); HYPRE_EUCLID_ERRCHKA;
    if (!strcmp(fnamePtr, "1")) {  /* in case usr didn't supply a name! */
      fnamePtr = fname;
    }

    /* print the report */
    fp = openFile_dh(fnamePtr, "w"); HYPRE_EUCLID_ERRCHKA;
    Euclid_dhPrintTestData(eu, fp); HYPRE_EUCLID_ERRCHKA;
    closeFile_dh(fp); HYPRE_EUCLID_ERRCHKA;
   
    printf_dh("\n@@@@@ Euclid test data was printed to file: %s\n\n", fnamePtr);
  }


  /*---------------------------------------------------------------- 
     determine which of Euclid's internal reports to print
   *----------------------------------------------------------------*/
  if (logging) {
    printStats = true;
    printMemReport = true;
  }
  if (parser_dh != NULL) {
    if (Parser_dhHasSwitch(parser_dh, "-eu_stats")) {
      printStats = true;
    }
    if (Parser_dhHasSwitch(parser_dh, "-eu_mem")) {
      printMemReport = true;
    }
  }

  /*------------------------------------------------------------------ 
     print Euclid's internal report, then destroy the Euclid object 
   *------------------------------------------------------------------ */
  if (printStats) {
    Euclid_dhPrintHypreReport(eu, stdout); HYPRE_EUCLID_ERRCHKA;
  }
  Euclid_dhDestroy(eu); HYPRE_EUCLID_ERRCHKA;


  /*------------------------------------------------------------------ 
     destroy all remaining Euclid library objects 
     (except the memory object)
   *------------------------------------------------------------------ */
  /*if (parser_dh != NULL) { dah 3/16/06  */
  if (parser_dh != NULL && ref_counter == 0) {
    Parser_dhDestroy(parser_dh); HYPRE_EUCLID_ERRCHKA;
    parser_dh = NULL;
  }

  /*if (tlog_dh != NULL) {  dah 3/16/06  */
  if (tlog_dh != NULL && ref_counter == 0) {
    TimeLog_dhDestroy(tlog_dh); HYPRE_EUCLID_ERRCHKA;
    tlog_dh = NULL;
  }

  /*------------------------------------------------------------------ 
     optionally print Euclid's memory report, 
     then destroy the memory object.
   *------------------------------------------------------------------ */
  /*if (mem_dh != NULL) {  dah 3/16/06  */
  if (mem_dh != NULL && ref_counter == 0) {
    if (printMemReport) { 
      Mem_dhPrint(mem_dh, stdout, false); HYPRE_EUCLID_ERRCHKA; 
    }
    Mem_dhDestroy(mem_dh);  HYPRE_EUCLID_ERRCHKA;
    mem_dh = NULL;
  }

#ifdef ENABLE_EUCLID_LOGGING
  closeLogfile_dh(); HYPRE_EUCLID_ERRCHKA;
#endif

  END_FUNC_VAL(0)
#endif
}

/*--------------------------------------------------------------------------
 * HYPRE_EuclidSetup - Set up function for Euclid.
 *--------------------------------------------------------------------------*/

#undef __FUNC__
#define __FUNC__ "HYPRE_EuclidSetup"
HYPRE_Int 
HYPRE_EuclidSetup( HYPRE_Solver solver,
                   HYPRE_ParCSRMatrix A,
                   HYPRE_ParVector b,
                   HYPRE_ParVector x   )
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  START_FUNC_DH
  Euclid_dh eu = (Euclid_dh)solver;


#if 0

for testing!
  {
  HYPRE_Int ierr;
  HYPRE_Int m,n,rs,re,cs,ce;

   HYPRE_DistributedMatrix mat;
   ierr = HYPRE_ConvertParCSRMatrixToDistributedMatrix( A, &mat );
   if (ierr) exit(-1);

    ierr = HYPRE_DistributedMatrixGetDims(mat, &m, &n);
    ierr = HYPRE_DistributedMatrixGetLocalRange(mat, &rs, &re,
                                       &cs, &ce);

    hypre_printf("\n### [%i] m= %i, n= %i, rs= %i, re= %i, cs= %i, ce= %i\n",
                                            myid_dh, m, n, rs, re, cs, ce);

   ierr = HYPRE_DistributedMatrixDestroy(mat);

   if (ierr) exit(-1);
  }
#endif

  Euclid_dhInputHypreMat(eu, A); HYPRE_EUCLID_ERRCHKA;
  Euclid_dhSetup(eu); HYPRE_EUCLID_ERRCHKA;

  END_FUNC_VAL(0)
#endif
}

/*--------------------------------------------------------------------------
 * HYPRE_EuclidSolve - Solve function for Euclid.
 *--------------------------------------------------------------------------*/

#undef __FUNC__
#define __FUNC__ "HYPRE_EuclidSolve"
HYPRE_Int 
HYPRE_EuclidSolve( HYPRE_Solver solver,
                   HYPRE_ParCSRMatrix A,
                   HYPRE_ParVector bb,
                   HYPRE_ParVector xx  )
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  START_FUNC_DH
  Euclid_dh eu = (Euclid_dh)solver;
  HYPRE_Real *b, *x;

  x = hypre_VectorData(hypre_ParVectorLocalVector((hypre_ParVector *) bb));
  b = hypre_VectorData(hypre_ParVectorLocalVector((hypre_ParVector *) xx));

  Euclid_dhApply(eu, x, b); HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

/*--------------------------------------------------------------------------
 * Insert command line (flag, value) pairs in Euclid's 
 *--------------------------------------------------------------------------*/

#undef __FUNC__
#define __FUNC__ "HYPRE_EuclidSetParams"
HYPRE_Int
HYPRE_EuclidSetParams(HYPRE_Solver solver, 
                      HYPRE_Int argc,
                      char *argv[] )
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else
  START_FUNC_DH
  Parser_dhInit(parser_dh, argc, argv); HYPRE_EUCLID_ERRCHKA;

  /* maintainers note: even though Parser_dhInit() was called in
     HYPRE_EuclidCreate(), it's O.K. to call it again.
   */
  END_FUNC_VAL(0)
#endif
}

/*--------------------------------------------------------------------------
 * Insert (flag, value) pairs in Euclid's  database from file
 *--------------------------------------------------------------------------*/

#undef __FUNC__
#define __FUNC__ "HYPRE_EuclidSetParamsFromFile"
HYPRE_Int
HYPRE_EuclidSetParamsFromFile(HYPRE_Solver solver, 
                              char *filename )
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  START_FUNC_DH
  Parser_dhUpdateFromFile(parser_dh, filename); HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

HYPRE_Int
HYPRE_EuclidSetLevel(HYPRE_Solver solver, 
                     HYPRE_Int level)
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  char str_level[8];
  START_FUNC_DH
  hypre_sprintf(str_level,"%d",level);
  Parser_dhInsert(parser_dh, "-level", str_level); HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

HYPRE_Int
HYPRE_EuclidSetBJ(HYPRE_Solver solver, 
                  HYPRE_Int bj)
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  char str_bj[8];
  START_FUNC_DH
  hypre_sprintf(str_bj,"%d",bj);
  Parser_dhInsert(parser_dh, "-bj", str_bj); HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

HYPRE_Int
HYPRE_EuclidSetStats(HYPRE_Solver solver, 
                     HYPRE_Int eu_stats)
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  char str_eu_stats[8];
  START_FUNC_DH
  hypre_sprintf(str_eu_stats,"%d",eu_stats);
  Parser_dhInsert(parser_dh, "-eu_stats", str_eu_stats); HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

HYPRE_Int
HYPRE_EuclidSetMem(HYPRE_Solver solver, 
                   HYPRE_Int eu_mem)
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  char str_eu_mem[8];
  START_FUNC_DH
  hypre_sprintf(str_eu_mem,"%d",eu_mem);
  Parser_dhInsert(parser_dh, "-eu_mem", str_eu_mem); HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

HYPRE_Int
HYPRE_EuclidSetSparseA(HYPRE_Solver solver, 
                       HYPRE_Real sparse_A)
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  char str_sparse_A[256];
  START_FUNC_DH
  hypre_sprintf(str_sparse_A,"%f",sparse_A);
  Parser_dhInsert(parser_dh, "-sparseA", str_sparse_A); 
  HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

HYPRE_Int
HYPRE_EuclidSetRowScale(HYPRE_Solver solver, 
                        HYPRE_Int row_scale)
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  char str_row_scale[8];
  START_FUNC_DH
  hypre_sprintf(str_row_scale,"%d",row_scale);
  Parser_dhInsert(parser_dh, "-rowScale", str_row_scale); 
  HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

HYPRE_Int
HYPRE_EuclidSetILUT(HYPRE_Solver solver, 
                    HYPRE_Real ilut)
{
#ifdef HYPRE_MIXEDINT
  hypre_error_w_msg(HYPRE_ERROR_GENERIC,"Euclid cannot be used in mixedint mode!");
  return hypre_error_flag;
#else

  char str_ilut[256];
  START_FUNC_DH
  hypre_sprintf(str_ilut,"%f",ilut);
  Parser_dhInsert(parser_dh, "-ilut", str_ilut); HYPRE_EUCLID_ERRCHKA;
  END_FUNC_VAL(0)
#endif
}

