/*
 * 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 LocalData.cpp
 * @brief Implementation of LocalData.h definitions
 * @author Emmanuel */

#ifndef _LOCAL_DATA_CPP_
#define _LOCAL_DATA_CPP_

// LOCAL
#include <LocalData.h>

LocalData::LocalData(int nbAtomInMolAParam, int nbAtomParam) : nbAtomInMolA(nbAtomInMolAParam), nbAtom(nbAtomParam)
{
  /* Allocating memory */
  contributionAtomicAlone = new axis_t[ nbAtom ];
  
  /* Initializing values */
  reinit();
}
		
LocalData::~LocalData()
{
  delete [] contributionAtomicAlone;	
}

void
LocalData::init()
{
  /* Looping through the arrays */
  for(int i(0); i < 3 ; ++i)
    {
      /* Value setting */
      grad[i]                        = 0.0;
      gradIGM[i]                     = 0.0;
      contributionInsideMoleculeA[i] = 0.0;
      contributionInsideMoleculeB[i] = 0.0;
      
    }	
  
  /* Looping through the atoms */
  for( int j(0) ; j < nbAtom ; ++j )
    {
      contributionAtomicAlone[j].x		= 0.0;
      contributionAtomicAlone[j].y		= 0.0;
      contributionAtomicAlone[j].z		= 0.0;
      
    }
  
} // end of init()

void
LocalData::reinit()
{
  /* Looping through the arrays */
  for(int i(0); i < 3 ; ++i)
    {
      /* Value setting */
      grad[i]					= 0.0;
      gradIGM[i] 				= 0.0;
      contributionInsideMoleculeA[i]		= 0.0;
      contributionInsideMoleculeB[i]		= 0.0;
      
      /* One init values */
      gradIGMInter[i]				= 0.0;
      gradIGMAbsOut[i]				= 0.0;
      gradIGMAbsIn[i]				= 0.0;
    }	
				
  /* Looping through the atoms */
  for( int j(0) ; j < nbAtom ; ++j )
    {
      contributionAtomicAlone[j].x		= 0.0;
      contributionAtomicAlone[j].y		= 0.0;
      contributionAtomicAlone[j].z		= 0.0;
    }
  
}

void
LocalData::updateGrad( int i, axis_t partialGrad )
{
  /* Gradient update */
  grad[0] += partialGrad.x;
  grad[1] += partialGrad.y;
  grad[2] += partialGrad.z;
  
  /* Gradient IGM update */
  gradIGM[0] -= std::abs(partialGrad.x);
  gradIGM[1] -= std::abs(partialGrad.y);
  gradIGM[2] -= std::abs(partialGrad.z);
  
  /* Atoms belong to the first molecule */
  if(i < nbAtomInMolA)
    {
      /* Contribution inside molecule A update */
      contributionInsideMoleculeA[0] += partialGrad.x;
      contributionInsideMoleculeA[1] += partialGrad.y;
      contributionInsideMoleculeA[2] += partialGrad.z;
      
      /* Atoms belong to the second molcule */
    }
  else
    {
      
      /* Contribution inside molecule B update */
      contributionInsideMoleculeB[0] += partialGrad.x;
      contributionInsideMoleculeB[1] += partialGrad.y;
      contributionInsideMoleculeB[2] += partialGrad.z;
    }

  /* Updating values for the atoms */
  contributionAtomicAlone[i].x = partialGrad.x;
  contributionAtomicAlone[i].y = partialGrad.y;
  contributionAtomicAlone[i].z = partialGrad.z;
} // end of updateGrad
		
void
LocalData::updateIGMInter()
{
  gradIGMInter[0] = -std::abs(contributionInsideMoleculeA[0]) - std::abs(contributionInsideMoleculeB[0]);
  gradIGMInter[1] = -std::abs(contributionInsideMoleculeA[1]) - std::abs(contributionInsideMoleculeB[1]);
  gradIGMInter[2] = -std::abs(contributionInsideMoleculeA[2]) - std::abs(contributionInsideMoleculeB[2]);
}
		
