/*
!===============================================================================
!
! quda_wrap.c - quda wrapper
!
!-------------------------------------------------------------------------------
!
! Copyright (C) 2012 Yoshifumi Nakamura
!
! This file is part of BQCD -- Berlin Quantum ChromoDynamics program
!
! BQCD is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! BQCD is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! GNU General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with BQCD.  If not, see <http://www.gnu.org/licenses/>.
!
!-------------------------------------------------------------------------------
*/
#ifdef NamesToLower
# define QUDA_INIT        quda_init
# define QUDA_INV         quda_inv
# define QUDA_SETKAPPA    quda_setkappa
# define QUDA_GCOPY       quda_gcopy
# define QUDA_CCOPY       quda_ccopy
# define QUDA_END         quda_end
# define QUDA_MDAGM       quda_mdagm
# define QUDA_D           quda_d
# define QUDA_CINV        quda_cinvd
# define QUDA_CLOVER_MULT quda_clover_mult
#endif
#ifdef NamesToLower_
# define QUDA_INIT        quda_init_
# define QUDA_INV         quda_inv_
# define QUDA_SETKAPPA    quda_setkappa_
# define QUDA_GCOPY       quda_gcopy_
# define QUDA_CCOPY       quda_ccopy_
# define QUDA_END         quda_end_
# define QUDA_MDAGM       quda_mdagm_
# define QUDA_D           quda_d_
# define QUDA_CINV        quda_cinvd_
# define QUDA_CLOVER_MULT quda_clover_mult_
#endif
#ifdef NamesToLower__
# define QUDA_INIT        quda_init__
# define QUDA_INV         quda_inv__
# define QUDA_SETKAPPA    quda_setkappa__
# define QUDA_GCOPY       quda_gcopy__
# define QUDA_CCOPY       quda_ccopy__
# define QUDA_END         quda_end__
# define QUDA_MDAGM       quda_mdagm__
# define QUDA_D           quda_d__
# define QUDA_CINV        quda_cinvd__
# define QUDA_CLOVER_MULT quda_clover_mult__
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// in a typical application, quda.h is the only QUDA header required
#include <quda.h>

extern QudaPrecision prec;
extern QudaPrecision  prec_sloppy;

QudaGaugeParam gauge_param;
QudaInvertParam inv_param ;
int localsize;

