/*
 *    vmg - a versatile multigrid solver
 *    Copyright (C) 2012 Institute for Numerical Simulation, University of Bonn
 *
 *  vmg 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.
 *
 *  vmg 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 this program.  If not, see .
 */
/**
 * @file   interface_fcs.cpp
 * @author Julian Iseringhausen 
 * @date   Mon Apr 18 12:56:20 2011
 *
 * @brief  VMG::InterfaceFCS
 *
 */
#ifdef HAVE_CONFIG_H
#include 
#endif
#ifndef HAVE_MPI
#error MPI is needed to use the Scafacos interface.
#endif
#include 
#ifdef HAVE_MARMOT
#include 
#include 
#endif
#include "base/object.hpp"
#include "base/timer.hpp"
#include "comm/domain_decomposition_mpi.hpp"
#ifdef DEBUG
#include "comm/mpi/error_handler.hpp"
#endif
#include "cycles/cycle_cs_periodic.hpp"
#include "cycles/cycle_fas_dirichlet.hpp"
#include "discretization/discretization_poisson_fd.hpp"
#include "discretization/discretization_poisson_fv.hpp"
#include "level/level_operator_cs.hpp"
#include "level/level_operator_fas.hpp"
#include "level/stencils.hpp"
#include "smoother/gsrb_poisson_2.hpp"
#include "smoother/gsrb_poisson_4.hpp"
#include "solver/givens.hpp"
#include "solver/solver_regular.hpp"
#include "solver/solver_singular.hpp"
#include "units/particle/comm_mpi_particle.hpp"
#include "units/particle/interface_fcs.h"
#include "units/particle/interface_particles.hpp"
#include "cycles/cycle_cs_periodic_debug.hpp"
//TODO: Remove Debug
using namespace VMG;
namespace VMGBackupSettings
{
  static vmg_int level = -1;
  static vmg_int periodic[3] = {-1, -1, -1};
  static vmg_int max_iter = -1;
  static vmg_int smoothing_steps = -1;
  static vmg_int cycle_type = -1;
  static vmg_float precision = -1;
  static vmg_float box_offset[3];
  static vmg_float box_size = -1.0;
  static vmg_int near_field_cells = -1;
  static vmg_int interpolation_degree = -1;
  static vmg_int discretization_order = -1;
  static MPI_Comm mpi_comm;
}
static void VMG_fcs_init(vmg_int level, vmg_int* periodic,vmg_int max_iter,
			 vmg_int smoothing_steps, vmg_int cycle_type, vmg_float precision,
			 vmg_float* box_offset, vmg_float box_size,
			 vmg_int near_field_cells, vmg_int interpolation_degree,
                         vmg_int discretization_order, MPI_Comm mpi_comm)
{
  VMGBackupSettings::level = level;
  std::memcpy(VMGBackupSettings::periodic, periodic, 3*sizeof(vmg_int));
  VMGBackupSettings::max_iter = max_iter;
  VMGBackupSettings::smoothing_steps = smoothing_steps;
  VMGBackupSettings::cycle_type = cycle_type;
  VMGBackupSettings::precision = precision;
  std::memcpy(VMGBackupSettings::box_offset, box_offset, 3*sizeof(vmg_float));
  VMGBackupSettings::box_size = box_size;
  VMGBackupSettings::near_field_cells = near_field_cells;
  VMGBackupSettings::interpolation_degree = interpolation_degree;
  VMGBackupSettings::discretization_order = discretization_order;
  VMGBackupSettings::mpi_comm = mpi_comm;
#ifdef DEBUG
  MPI_Errhandler mpiErrorHandler;
  MPI_Comm_create_errhandler(VMG::MPI::ConvertToException, &mpiErrorHandler);
  MPI_Comm_set_errhandler(mpi_comm, mpiErrorHandler);
#endif
  const Boundary boundary(periodic[0] ? Periodic : Open,
			  periodic[1] ? Periodic : Open,
			  periodic[2] ? Periodic : Open);
  const bool singular = boundary[0] == Periodic &&
                        boundary[1] == Periodic &&
                        boundary[2] == Periodic;
  /*
   * Choose multigrid components
   */
  if (singular) {
    new Particle::CommMPI(boundary, new DomainDecompositionMPI(), mpi_comm);
    new DiscretizationPoissonFD(discretization_order);
    new InterfaceParticles(boundary, 2, level, Vector(box_offset), box_size, near_field_cells);
    new LevelOperatorCS(Stencils::RestrictionFullWeight, Stencils::InterpolationTrilinear);
    new Givens();
    new CycleCSPeriodic(cycle_type);
  }else {
    new Particle::CommMPI(boundary, new DomainDecompositionMPI(), mpi_comm);
    new DiscretizationPoissonFV(discretization_order);
    new InterfaceParticles(boundary, 2, level, Vector(box_offset), box_size, near_field_cells, 9, 1.6);
    new LevelOperatorFAS(Stencils::RestrictionFullWeight, Stencils::Injection, Stencils::InterpolationTrilinear);
    new Givens();
    new CycleFASDirichlet(cycle_type);
  }
  /*
   * Use Gauss-Seidel Red-Black ordering
   */
  if (discretization_order == 2)
    new GaussSeidelRBPoisson2();
  else
    new GaussSeidelRBPoisson4();
  /*
   * Register required parameters
   */
  new ObjectStorage("PRESMOOTHSTEPS", smoothing_steps);
  new ObjectStorage("POSTSMOOTHSTEPS", smoothing_steps);
  new ObjectStorage("PRECISION", precision);
  new ObjectStorage("MAX_ITERATION", max_iter);
  new ObjectStorage("PARTICLE_NEAR_FIELD_CELLS", near_field_cells);
  new ObjectStorage("PARTICLE_INTERPOLATION_DEGREE", interpolation_degree);
  /*
   * Post init
   */
  MG::PostInit();
  /*
   * Check whether the library is correctly initialized now.
   */
  MG::IsInitialized();
}
void VMG_fcs_setup(vmg_int level, vmg_int* periodic, vmg_int max_iter,
		   vmg_int smoothing_steps, vmg_int cycle_type, vmg_float precision,
		   vmg_float* box_offset, vmg_float box_size,
		   vmg_int near_field_cells, vmg_int interpolation_degree,
                   vmg_int discretization_order, MPI_Comm mpi_comm)
{
  if (VMGBackupSettings::level != level ||
      VMGBackupSettings::periodic[0] != periodic[0] ||
      VMGBackupSettings::periodic[1] != periodic[1] ||
      VMGBackupSettings::periodic[2] != periodic[2] ||
      VMGBackupSettings::max_iter != max_iter ||
      VMGBackupSettings::smoothing_steps != smoothing_steps ||
      VMGBackupSettings::cycle_type != cycle_type ||
      VMGBackupSettings::precision != precision ||
      VMGBackupSettings::box_offset[0] != box_offset[0] ||
      VMGBackupSettings::box_offset[1] != box_offset[1] ||
      VMGBackupSettings::box_offset[2] != box_offset[2] ||
      VMGBackupSettings::box_size != box_size ||
      VMGBackupSettings::near_field_cells != near_field_cells ||
      VMGBackupSettings::interpolation_degree != interpolation_degree ||
      VMGBackupSettings::discretization_order != discretization_order ||
      VMGBackupSettings::mpi_comm != mpi_comm) {
    VMG_fcs_destroy();
    VMG_fcs_init(level, periodic, max_iter,
		 smoothing_steps, cycle_type, precision,
		 box_offset, box_size, near_field_cells,
                 interpolation_degree, discretization_order,
		 mpi_comm);
  }
}
int VMG_fcs_check()
{
  const int& near_field_cells = MG::GetFactory().GetObjectStorageVal("PARTICLE_NEAR_FIELD_CELLS");
  const Multigrid& multigrid = *MG::GetRhs();
  const Grid& grid = multigrid(multigrid.MaxLevel());
  vmg_int error_code = 0;
  if (!grid.Global().LocalSize().IsComponentwiseGreater(near_field_cells))
    error_code = 1;
  return MG::GetComm()->GlobalMax(error_code);
}
void VMG_fcs_run(vmg_float* x, vmg_float* q, vmg_float* p, vmg_float* f, vmg_int num_particles_local)
{
  /*
   * Register parameters for later use.
   */
  new ObjectStorage("PARTICLE_POS_ARRAY", x);
  new ObjectStorage("PARTICLE_CHARGE_ARRAY", q);
  new ObjectStorage("PARTICLE_POTENTIAL_ARRAY", p);
  new ObjectStorage("PARTICLE_FIELD_ARRAY", f);
  new ObjectStorage("PARTICLE_NUM_LOCAL", num_particles_local);
  /*
   * Start the multigrid solver
   */
  MG::Solve();
  Timer::Print();
}
void VMG_fcs_print_timer()
{
  Timer::Print();
}
void VMG_fcs_destroy(void)
{
  /*
   * Delete all data.
   */
  MG::Destroy();
}