void
LocalData::updateIGMAbsOut(int atomIndex)
/* ============ implement equations (6) of our paper          */
/*              J. Chem. Info. Moel., DOI: 10.1021/acs.jcim.9b01016,  */
/*              2020, 60, 1, 268–278     */
{
  /* If the atom is molecule B */
  if( atomIndex >= nbAtomInMolA )
    {
      /* Updating gradient values ==> EQUATION (6) in our paper: |At+A| + |B-At|           */
      /* let us consider an atom At inside FRAGMENT B                                      */
      /* substract its grad contribution from fragment B                                   */
      /*  and add it to the fragment A !                               */                      
      gradIGMAbsOut[0] = -std::abs(contributionInsideMoleculeA[0] + contributionAtomicAlone[atomIndex].x) - std::abs(contributionInsideMoleculeB[0] - contributionAtomicAlone[atomIndex].x);
      gradIGMAbsOut[1] = -std::abs(contributionInsideMoleculeA[1] + contributionAtomicAlone[atomIndex].y) - std::abs(contributionInsideMoleculeB[1] - contributionAtomicAlone[atomIndex].y);
      gradIGMAbsOut[2] = -std::abs(contributionInsideMoleculeA[2] + contributionAtomicAlone[atomIndex].z) - std::abs(contributionInsideMoleculeB[2] - contributionAtomicAlone[atomIndex].z);
      
      /* If the atom is molecule A R(just like in our paper) */	
    }
  else
    {
      /* Updating gradient values ==> EQUATION (6) in our paper: |At+B| + |A-At|  */
      /* let us consider an atom At inside FRAGMENT A                 */
      /* substract its grad contribution from fragment A              */
      /* and add it to the fragment B !                               */
      gradIGMAbsOut[0] = -std::abs(contributionInsideMoleculeB[0] + contributionAtomicAlone[atomIndex].x) - std::abs(contributionInsideMoleculeA[0] - contributionAtomicAlone[atomIndex].x);
      gradIGMAbsOut[1] = -std::abs(contributionInsideMoleculeB[1] + contributionAtomicAlone[atomIndex].y) - std::abs(contributionInsideMoleculeA[1] - contributionAtomicAlone[atomIndex].y);
      gradIGMAbsOut[2] = -std::abs(contributionInsideMoleculeB[2] + contributionAtomicAlone[atomIndex].z) - std::abs(contributionInsideMoleculeA[2] - contributionAtomicAlone[atomIndex].z);
      
    }
			
  
}// end of updateIGMAbsOut

void
LocalData::updateIGMAbsIn(int atomIndex)
/* function which must be called after updateIGMAbsOut ! */
/* ============ implement equations (5) of our paper          */
/*              J. Chem. Info. Moel., DOI: 10.1021/acs.jcim.9b01016,  */
/*              2020, 60, 1, 268–278     */
{
  /* If the atom is in FRAGMENT B */
  if( atomIndex >= nbAtomInMolA )
    {
      /* Updating gradient values ==> EQUATION (5) in our paper: |At| + |A| + |B-At| */
      /* let us consider an atom At inside FRAGMENT B                                */
      /* substract its grad contribution from fragment B                             */
      /*  and add it separately from |A| and |B-At| !                                */
      gradIGMAbsIn[0] = -std::abs(contributionInsideMoleculeA[0]) - std::abs(contributionInsideMoleculeB[0] - contributionAtomicAlone[atomIndex].x) -std::abs(contributionAtomicAlone[atomIndex].x);
      gradIGMAbsIn[1] = -std::abs(contributionInsideMoleculeA[1]) - std::abs(contributionInsideMoleculeB[1] - contributionAtomicAlone[atomIndex].y) -std::abs(contributionAtomicAlone[atomIndex].y);
      gradIGMAbsIn[2] = -std::abs(contributionInsideMoleculeA[2]) - std::abs(contributionInsideMoleculeB[2] - contributionAtomicAlone[atomIndex].z) -std::abs(contributionAtomicAlone[atomIndex].z);

      /* If the atom is in FRAGMENT A  (just like in our paper) */
    }
  else
    {
      /* Updating gradient values ==> EQUATION (5) in our paper: |At| + |B| + |A-At| */
      /* let us consider an atom At inside FRAGMENT A                                */
      /* substract its grad contribution from fragment A                             */
      /*  and add it separately from |B| and |A-At| !                                */
      gradIGMAbsIn[0] = -std::abs(contributionInsideMoleculeB[0]) - std::abs(contributionInsideMoleculeA[0] - contributionAtomicAlone[atomIndex].x) -std::abs(contributionAtomicAlone[atomIndex].x);
      gradIGMAbsIn[1] = -std::abs(contributionInsideMoleculeB[1]) - std::abs(contributionInsideMoleculeA[1] - contributionAtomicAlone[atomIndex].y) -std::abs(contributionAtomicAlone[atomIndex].y);
      gradIGMAbsIn[2] = -std::abs(contributionInsideMoleculeB[2]) - std::abs(contributionInsideMoleculeA[2] - contributionAtomicAlone[atomIndex].z) -std::abs(contributionAtomicAlone[atomIndex].z);
      
    }
  
} // end of updateIGMAbsIn
		
