/*
** (c) 1996-2000 The Regents of the University of California (through
** E.O. Lawrence Berkeley National Laboratory), subject to approval by
** the U.S. Department of Energy.  Your use of this software is under
** license -- the license agreement is attached and included in the
** directory as license.txt or you may contact Berkeley Lab's Technology
** Transfer Department at TTD@lbl.gov.  NOTICE OF U.S. GOVERNMENT RIGHTS.
** The Software was developed under funding from the U.S. Government
** which consequently retains certain rights as follows: the
** U.S. Government has been granted for itself and others acting on its
** behalf a paid-up, nonexclusive, irrevocable, worldwide license in the
** Software to reproduce, prepare derivative works, and perform publicly
** and display publicly.  Beginning five (5) years after the date
** permission to assert copyright is obtained from the U.S. Department of
** Energy, and subject to any subsequent five (5) year renewals, the
** U.S. Government is granted for itself and others acting on its behalf
** a paid-up, nonexclusive, irrevocable, worldwide license in the
** Software to reproduce, prepare derivative works, distribute copies to
** the public, perform publicly and display publicly, and to permit
** others to do so.
*/

#include <iostream>

#include <IntVect.H>
#include <Box.H>
#include <FArrayBox.H>
#include <multigrid.H>
#include <hgproj.H>
#include <globals.H>
#include <HGPROJ_F.H>

#include <ParmParse.H>

#define ARLIM(x) x[0],x[1]

int hg_projector::numSmoothCoarsen = 2;
int hg_projector::numSmoothRefine = 2;

// ************************************************************************
// ** constructor **
// ************************************************************************

hg_projector::hg_projector(const Box& Domain, Real * dx) :
       domain(Domain)
{

  ParmParse pp("hg");
  pp.get("tol",tol);

  pp.query("numSmoothCoarsen",numSmoothCoarsen);
  pp.query("numSmoothRefine",numSmoothRefine);

  hx = dx[0];
  hy = dx[1];

  phi = new FArrayBox(BoxLib::surroundingNodes(BoxLib::grow(domain,1)),1);
  phi->setVal(0.0);
}

// ************************************************************************
// ** destructor **
// ************************************************************************

hg_projector::~hg_projector()
{
  delete phi;
}

// ************************************************************************
// ** project **
// ************************************************************************

void hg_projector::project(FArrayBox * state, 
                           FArrayBox * pressure,
                           FArrayBox * rho, 
                           FArrayBox * divu_src, 
                           Real time, 
                           Real dt)

{

  Box sz(BoxLib::grow(domain,1));
  Box p_size(BoxLib::surroundingNodes(sz));

  FArrayBox *sigma = new FArrayBox(sz,1);
  FArrayBox *source = new FArrayBox(p_size,1);
  FArrayBox *resid  = new FArrayBox(p_size,1);

  sigma->setVal(0.);
  source->setVal(0.);
  resid->setVal(0.);

  FORT_INITSIG(sigma->dataPtr(),rho->dataPtr(),x,ARLIM(lo),ARLIM(hi),
               &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi);

  Real rhsnorm;

  FArrayBox gradp(sz,2);
  gradient(gradp,*pressure);

  for (int n = 0; n < 2; n++) 
  {
    gradp.divide(*rho,domain,0,n,1);
    state->plus(gradp,domain,n,n,1);
  }

  FORT_RHSHG(source->dataPtr(),state->dataPtr(),divu_src->dataPtr(),x,
             ARLIM(lo),ARLIM(hi),&hx,&hy,
             &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&rhsnorm,
             &time,&dt);

  std::cout << std::endl << "HG Source norm: " << rhsnorm << std::endl;

  if (rhsnorm > tol) {

    phi->setVal(0.0);

    Real goal = tol*rhsnorm;

    hgprojection_mg *solver =
      new hgprojection_mg(domain,phi,source,resid,sigma,hx,hy);

    solver->solve(goal,rhsnorm,numSmoothCoarsen,numSmoothRefine);
    delete solver;

    FArrayBox gradphi;
    gradphi.resize(sz,2);
    gradient(gradphi,*phi);

    FORT_PROJUHG(state->dataPtr(), pressure->dataPtr(), phi->dataPtr(),
  	         gradphi.dataPtr(),rho->dataPtr(),ARLIM(lo),ARLIM(hi));
  }

  delete sigma;
  delete source;
  delete resid;
}

// ************************************************************************
// ** gradient **
// ************************************************************************

void hg_projector::gradient(FArrayBox & gradp, FArrayBox & pressure)
{
  const int* p_lo = pressure.loVect();
  const int* p_hi = pressure.hiVect();

  const int* g_lo = gradp.loVect();
  const int* g_hi = gradp.hiVect(); 

  FORT_GRADHG(gradp.dataPtr(),ARLIM(g_lo),ARLIM(g_hi),
              pressure.dataPtr(),ARLIM(p_lo),ARLIM(p_hi),
	      ARLIM(lo),ARLIM(hi),&hx,&hy,
	      &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi);
}

// ************************************************************************
// ** constructor **
// ************************************************************************