void quda_init_(int* lx, int* ly, int* lz, int* lt, int* maxiter, int *cid, int *dims, int *t_boundary){
  int i;
#ifdef MULTI_GPU
  int device = -1;
#else
  int device = 0;
#endif

  //  QudaPrecision cpu_prec = QUDA_DOUBLE_PRECISION;
  //  QudaPrecision cuda_prec = prec;
  //  QudaPrecision cuda_prec_sloppy = prec_sloppy;
  //  QudaPrecision cuda_prec_precondition = QUDA_HALF_PRECISION;

  //  QudaGaugeParam gauge_param = newQudaGaugeParam();
  //  QudaInvertParam inv_param = newQudaInvertParam();

  gauge_param = newQudaGaugeParam();
  inv_param = newQudaInvertParam();

  gauge_param.X[0] = *lx;
  gauge_param.X[1] = *ly;
  gauge_param.X[2] = *lz;
  gauge_param.X[3] = *lt;
  localsize=(*lx)*(*ly)*(*lz)*(*lt);
  //  setDims(gauge_param.X);


  gauge_param.anisotropy = 1.0;
  gauge_param.type = QUDA_WILSON_LINKS;
  //gauge_param.gauge_order = QUDA_QDP_GAUGE_ORDER;// works only Tbc=-1
  gauge_param.gauge_order = QUDA_BQCD_GAUGE_ORDER;// works only Tbc=-1
  //gauge_param.t_boundary = QUDA_ANTI_PERIODIC_T;
  gauge_param.t_boundary = *t_boundary;

  gauge_param.cpu_prec = QUDA_DOUBLE_PRECISION;
  gauge_param.cuda_prec = QUDA_DOUBLE_PRECISION;
  //gauge_param.reconstruct = QUDA_RECONSTRUCT_NO;//QUDA_RECONSTRUCT_12;
  gauge_param.reconstruct = QUDA_RECONSTRUCT_12;
  //gauge_param.cuda_prec_sloppy = QUDA_SINGLE_PRECISION;
  gauge_param.cuda_prec_sloppy = QUDA_DOUBLE_PRECISION;//need double(?) for invertMultiShiftQuda
  //gauge_param.reconstruct_sloppy = QUDA_RECONSTRUCT_NO;//QUDA_RECONSTRUCT_12;
  gauge_param.reconstruct_sloppy = QUDA_RECONSTRUCT_12;
  gauge_param.cuda_prec_precondition = QUDA_HALF_PRECISION;
  gauge_param.gauge_fix = QUDA_GAUGE_FIXED_NO;


  // Setup padding
#ifdef MULTI_GPU
  int face_size[4];
  face_size[0] = gauge_param.X[1]*gauge_param.X[2]*gauge_param.X[3]/2;
  face_size[1] = gauge_param.X[0]*gauge_param.X[2]*gauge_param.X[3]/2;
  face_size[2] = gauge_param.X[0]*gauge_param.X[1]*gauge_param.X[3]/2;
  face_size[3] = gauge_param.X[0]*gauge_param.X[1]*gauge_param.X[2]/2;
  int max_face = face_size[0];
  for(i=1; i <=3; i++) { 
    if ( face_size[i] > max_face ) { 
      max_face = face_size[i]; 
    }
  }
  gauge_param.ga_pad = max_face;
#else
  //gauge_param.ga_pad = 0;
#endif

  inv_param.maxiter = *maxiter;
  inv_param.solution_type = QUDA_MATPCDAG_MATPC_SOLUTION;
  inv_param.solve_type = QUDA_NORMEQ_PC_SOLVE;
  inv_param.mass_normalization = QUDA_KAPPA_NORMALIZATION;
  inv_param.preserve_source = QUDA_PRESERVE_SOURCE_YES;
  inv_param.tune = QUDA_TUNE_YES;
  inv_param.verbosity = QUDA_VERBOSE;
  inv_param.verbosity = QUDA_SILENT;


  inv_param.kappa=0;

  inv_param.matpc_type = QUDA_MATPC_EVEN_EVEN; // same as BQCD eo with omtdtd
  inv_param.dagger = QUDA_DAG_NO;
  inv_param.cpu_prec = gauge_param.cpu_prec;
  inv_param.cuda_prec = gauge_param.cuda_prec;
  inv_param.cuda_prec_sloppy = gauge_param.cuda_prec_sloppy;

  inv_param.sp_pad = 0;
  inv_param.cl_pad = 0;
  inv_param.gamma_basis = QUDA_UKQCD_GAMMA_BASIS; // same as BQCD
  inv_param.dirac_order = QUDA_QDP_DIRAC_ORDER; // same as BQCD

  if ( *cid != 0) {
    inv_param.dslash_type = QUDA_CLOVER_WILSON_DSLASH; // Sets Clover Matrix  
    inv_param.clover_cpu_prec = gauge_param.cpu_prec;
    inv_param.clover_cuda_prec = gauge_param.cuda_prec;
    inv_param.clover_cuda_prec_sloppy = gauge_param.cuda_prec_sloppy;
    inv_param.clover_cuda_prec_precondition = QUDA_HALF_PRECISION;
    inv_param.clover_order = QUDA_BQCD_CLOVER_ORDER;
  } else {
    inv_param.dslash_type = QUDA_WILSON_DSLASH; // Sets Clover Matrix  
  }

  //initCommsGridQuda(4, dims, NULL, NULL);
  comm_set_gridsize_(dims);

  // Everything between here and the call to initQuda() is application-specific.
  // set parameters for the reference Dslash, and prepare fields to be loaded
  //  setDims(gauge_param.X);
  // initialize the QUDA library
  initQuda(device);
}

void quda_inv_(void* spinorOut, void* spinorIn, double* tol, int* iter){
  inv_param.inv_type = QUDA_CG_INVERTER;             // BQCD CG setup
  inv_param.use_init_guess = QUDA_USE_INIT_GUESS_YES;// BQCD CG setup
  inv_param.tol = *tol;
  inv_param.reliable_delta = 1e-1; // ignored by multi-shift solver

  // domain decomposition preconditioner parameters
  inv_param.inv_type = QUDA_GCR_INVERTER;
  inv_param.gcrNkrylov = 10;
  inv_param.inv_type_precondition = QUDA_MR_INVERTER;
  inv_param.tol_precondition = 1e-1;
  inv_param.maxiter_precondition = 10;
  inv_param.verbosity_precondition = QUDA_SILENT;
  inv_param.cuda_prec_precondition = QUDA_HALF_PRECISION;
  inv_param.omega = 1.0;
  /////////////////////////////////////////////////
  invertQuda(spinorOut, spinorIn, &inv_param);

  freeGaugeQuda();
  if ( inv_param.dslash_type == QUDA_CLOVER_WILSON_DSLASH ) freeCloverQuda();

  *iter = inv_param.iter;
  //printQudaInvertParam(&inv_param);
}