double
LocalData::getNormGrad()
{
  return normGrad;
}
		
void
LocalData::processNormGrad()
{
  normGrad = std::sqrt( ( grad[0] * grad[0] ) + ( grad[1] * grad[1] ) + ( grad[2] * grad[2] ) );
}

double
LocalData::getNormGradIGM()
{
  return normGradIGM;
}
		
void
LocalData::processNormGradIGM()
{
  normGradIGM = std::sqrt( ( gradIGM[0] * gradIGM[0] ) + ( gradIGM[1] * gradIGM[1] ) + ( gradIGM[2] * gradIGM[2] ) );
}

double
LocalData::getNormGradIGMInter()
{
  return normGradIGMInter;
}
		
void
LocalData::processNormGradIGMInter()
{
  //ERIC CHANGE (moved before, at the end of computeGradHess)
  //updateIGMInter();
  normGradIGMInter = std::sqrt( ( gradIGMInter[0] * gradIGMInter[0] ) + 
                                ( gradIGMInter[1] * gradIGMInter[1] ) + 
                                ( gradIGMInter[2] * gradIGMInter[2] ) );
}// end of processNormGradIGMInter
		
double
LocalData::getNormGradIGMAbsOut()
{
  return normGradIGMInterAbsOut;
}

void
LocalData::processNormGradIGMAbsOut(int i)
{
  // compute the equation (6) of our paper An Atomic Decomposition Scheme 
  updateIGMAbsOut(i);
  normGradIGMInterAbsOut = std::sqrt( ( gradIGMAbsOut[0] * gradIGMAbsOut[0] ) + ( gradIGMAbsOut[1] * gradIGMAbsOut[1] ) + ( gradIGMAbsOut[2] * gradIGMAbsOut[2] ) );
}
		
double
LocalData::getNormGradIGMAbsIn()
{
  return normGradIGMInterAbsIn;
}
		
void
LocalData::processNormGradIGMAbsIn(int i)
{
  // compute the equation (5) of our paper An Atomic Decomposition Scheme 
  updateIGMAbsIn(i);
  normGradIGMInterAbsIn = std::sqrt( ( gradIGMAbsIn[0] * gradIGMAbsIn[0] ) + ( gradIGMAbsIn[1] * gradIGMAbsIn[1] ) + ( gradIGMAbsIn[2] * gradIGMAbsIn[2] ) );
}

		
double
LocalData::getGradVal(int index)
{
  if(index >= 0 && index < 3)
    {
      return grad[index];	
    }
  else
    {
      return -1;
    }
}
		
void
LocalData::setGradNull()
{
  grad[0] = 0.0;
  grad[1] = 0.0;
  grad[2] = 0.0;
  
}// end of setGradNull()

#endif
