/*
** (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],x[2]

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];
  _hz = dx[2];
}

// ************************************************************************
// ** 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));

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

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

  std::cout << "Viscous RHS is " << rhsnorm << std::endl;

  if (rhsnorm > tol) 
  {

    solver = new diffuser_mg(domain,temp,source,resid,sigma,
                             uinx_lo,uinx_hi,uiny_lo,uiny_hi,uinz_lo,uinz_hi,
		             _hx,_hy,_hz,0);

    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;
  delete uinz_lo;
  delete uinz_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)*(domain.length()[2]+2)];
  Real * uinx_hi = new Real[(domain.length()[1]+2)*(domain.length()[2]+2)];
  Real * uiny_lo = new Real[(domain.length()[0]+2)*(domain.length()[2]+2)];
  Real * uiny_hi = new Real[(domain.length()[0]+2)*(domain.length()[2]+2)];
  Real * uinz_lo = new Real[(domain.length()[0]+2)*(domain.length()[1]+2)];
  Real * uinz_hi = new Real[(domain.length()[0]+2)*(domain.length()[1]+2)];

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

  std::cout << "Diffusive RHS is " << rhsnorm << std::endl;

  if (rhsnorm > tol)
  {

    solver = new diffuser_mg(domain,temp,source,resid,sigma,
                             uinx_lo,uinx_hi,uiny_lo,uiny_hi,uinz_lo,uinz_hi,
		             _hx,_hy,_hz,0);

    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;
  delete uinz_lo;
  delete uinz_hi;

}

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

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

  uinx_lo = Uinx_lo;
  uinx_hi = Uinx_hi;
  uiny_lo = Uiny_lo;
  uiny_hi = Uiny_hi;
  uinz_lo = Uinz_lo;
  uinz_hi = Uinz_hi;
  sigma = Sigma;
  level = Level;

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

  const IntVect& len = domain.length();

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

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

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

    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 newhz = 2.0*_hz;

    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));

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

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

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

diffuser_mg::~diffuser_mg()

{
  if (Next != NULL) {
    delete Next->phi;
    delete Next->source;
    delete Next->resid;
    delete Next->uinx_lo;
    delete Next->uinx_hi;
    delete Next->uiny_lo;
    delete Next->uiny_hi;
    delete Next->uinz_lo;
    delete Next->uinz_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,uinz_lo,uinz_hi,
             ARLIM(lo_mg),ARLIM(hi_mg),&_hx,&_hy,&_hz,&rnorm,
             &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi,&level);
  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,uinz_lo,uinz_hi,
             ARLIM(lo_mg),ARLIM(hi_mg),&_hx,&_hy,&_hz,
             &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi,
             &level,&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));
}

// ************************************************************************
// ** 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));
}
