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

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

int mac_projector::numSmoothCoarsen = 2;
int mac_projector::numSmoothRefine = 2;


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

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

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

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

  _hx = dx[0];
  _hy = dx[1];
  _hz = dx[2];
}

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

mac_projector::~mac_projector()
{
}

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

void mac_projector::project(Real * uadv, Real * vadv, Real * wadv, 
                            Real *rho, FArrayBox& divu_src)

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

  FArrayBox *source = new FArrayBox(domain,1);
  FArrayBox *resid  = new FArrayBox(domain,1);

  Box xedges(domain);
  int idir   =  0;
  int ngrow =  1;
  xedges.growHi(idir,ngrow);
  FArrayBox *sigmax = new FArrayBox(xedges,1);

  Box yedges(domain);
  idir   =  1;
  ngrow =  1;
  yedges.growHi(idir,ngrow);
  FArrayBox *sigmay = new FArrayBox(yedges,1);

  Box zedges(domain);
  idir   =  2;
  ngrow =  1;
  zedges.growHi(idir,ngrow);
  FArrayBox *sigmaz = new FArrayBox(zedges,1);

  FORT_INITSIGMA(sigmax->dataPtr(),sigmay->dataPtr(),sigmaz->dataPtr(),rho,
		 ARLIM(lo),ARLIM(hi),
                 &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi);

  Real rhsnorm;
  FORT_RHSMAC(uadv,vadv,wadv,divu_src.dataPtr(),source->dataPtr(),
              ARLIM(lo),ARLIM(hi),&_hx,&_hy,&_hz,&rhsnorm);

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

  if (rhsnorm > tol) {

    macprojection_mg *solver =
      new macprojection_mg(domain,phi,source,resid,sigmax,sigmay,sigmaz,
                           _hx,_hy,_hz);
    solver->solve(tol*rhsnorm,rhsnorm,numSmoothCoarsen,numSmoothRefine);
    delete solver;

    Real * gradpx = new Real[(domain.length()[0]+1)* 
                              domain.length()[1]   *
                              domain.length()[2]    ];
    Real * gradpy = new Real[ domain.length()[0]   *
                             (domain.length()[1]+1)*
                              domain.length()[2]    ];
    Real * gradpz = new Real[ domain.length()[0]   *
                              domain.length()[1]   *
                             (domain.length()[2]+1)];

    FORT_GRADMAC(gradpx,gradpy,gradpz,phi->dataPtr(),ARLIM(lo),ARLIM(hi),
                 &_hx,&_hy,&_hz,
		 &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi);

    FORT_PROJUMAC(uadv, vadv, wadv, gradpx, gradpy, gradpz, rho, 
                  ARLIM(lo), ARLIM(hi));

    delete gradpx;
    delete gradpy;
    delete gradpz;
  }

  delete phi;
  delete source;
  delete resid;
  delete sigmax;
  delete sigmay;
  delete sigmaz;
}

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