void quda_minv_(double* spinorOutMulti, void* spinorIn, int* num_offset, double* offset, double* tol, int* iter){
  int i;
  void* out[*num_offset];

  inv_param.inv_type = QUDA_CG_INVERTER;
  inv_param.tol = (double)*tol;
  inv_param.reliable_delta = 1e-1; // ignored by multi-shift solver
  inv_param.num_offset = *num_offset;
  //double offset[4] = {0.01, 0.02, 0.03, 0.04};
  for (i=0; i<inv_param.num_offset; i++) {
    out[i] = (void*)&spinorOutMulti[i*localsize/2*24];
    inv_param.offset[i] = (double)*(offset+i);
    inv_param.tol_offset[i] = (double)*(tol+i);
  }
  
  invertMultiShiftQuda(out, spinorIn, &inv_param);


  freeGaugeQuda();
  if ( inv_param.dslash_type == QUDA_CLOVER_WILSON_DSLASH ) freeCloverQuda();

  *iter = inv_param.iter;
  //printQudaInvertParam(&inv_param);
}



void quda_setkappa_(double* kappa){  inv_param.kappa=*kappa;}
void quda_gcopy_(double* hostGauge){
  //int dir;
  //void* hostGauge1[4];
  //for (dir = 0; dir < 4; dir++) hostGauge1[dir] = (void*)&hostGauge[dir*localsize*18];
  //loadGaugeQuda(hostGauge1, &gauge_param);
  loadGaugeQuda(hostGauge, &gauge_param);
}
void quda_ccopy_(void* clv, void* inv){loadCloverQuda(clv, inv, &inv_param);}
void quda_end_() { endQuda();}

//
// some test function
//
void quda_mdagm_(void* spinorOut, void* spinorIn){MatDagMatQuda(spinorOut, spinorIn, &inv_param);}
void quda_d_(void* spinorOut, void* spinorIn, int* eo){
  QudaDslashType dtype = inv_param.dslash_type;
  inv_param.dslash_type = QUDA_WILSON_DSLASH;
  if (*eo == 0) {
    //    printf("dslashQuda QUDA_EVEN_PARITY\n");
    dslashQuda(spinorOut, spinorIn, &inv_param, QUDA_EVEN_PARITY);
  } else {
    //    printf("dslashQuda QUDA_ODD_PARITY\n");
    dslashQuda(spinorOut, spinorIn, &inv_param, QUDA_ODD_PARITY);
  }
  inv_param.dslash_type = dtype;
}

void quda_cinvd_(void* spinorOut, void* spinorIn, int* eo){
  if (*eo == 0) {
    //    printf("dslashQuda QUDA_EVEN_PARITY\n");
    dslashQuda(spinorOut, spinorIn, &inv_param, QUDA_EVEN_PARITY);
  } else {
    //    printf("dslashQuda QUDA_ODD_PARITY\n");
    dslashQuda(spinorOut, spinorIn, &inv_param, QUDA_ODD_PARITY);
  }
}

void quda_clover_mult_(void* spinorOut, void* spinorIn, int* eo){
  double kappa = inv_param.kappa;
  QudaMatPCType type = inv_param.matpc_type;

  inv_param.kappa=0;
  if (*eo == 0) {
    //    printf("MatQuda QUDA_MATPC_EVEN_EVEN_ASYMMETRIC kappa=%f\n",inv_param.kappa);
    inv_param.matpc_type = QUDA_MATPC_EVEN_EVEN_ASYMMETRIC;
  }  else {
    //    printf("MatQuda QUDA_MATPC_ODD_ODD_ASYMMETRIC kappa=%f\n",inv_param.kappa);
    inv_param.matpc_type = QUDA_MATPC_ODD_ODD_ASYMMETRIC;
  }
  MatQuda(spinorOut, spinorIn, &inv_param);
  //  printQudaInvertParam(&inv_param);
  inv_param.kappa=kappa;
  inv_param.matpc_type=type;
}
