/*
 * Copyright University of Reims Champagne-Ardenne
 * Authors and Contributors: Akilan RAJAMANI, Corentin LEFEBVRE, Johanna KLEIN,
 *                           Emmanuel PLUOT, Gaetan RUBEZ, Hassan KHARTABIL,
 *                           Jean-Charles BOISSON and Eric HENON
 * (24/07/2017)
 * jean-charles.boisson@univ-reims.fr, eric.henon@univ-reims.fr
 *
 * This software is a computer program whose purpose is to
 * detect and quantify interactions from electron density
 * obtained either internally from promolecular density or
 * calculated from an input wave function input file. It also
 * prepares for the visualization of isosurfaces representing
 * several descriptors (dg) coming from the IGM methodology.
 *
 * This software is governed by the CeCILL-C license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL-C
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 *
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 *
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C license and that you accept its terms.
 *
 * */

/**
 * @file Results.cpp
 * @brief Implementation of Results.h definitions
 * @author Emmanuel */

#ifndef _RESULTS_CPP_
#define _RESULTS_CPP_

// LOCAL
#include <Results.h>

//PROMOLECULAR MODE
/* ============= C O N S T R U C T O R    of  R E S U L T S ====================== */
Results::Results(int nbAtomParam, int nbStepsParam) : nbAtom(nbAtomParam), fullSize(nbStepsParam), nbThreads(1)
{

#ifndef No_OpenMP
#pragma omp parallel
      {
#pragma omp single
	nbThreads = omp_get_num_threads();
      }
#endif
  
  try
    {
      
      /* Allocating values */
      cubedeltaGIntra 		= new double[fullSize];
      cubedeltaGInter 		= new double[fullSize];
      cubedeltaGIntraFC         = new double[fullSize];
      cubedeltaGInterFC         = new double[fullSize];
      cubeRho			= new double[fullSize];
      cubeRDG 			= new double[fullSize];
      intermolecularCheck	= new bool[fullSize];
      sumDeltaGInterAtom 	= new double*[nbAtom];

      for(int i(0);i<nbAtom;++i)
	{
	  sumDeltaGInterAtom[i]=new double[nbThreads];
	  
	}
    }
  catch (const std::bad_alloc& bad_alloc_error)
    {
      std::cerr << std::endl
		<< "[ERROR] Allocation failed: bad_alloc error catched" << std::endl
		<< "[ERROR] The chosen increments have generated a too high amount of data" << std::endl
		<< "[ERROR] that the current version of IGMplot is not able to manage, sorry." << std::endl
		<< "[ERROR] The program will now exit." << std::endl << std::endl;
      exit(EXIT_FAILURE);
    }
  
  
  /* Initialization */
  for( int i(0) ; i < nbAtom ; ++i )
    {
      
      for( unsigned int j(0) ; j < nbThreads ; ++j )
	{
	  
	  sumDeltaGInterAtom[i][j] = 0.0;
	}
      
    }

  // initialize grid integration properties
  sumdgInter = 0.0;
  scoreInterac = 0.0;



} // end of C O N S T R U C T O R    of  R E S U L T S  .............................

Results::~Results()
{
  
  /* Memory deallocation */
  delete [] cubedeltaGIntra;
  delete [] cubedeltaGInter;
  delete [] cubedeltaGIntraFC;
  delete [] cubedeltaGInterFC;
  delete [] cubeRho;
  delete [] cubeRDG;
  delete [] intermolecularCheck;

  for(int i(0);i<nbAtom; ++i)
    {
      delete[] sumDeltaGInterAtom[i];
    }
  
  delete [] sumDeltaGInterAtom;
  //delete [] sumDeltaKInterAtom;	
  
}

int
Results::getNbAtom()
{
  return nbAtom;
}

void
Results::updateCubePauli( int index, double &self)
{  /* ============  S T O R E    c u b e   F I N A L    R E S U L T S   ============ */

 // index   = input = position of current point in the grid represented as a one dimension array
 // index   = iz *[nbstep(2) * nbstep(1)] + iy * [nbstep(1)] + ix !!!
 // self    = input --> the self promolecular descriptor 

  // cubeRho array is squatted to store self //

  cubeRho[index]        =    self;                       

} // end of updateCubePauli method .........................................................


		
void
Results::updateCube( int index, LocalData & data, double & rho, double & lambda)
{  /* ============  S T O R E    c u b e   F I N A L    R E S U L T S   ============ */

 // index   = input = position of current point in the grid represented as a one dimension array
 // index   = iz *[nbstep(2) * nbstep(1)] + iy * [nbstep(1)] + ix !!!
 // data    = input = an object containing results like grad, gradIGM, ... but not rho
 // rho     = input = electron density
 // lambda  = input = second eigenvalue of the hessian ED (its sign will govern the kind of interaction
 //                   (attractive (<0)  or repulsive (<0))
  
  /* Processing delta G value for both inter and intra molecular interactions */
  cubedeltaGIntra[index]= 	( data.getNormGradIGM() 	-	data.getNormGradIGMInter() 	);
  cubedeltaGInter[index]= 	( data.getNormGradIGMInter() 	-	data.getNormGrad() 		);
 
  /* Updating rho and RDG's values */
  cubeRho[index]	=	lambda < 0 ? -rho : rho;
  cubeRDG[index] 	=	ALPHA 	* 	data.getNormGrad()/std::pow(rho, FOUR_THIRD);

//DEBUG
//std::cout << " rho = " << std::scientific << std::setprecision(8) << cubeRho[index]   << std::endl;

  
} // end of updateCube method .........................................................

