/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2015 Frederik Heber. All rights reserved.
 *
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder 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 2 of the License, or
 *    (at your option) any later version.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * ChargeSmearer.cpp
 *
 *  Created on: Sep 2, 2015
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
//#include "CodePatterns/MemDebug.hpp"
#include "ChargeSmearer.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Singleton_impl.hpp"
#include 
#include 
#include 
ChargeSmearer::ChargeSmearer() :
  nfc(0),
  meshwidth(0.),
  vals(NULL),
  int_val(0.)
{}
ChargeSmearer::~ChargeSmearer()
{
  delete[] vals;
}
void ChargeSmearer::initializeSplineArray(
    const VMG::Particle::BSpline &_spl,
    const unsigned int _nfc,
    const double _meshwidth)
{
  // check whether it is changed, if not, do nothing
  if ((nfc == _nfc) && (meshwidth == _meshwidth))
    return;
  nfc = _nfc;
  meshwidth = _meshwidth;
  // reallocate memory for spline values
  delete[] vals;
  vals = new double[VMG::Helper::intpow(2*nfc+1,3)];
  // recalculate spline values
  unsigned int c=0;
  int_val = 0.;
  std::pair bounds[3];
  for (int dim=0;dim<3;++dim) {
    bounds[dim].first = -1*nfc;
    bounds[dim].second = nfc;
  }
  for (int i=bounds[0].first; i<=bounds[0].second; ++i)
    for (int j=bounds[1].first; j<=bounds[1].second; ++j)
      for (int k=bounds[2].first; k<=bounds[2].second; ++k) {
        const double dir_x = (double)i*meshwidth;
        const double dir_y = (double)j*meshwidth;
        const double dir_z = (double)k*meshwidth;
        // Compute distance from grid point to particle
        const double distance = std::sqrt(dir_x*dir_x+dir_y*dir_y+dir_z*dir_z);
        const double temp_val = _spl.EvaluateSpline(distance);
        vals[c++] = temp_val;
        int_val += temp_val;
        LOG(4, "DEBUG: Spline evaluated at " << distance << " is " << temp_val);
      }
  /// then integrate
  int_val = 1.0 / int_val;
  LOG(3, "DEBUG: Spline for smearing electronic charge distribution has norm factor of " << int_val);
}
void ChargeSmearer::visitBSplineDomain(
    const VMG::GridIterator &_iter,
    const visitor_t &_visitor
    ) const
{
  unsigned int c = 0;
  VMG::Index index;
  const VMG::Index end = _iter.GetIndex() + (int)nfc;
  for (index[0] = _iter.GetIndex()[0] - (int)nfc; index[0]<=end[0]; ++index[0])
    for (index[1] = _iter.GetIndex()[1] - (int)nfc; index[1]<=end[1]; ++index[1])
      for (index[2] = _iter.GetIndex()[2] - (int)nfc; index[2]<=end[2]; ++index[2]) {
        if (index.IsInBounds(_iter.GetBegin(), _iter.GetEnd()))
          _visitor(index, vals[c++]);
        else
          ++c;
      }
  assert( c == (unsigned int)VMG::Helper::intpow(2*nfc+1,3) );
}
static void DomainIntegrator(
    const VMG::Index & _index,
    const double _val,
    double &_int_val)
{
  _int_val += _val;
}
static void SplineSetter(
    const VMG::Index & _index,
    const double _val,
    const double _factor,
    VMG::Grid& _grid)
{
  _grid(_index) += _val * _factor;
}
void ChargeSmearer::operator()(
    VMG::Grid& _grid,
    const VMG::GridIterator &_iter,
    const double _charge) const
{
  // check whether we go over whole bspline support
  bool renormalize = false;
  for (int dim=0;dim<3;++dim) {
    renormalize |= (_iter.GetBegin()[dim] > _iter.GetIndex()[dim] - (int)nfc);
    renormalize |= (_iter.GetEnd()[dim] < _iter.GetIndex()[dim] + (int)nfc);
  }
  /// renormalize bspline if necessary (bounds don't fit in window)
  double temp_int_val = 0.;
  if (renormalize) {
    const visitor_t integrator = boost::bind(DomainIntegrator, _1, _2, boost::ref(temp_int_val));
    visitBSplineDomain(_iter, integrator);
    temp_int_val = 1.0 / temp_int_val;
  } else {
    temp_int_val = int_val;
  }
  /// then transfer to grid in bounded window
  {
    const visitor_t setter = boost::bind(SplineSetter, _1, _2, temp_int_val * _charge, boost::ref(_grid));
    visitBSplineDomain(_iter, setter);
  }
}
CONSTRUCT_SINGLETON(ChargeSmearer)