macprojection_mg::macprojection_mg(const Box& Domain,
                                              FArrayBox *Phi,
                                              FArrayBox *Source,
                                              FArrayBox *Resid,
                                              FArrayBox *Sigmax,
                                              FArrayBox *Sigmay,
                                              FArrayBox *Sigmaz,
                                              Real Hx, Real Hy, Real Hz) :
       multigrid(Domain, Phi, Source, Resid, Hx, Hy, Hz)
{
  sigmax = Sigmax;
  sigmay = Sigmay;
  sigmaz = Sigmaz;

  // create next (coarser) level 

  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[2]&1) != 0 || 
        len[0] < 4 || len[1] < 4 || len[2] < 4) {

    Next = NULL;  

    cgwork = new FArrayBox(domain,4);
    FORT_MKSUMMAC(sigmax->dataPtr(), sigmay->dataPtr(), sigmaz->dataPtr(),
                  cgwork->dataPtr(0),
                  ARLIM(lo_mg), ARLIM(hi_mg), &_hx, &_hy, &_hz,
                  &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi);

  } else {

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

    Real newhx = 2.0*_hx;
    Real newhy = 2.0*_hy;
    Real newhz = 2.0*_hz;

    FArrayBox* newresid  = new FArrayBox(BoxLib::grow(newdomain,1),1);
    FArrayBox* newsigmax = new FArrayBox(BoxLib::grow(newdomain,1),1);
    FArrayBox* newsigmay = new FArrayBox(BoxLib::grow(newdomain,1),1);
    FArrayBox* newsigmaz = new FArrayBox(BoxLib::grow(newdomain,1),1);

    const int* dom_lo = domain.smallEnd().getVect();
    const int* dom_hi = domain.bigEnd().getVect();

    const int* new_lo = newdomain.smallEnd().getVect();
    const int* new_hi = newdomain.bigEnd().getVect();

    FORT_COARSIGMA(sigmax->dataPtr(),sigmay->dataPtr(),sigmaz->dataPtr(),
                   newsigmax->dataPtr(),newsigmay->dataPtr(),
                   newsigmaz->dataPtr(),
                   ARLIM(dom_lo), ARLIM(dom_hi),
                   ARLIM(new_lo), ARLIM(new_hi));

    Next = new macprojection_mg(newdomain,
                                newphi,
                                new FArrayBox(newdomain,1),
                                newresid, 
                                newsigmax, newsigmay, newsigmaz,
			        newhx, newhy, newhz);
  }
  next = Next;
}

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

macprojection_mg::~macprojection_mg()

{
  if (Next != NULL) {
    delete Next->phi;
    delete Next->source;
    delete Next->resid;
    delete Next->sigmax;
    delete Next->sigmay;
    delete Next->sigmaz;
    delete Next;
  }
    delete cgwork;
}

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

Real macprojection_mg::residual()
{
  Real rnorm;
  FORT_RESIDUAL(resid->dataPtr(),phi->dataPtr(),source->dataPtr(),
	        sigmax->dataPtr(),sigmay->dataPtr(),sigmaz->dataPtr(),
                ARLIM(lo_mg),ARLIM(hi_mg),&_hx,&_hy,&_hz,&rnorm,
                &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi);
  return rnorm;
}

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

void macprojection_mg::step(int nngsrb)
{

  Real rnorm = 1.e10;

  if (cgwork != NULL) {

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

   FORT_SOLVEMAC(phi->dataPtr(), dest0->dataPtr(), source->dataPtr(),
                 sigmax->dataPtr(), sigmay->dataPtr(), sigmaz->dataPtr(),
                 cgwork->dataPtr(0), cgwork->dataPtr(1), cgwork->dataPtr(2), 
                 cgwork->dataPtr(3), resid->dataPtr(), 
                 ARLIM(lo_mg), ARLIM(hi_mg), &_hx, &_hy, &_hz,  
                 &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi,
                 &rnorm,&prob_norm);

   delete dest0;

  } else {
    FORT_GSRB(phi->dataPtr(),source->dataPtr(),
              sigmax->dataPtr(),sigmay->dataPtr(), sigmaz->dataPtr(),
              ARLIM(lo_mg),ARLIM(hi_mg),&_hx,&_hy,&_hz,
              &bcx_lo,&bcx_hi,&bcy_lo,&bcy_hi,&bcz_lo,&bcz_hi,&nngsrb);
  }
}

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

void macprojection_mg::Restrict()
{
  const int * loc = next->domain.smallEnd().getVect();
  const int * hic = next->domain.bigEnd().getVect();

  FORT_RESTRICT(resid->dataPtr(), next->source->dataPtr(),
		ARLIM(lo_mg),ARLIM(hi_mg),ARLIM(loc),ARLIM(hic));
}

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

void macprojection_mg::interpolate()
{
  const int * loc = next->domain.smallEnd().getVect();
  const int * hic = next->domain.bigEnd().getVect();

  FORT_INTERPOLATE(phi->dataPtr(), next->phi->dataPtr(),
		   ARLIM(lo_mg),ARLIM(hi_mg),ARLIM(loc),ARLIM(hic));
}