void
Results::updateCubeFC( int index, LocalData & data)
{  /* ============  S T O R E    c u b e   F I N A L    R E S U L T S   ============ */
   //                           B A S I S     C H A N G E  

 // index   = input = position of current point in the grid represented as a one dimension array
 // index   = iz *[nbstep(2) * nbstep(1)] + iy * [nbstep(1)] + ix !!!
 // data    = input = an object containing results like grad, gradIGM, ... but not rho

  /* Processing delta G value for both inter and intra molecular interactions */
  cubedeltaGIntraFC[index]=       ( data.getNormGradIGM()         -       data.getNormGradIGMInter()      );
  cubedeltaGInterFC[index]=       ( data.getNormGradIGMInter()    -       data.getNormGrad()              );

} // end of updateCubeFC method .........................................................

void
Results::updateAtom( int i, LocalData & data/*, double rho*/)
{	
  /* Sum the delta gInterAtom for current atom i over the cube (equation (7) our our paper An Atomic Decomposition Scheme */
  /* 10.1021/acs.jcim.9b01016) */ 
#ifndef No_OpenMP
  sumDeltaGInterAtom[i][omp_get_thread_num()]+=( data.getNormGradIGMAbsIn()-data.getNormGradIGMAbsOut() );
#else
  sumDeltaGInterAtom[i][0]+=( data.getNormGradIGMAbsIn()-data.getNormGradIGMAbsOut() );
#endif

}

void
Results::update( int index, double rho, double value )
{
  
  /* Setting delta G value for both inter and intra molecular interactions 	*/
  cubedeltaGIntra[index]		= 	value;
  cubedeltaGInter[index]		= 	value;
  
  /* Setting cubeRho and cubeRDG's values 									*/
  cubeRho[index] 				= 	rho;
  cubeRDG[index] 				= 	100;
  
}

void
Results::sum()
{//reduction over threads
  
  double sumTmp;
  //double sumTmp2;
  
  /* Looping through atoms' indexes */
  for( int i(0) ; i < nbAtom ; ++i )
    {
      sumTmp = 0.0;
      
      unsigned int j;
      /* Looping through threads' indexes (except 0 : sum position) */
      
#ifndef No_OpenMP
#pragma omp parallel for private(j) reduction(+: sumTmp)
#endif
      
      for( j = 1 ; j < nbThreads ; ++j )
	{
	  
	  sumTmp += sumDeltaGInterAtom[i][j];
	  
	}
      
      sumDeltaGInterAtom[i][0] += sumTmp;
      
    }
  
}

void
Results::intermolecular(bool check, int pos)
{
  intermolecularCheck[pos] = check;
}

double
Results::get(int index, int type)
{
 // PROMOLECULAR MODE: returns a single value of a result ARRAY
 // input = index  : the ijk index expressed in one-dimensional array
 //                  or an atom index, depending on the type parameter
  double returnValue=0.0;
  
  switch(type)
    {
    case TYPE_DELTA_G_INTRA :
      returnValue=cubedeltaGIntra[index];
      break;
      
    case TYPE_DELTA_G_INTER :
      returnValue=cubedeltaGInter[index];
      break;
      
    case TYPE_DELTA_G_INTRAFC : 
      returnValue=cubedeltaGIntraFC[index];
      break;

    case TYPE_DELTA_G_INTERFC : 
      returnValue=cubedeltaGInterFC[index];
      break;
      
    case DGSIE :
      returnValue= sumDeltaGInterAtom[index][0]; // index = atom index
      break;
      
    case TYPE_RHO :
      returnValue=cubeRho[index];
      break;
      
    case TYPE_RDG :
      returnValue= cubeRDG[index];
      break;
      
    default :
      returnValue=-1.0;
      
    } // end of switch

  return returnValue;
} // end of get(int index, int type)
		
double*
Results::get(int type)
{

// PROMOLECULAR MODE: returns the cube reference associated with type parameter (dgIntra, dgInter, ...)
  double* returnValue=NULL;
  
  switch(type)
    {
    case TYPE_DELTA_G_INTRA :
      returnValue=cubedeltaGIntra;
      break;
      
    case TYPE_DELTA_G_INTER :
      returnValue=cubedeltaGInter;
      break;
      
    case TYPE_DELTA_G_INTRAFC : 
      returnValue=cubedeltaGIntraFC;
      break;
	
    case TYPE_DELTA_G_INTERFC : 
      returnValue=cubedeltaGInterFC;
      break;
      
    case TYPE_RHO :
      returnValue=cubeRho;
      break;
      
    case TYPE_RDG :
      returnValue=cubeRDG;
      break;
      
    default :
      returnValue=NULL;
      
    }

  return returnValue;
  
} // end of method get(int type)
		
bool*
Results::getShouldPrint()
{
  return intermolecularCheck;
}

/* returns the sumdgInter grid integration property */
double 
Results::getsumdgInter() 
{
  return sumdgInter;
}

/* returns the scoreInterac integration property */
double 
Results::getscoreInterac()
{
  return scoreInterac;
}

/* set the sumdgInter grid integration property */
void   
Results::setsumdgInter(double * newvalue)
{
  if (*newvalue >= 0.0) 
   {
    sumdgInter = *newvalue; // can be positive only
   }
  else
   {
      std::cerr << std::endl
                << "[ERROR] Invalid setting of sumdgInter in setsumdgInter(x)" <<  std::endl
                << "[ERROR] x must be positive, x = " << *newvalue << std::endl
                << "[ERROR] The program will now exit." << std::endl << std::endl;
      exit(EXIT_FAILURE);
   }
} // end of setsumdgInter ...............................................................

/* set the scoreInterac grid integration property */
void
Results::setscoreInterac(double newvalue)
{
    scoreInterac = newvalue; // can be positive or negative
} // end of setscoreInterac..............................................................

#endif
