/*
** (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 <diffuser.H>
#include <globals.H>
#include <VISC_F.H>
#include <ParmParse.H>

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

int diffuser::numSmoothCoarsen = 2;
int diffuser::numSmoothRefine = 2;

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

diffuser::diffuser(const Box& Domain, Real * dx)  :
            domain(Domain)
{
  ParmParse pp("diff");
  pp.get("tol",tol);

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

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

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

diffuser::~diffuser()
{
}

// ************************************************************************
// ** solveVel **
// ************************************************************************

void diffuser::solveVel(FArrayBox * staten, 
                        FArrayBox * temp,
                        Real * rho, 
                        Real mu,
                        int n)
{

  const int *  lo = domain.smallEnd().getVect();
  const int *  hi = domain.bigEnd().getVect();

  Real rhsnorm;

  diffuser_mg *solver;

  FArrayBox *source = new FArrayBox(domain,1);
  FArrayBox *sigma  = new FArrayBox(BoxLib::grow(domain,1),1);
  FArrayBox *resid  = new FArrayBox(domain,1);

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

  Real * uinx_lo = new Real[domain.length()[1]+2];
  Real * uinx_hi = new Real[domain.length()[1]+2];
  Real * uiny_lo = new Real[domain.length()[0]+2];
  Real * uiny_hi = new Real[domain.length()[0]+2];

  FORT_RHSNORM(staten->dataPtr(n),source->dataPtr(), 
	       ARLIM(lo),ARLIM(hi),&rhsnorm,uinx_lo,uinx_hi,uiny_lo,uiny_hi);

  std::cout << "Viscous RHS is " << rhsnorm << std::endl;
  rhsnorm = (rhsnorm > 1.0) ? rhsnorm : 1.0;

  solver = new diffuser_mg(domain,temp,source,resid,sigma,x,xhalf,
		           uinx_lo,uinx_hi,uiny_lo,uiny_hi,hx,hy,0,n);

  solver->solve(tol*rhsnorm,rhsnorm,numSmoothCoarsen,numSmoothRefine);

  delete solver;

  delete source;
  delete resid;
  delete sigma;
  delete uinx_lo;
  delete uinx_hi;
  delete uiny_lo;
  delete uiny_hi;

}

// ************************************************************************
// ** solveScal **
// ************************************************************************

void diffuser::solveScal(FArrayBox * staten, 
                         FArrayBox * temp,
                         Real mu,
                         int n)
{

  const int *  lo = domain.smallEnd().getVect();
  const int *  hi = domain.bigEnd().getVect();

  Real rhsnorm;

  diffuser_mg *solver;

  FArrayBox *source = new FArrayBox(domain,1);
  FArrayBox *sigma  = new FArrayBox(BoxLib::grow(domain,1),1);
  FArrayBox *resid  = new FArrayBox(domain,1);

  sigma->setVal(mu);

  Real * uinx_lo = new Real[domain.length()[1]+2];
  Real * uinx_hi = new Real[domain.length()[1]+2];
  Real * uiny_lo = new Real[domain.length()[0]+2];
  Real * uiny_hi = new Real[domain.length()[0]+2];

  FORT_RHSNORM(staten->dataPtr(n),source->dataPtr(), 
	       ARLIM(lo),ARLIM(hi),&rhsnorm,
               uinx_lo,uinx_hi,uiny_lo,uiny_hi);

  std::cout << "Diffusive RHS is " << rhsnorm << std::endl;
  rhsnorm = (rhsnorm > 1.0) ? rhsnorm : 1.0;

  solver = new diffuser_mg(domain,temp,source,resid,sigma,x,xhalf,
		           uinx_lo,uinx_hi,uiny_lo,uiny_hi,hx,hy,0,n);

  solver->solve(tol*rhsnorm,rhsnorm,numSmoothCoarsen,numSmoothRefine);

  delete solver;

  delete source;
  delete resid;
  delete sigma;
  delete uinx_lo;
  delete uiny_lo;
  delete uinx_hi;
  delete uiny_hi;

}

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

diffuser_mg::diffuser_mg(const Box & Domain,
                         FArrayBox * Phi,
                         FArrayBox * Source,
                         FArrayBox * Resid,
                         FArrayBox * Sigma,
                         Real * X, 
                         Real * Xhalf,
                         Real * Uinx_lo, 
                         Real * Uinx_hi, 
                         Real * Uiny_lo,
                         Real * Uiny_hi,
                         Real Hx, 
                         Real Hy, 
			 int Level, 
                         int Idir) :
       multigrid(Domain, Phi, Source, Resid, Hx, Hy)
{

  x_mg = X;
  xhalf_mg = Xhalf;
  uinx_lo = Uinx_lo;
  uinx_hi = Uinx_hi;
  uiny_lo = Uiny_lo;
  uiny_hi = Uiny_hi;
  sigma = Sigma;
  level = Level;
  idir = Idir;

  // create next (coarser) level and set overrelaxation parameter

  const IntVect& len = domain.length();

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

    cgwork = new FArrayBox(domain,3);
    Next = NULL;  

  } else if (len[0] < 8 || len[1] < 8 ) {

    cgwork = new FArrayBox(domain,3);
    Next = NULL;  

  } else {

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

    const int * loc_mg = newdomain.smallEnd().getVect();
    const int * hic_mg = newdomain.bigEnd().getVect();

    Real newhx = 2.0*hx;
    Real newhy = 2.0*hy;

    Real* newx = new Real[newdomain.length()[0]+2];
    Real* newxhalf = new Real[newdomain.length()[0]+2];
    FORT_XINIT(newx, newxhalf, &newhx, ARLIM(loc_mg), ARLIM(hic_mg), &irz);

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

    FORT_COARSIGV(sigma->dataPtr(),newsigma->dataPtr(),
                  ARLIM(lo_mg),ARLIM(hi_mg),ARLIM(loc_mg),ARLIM(hic_mg),x);

    Real * newuinx_lo = new Real[newdomain.length()[1]+2];
    Real * newuinx_hi = new Real[newdomain.length()[1]+2];
    Real * newuiny_lo = new Real[newdomain.length()[0]+2];
    Real * newuiny_hi = new Real[newdomain.length()[0]+2];

    Next = new diffuser_mg(newdomain,
                             newphi, newsource,
                             newresid, newsigma,
			     newx, newxhalf, 
                             newuinx_lo, newuinx_hi,
                             newuiny_lo, newuiny_hi,
		             newhx, newhy, level-1,idir);
  }
  next = Next;
}

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

diffuser_mg::~diffuser_mg()

{
  if (Next != NULL) {
    delete Next->phi;
    delete Next->source;
    delete Next->resid;
    delete Next->x_mg;
    delete Next->xhalf_mg;
    delete Next->uinx_lo;
    delete Next->uinx_hi;
    delete Next->uiny_lo;
    delete Next->uiny_hi;
    delete Next->sigma;
    delete Next;
  }
    delete cgwork;
}

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

Real diffuser_mg::residual()
{
  Real rnorm;
  FORT_RESID(resid->dataPtr(),phi->dataPtr(),source->dataPtr(),
	     sigma->dataPtr(),uinx_lo,uinx_hi,uiny_lo,uiny_hi,
             x_mg, xhalf_mg,
             ARLIM(lo_mg),ARLIM(hi_mg),&hx,&hy,
             &rnorm,&bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&level,&irz,&idir);
  return rnorm;
}

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

void diffuser_mg::step(int nngsrb)
{
    FORT_GSRBV(phi->dataPtr(),source->dataPtr(), sigma->dataPtr(),
               uinx_lo,uinx_hi,uiny_lo,uiny_hi,x_mg,xhalf_mg,
               ARLIM(lo_mg),ARLIM(hi_mg),&hx,&hy,
               &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&level,&irz,&idir,&nngsrb);
}

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

void diffuser_mg::Restrict()
{

  const int * loc_mg = next->domain.smallEnd().getVect();
  const int * hic_mg = next->domain.bigEnd().getVect();

  FORT_RESTRICT(resid->dataPtr(), next->source->dataPtr(),
		ARLIM(lo_mg),ARLIM(hi_mg),
		ARLIM(loc_mg),ARLIM(hic_mg),x_mg);
}

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

void diffuser_mg::interpolate()
{

  const int * loc_mg = next->domain.smallEnd().getVect();
  const int * hic_mg = next->domain.bigEnd().getVect();

  FORT_INTERPOLATE(phi->dataPtr(), next->phi->dataPtr(),
		   ARLIM(lo_mg),ARLIM(hi_mg),
		   ARLIM(loc_mg),ARLIM(hic_mg));
}