hgprojection_mg::hgprojection_mg(const Box& Domain,
                                 FArrayBox *Phi,
                                 FArrayBox *Source,
                                 FArrayBox *Resid,
                                 FArrayBox *Sigma,
                                 Real Hx, 
                                 Real Hy) :
       multigrid(Domain, Phi, Source, Resid, Hx, Hy)
{

  sigma = Sigma;
  const IntVect& len = domain.length();

// If the grid has an odd number of cells, or has reached its minimum
// length in either direction, then we can not coarsen any further.
// If this is a pure multigrid cycle, use a conjugate gradient bottom
// solver; if this is a multigrid preconditioner for a conjugate gradient
// solve, then just relax many times at the bottom.

  if ( (len[0]&1) != 0 || (len[1]&1) != 0 || len[0] < 4 || len[1] < 4) {

    Next = NULL;

    cgwork = new FArrayBox(BoxLib::surroundingNodes(BoxLib::grow(domain,1)),4);
    FORT_MAKESUM(sigma->dataPtr(), cgwork->dataPtr(0),
                 ARLIM(lo_mg),ARLIM(hi_mg),&hx,&hy,&bcx_lo,&bcx_hi,
                 &bcy_lo,&bcy_hi);
  } else {

    cgwork = NULL; 
    Box newdomain = BoxLib::coarsen(domain,2);
    Box newp_size = BoxLib::surroundingNodes(BoxLib::grow(newdomain,1));
    FArrayBox* newphi = new FArrayBox(newp_size,1);
    newphi->setVal(0.0);

    FArrayBox* newsigma = new FArrayBox(BoxLib::grow(newdomain,1),1);
    FArrayBox* newresid  = new FArrayBox(newp_size,1);

    newsigma->setVal(0.);
    newresid->setVal(0.);
   
    FORT_COARSIG(sigma->dataPtr(),newsigma->dataPtr(),
                 ARLIM(lo_mg), ARLIM(hi_mg),
		 ARLIM(newdomain.smallEnd().getVect()),
		 ARLIM(newdomain.bigEnd().getVect()),
                 &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi);

    Real newhx = 2.0*hx;
    Real newhy = 2.0*hy;
    Next = new hgprojection_mg(newdomain, newphi,
                               new FArrayBox(newp_size,1),
                               newresid, newsigma, 
			       newhx, newhy);
  }
  next = Next;
}

// ************************************************************************
// ** destructor **
// ************************************************************************

hgprojection_mg::~hgprojection_mg()
{
  if (Next != NULL) {
    delete Next->phi;
    delete Next->source;
    delete Next->resid;
    delete Next->sigma;
    delete Next;
  }
    delete cgwork;
}

// ************************************************************************
// ** residual **
// ************************************************************************

Real hgprojection_mg::residual()
{
  Real rnorm;

  FArrayBox * dgphi = new FArrayBox(BoxLib::surroundingNodes(BoxLib::grow(domain,1)),1);
  FORT_RESIDUAL(resid->dataPtr(),phi->dataPtr(),source->dataPtr(),
		sigma->dataPtr(),dgphi->dataPtr(),
	        ARLIM(lo_mg),ARLIM(hi_mg),&hx,&hy,&rnorm,
                &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi);
  delete dgphi;
  return rnorm;
}

// ************************************************************************
// ** step **
// ************************************************************************

void hgprojection_mg::step(int nngsrb)
{
  int maxiter = 100;

  FArrayBox * temp = new FArrayBox(BoxLib::surroundingNodes(BoxLib::grow(domain,1)),1);

  if (cgwork != NULL) {

   temp->copy(*phi);
   phi->setVal(0.);

   Real resnorm  = 0.0;
  
   FORT_SOLVEHG(phi->dataPtr(), temp->dataPtr(), source->dataPtr(),
	        sigma->dataPtr(), cgwork->dataPtr(0),
                cgwork->dataPtr(1), cgwork->dataPtr(2), cgwork->dataPtr(3),
		resid->dataPtr(), ARLIM(lo_mg), ARLIM(hi_mg), &hx, &hy, 
                &bcx_lo, &bcx_hi, &bcy_lo, &bcy_hi, 
                &maxiter, &resnorm, &prob_norm);

  } else {
      FORT_RELAX(phi->dataPtr(),source->dataPtr(),sigma->dataPtr(),
  	         temp->dataPtr(),ARLIM(lo_mg),ARLIM(hi_mg),
  	         &hx,&hy,&bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&nngsrb);
  }
  delete temp;
}

// ************************************************************************
// ** Restrict **
// ************************************************************************

void hgprojection_mg::Restrict()
{
  FORT_RESTRICT(resid->dataPtr(), next->source->dataPtr(), 
                ARLIM(lo_mg),ARLIM(hi_mg),
		ARLIM(next->domain.smallEnd().getVect()),
		ARLIM(next->domain.bigEnd().getVect()),
                &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi);
}

// ************************************************************************
// ** interpolate **
// ************************************************************************

void hgprojection_mg::interpolate()
{

  FArrayBox * temp = new FArrayBox(BoxLib::surroundingNodes(BoxLib::grow(domain,1)),1);
  temp->setVal(0.);

  FORT_INTERP(temp->dataPtr(), next->phi->dataPtr(),
              ARLIM(lo_mg),ARLIM(hi_mg),
              ARLIM(next->domain.smallEnd().getVect()),
	      ARLIM(next->domain.bigEnd().getVect()));

  phi->plus(*temp,0,0,1);
  delete temp;
}
