/*
** (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.
*/


//
// $Id: LevelAdvance.cpp,v 1.32 2001/09/06 21:18:07 lijewski Exp $
//

#include <FluxRegister.H>
#include <LevelAdvance.H>
#include <HyperCLaw.H>
#include <GODUNOV_F.H>
#include <ParallelDescriptor.H>
#include <integrator.fh>
#ifdef BL_USE_CHEM
#include <PROB_F.H>
#endif

enum { LeftFace, RightFace, Bottom, Top, Front, Back };
enum { interior = -1, OnPhysBndry, Buffered };

static
void
BoundaryStatus (const Box& grid,
                const Box& domain,
                int*       boundary)
{
    BL_ASSERT(boundary != 0);
    //
    // Determines whether any of the faces of an interior grid are touching
    // the physical boundary, are HYP_GROW away from the boundary, or
    // are > HYP_GROW away from the boundary
    //
    //      Inputs:
    //      const Box& grid     ->   the interior grid   
    //      const Box& domain   ->   the physical domain
    //
    //      Outputs:
    //      int* boundary <-        flags the status of each grid face
    //
    const int ilo_diff = grid.smallEnd(0) - domain.smallEnd(0);
    const int jlo_diff = grid.smallEnd(1) - domain.smallEnd(1);
    const int ihi_diff = domain.bigEnd(0) - grid.bigEnd(0);
    const int jhi_diff = domain.bigEnd(1) - grid.bigEnd(1);

    BL_ASSERT(ilo_diff >= 0);
    BL_ASSERT(jlo_diff >= 0);
    BL_ASSERT(ihi_diff >= 0);
    BL_ASSERT(jhi_diff >= 0);

#if (BL_SPACEDIM==3)
    const int klo_diff = grid.smallEnd(2) - domain.smallEnd(2);
    const int khi_diff = domain.bigEnd(2) - grid.bigEnd(2);
    BL_ASSERT(klo_diff >= 0);
    BL_ASSERT(khi_diff >= 0);
#endif

    switch (ilo_diff)
    {
    case 0:
        boundary[LeftFace] = OnPhysBndry; break;
    case HYP_GROW:
        boundary[LeftFace] = Buffered; break;
    default:
        boundary[LeftFace] = interior; break; 
    }
    switch (jlo_diff)
    {
    case 0:
        boundary[Bottom] = OnPhysBndry; break;
    case HYP_GROW:
        boundary[Bottom] = Buffered; break;
    default:
        boundary[Bottom] = interior; break; 
    }
    switch (ihi_diff)
    {
    case 0:
        boundary[RightFace] = OnPhysBndry; break;
    case HYP_GROW:
        boundary[RightFace] = Buffered; break;
    default:
        boundary[RightFace] = interior; break; 
    }
    switch (jhi_diff)
    {
    case 0:
        boundary[Top] = OnPhysBndry; break;
    case HYP_GROW:
        boundary[Top] = Buffered; break;
    default:
        boundary[Top] = interior; break; 
    }
#if (BL_SPACEDIM==3)
    switch (klo_diff)
    {
    case 0:
        boundary[Front] = OnPhysBndry; break;
    case HYP_GROW:
        boundary[Front] = Buffered; break;
    default:
        boundary[Front] = interior; break;
    }
    switch (khi_diff)
    {
    case 0:
        boundary[Back] = OnPhysBndry; break;
    case HYP_GROW:
        boundary[Back] = Buffered; break;
    default:
        boundary[Back] = interior; break;
    }
#endif /*BL_SPACEDIM==3*/
}

static
void
Flaten (FArrayBox& q,
        FArrayBox& flatn,
        FArrayBox& dp,
        FArrayBox& z,
        FArrayBox& chi,
        FArrayBox& p,
        FArrayBox& c,
        int        numCVar,
        FArrayBox& qq,
        FArrayBox& qcen,
        FArrayBox& qsgn,
        FArrayBox& dqlim,
        FArrayBox& dqf,
        int*       gBndry,
        int        sweep)
{
    switch (sweep)
    {
    case 0:
        FORT_FLATENX(BL_FAA(q),BL_FAA(flatn),BL_FAA(dp),
                     BL_FAA(z),BL_FAA(chi),BL_FAA(p),BL_FAA(c),&numCVar);

        FORT_SLOPEX(BL_FAA(q),BL_FAA(qq),BL_FAA(flatn),
                    BL_FAA(qcen),BL_FAA(qsgn),
                    BL_FAA(dqlim), BL_FAA(dqf),&numCVar,gBndry);
        break;
    case 1:
        FORT_FLATENY(BL_FAA(q),BL_FAA(flatn),BL_FAA(dp),
                     BL_FAA(z),BL_FAA(chi),BL_FAA(p),BL_FAA(c),&numCVar);

        FORT_SLOPEY(BL_FAA(q),BL_FAA(qq),BL_FAA(flatn),
                    BL_FAA(qcen),BL_FAA(qsgn),
                    BL_FAA(dqlim), BL_FAA(dqf),&numCVar,gBndry);
        break;
#if (BL_SPACEDIM==3)
    case 2:
        FORT_FLATENZ(BL_FAA(q),BL_FAA(flatn),BL_FAA(dp),
                     BL_FAA(z),BL_FAA(chi),BL_FAA(p),BL_FAA(c),&numCVar);

        FORT_SLOPEZ(BL_FAA(q),BL_FAA(qq),BL_FAA(flatn),
                    BL_FAA(qcen),BL_FAA(qsgn),
                    BL_FAA(dqlim), BL_FAA(dqf),&numCVar,gBndry);
        break;
#endif /*BL_SPACEDIM==3*/
    default:
        BoxLib::Error("Invalid value of sweep");
    }
}

static
void
Trace (FArrayBox& q,
       FArrayBox& qq,
       FArrayBox& c,
       FArrayBox& enth,
       FArrayBox& qbarl,
       FArrayBox& qbarr,
       FArrayBox& dloga,
       FArrayBox& courno,
       Box&       qqbx,
       const Real* dx,
       Real        dt,
       int*        bc,
       int*        gBndry,
       int         numCVar,
       int         sweep)
{
    switch (sweep)
    {
    case 0:
        FORT_XTRACE(BL_FAA(q), BL_FAA(qq), BL_FAA(c), BL_FAA(enth),
                    BL_FAA(qbarl), BL_FAA(qbarr), BL_FAA(dloga),BL_FAA(courno),
                    BL_BOXARG(qqbx), dx, &dt, bc, gBndry, &numCVar);
        break;
    case 1:
        FORT_YTRACE(BL_FAA(q), BL_FAA(qq), BL_FAA(c), BL_FAA(enth),
                    BL_FAA(qbarl), BL_FAA(qbarr), BL_FAA(dloga),BL_FAA(courno),
                    BL_BOXARG(qqbx), dx, &dt, bc, gBndry, &numCVar);
        break;
#if (BL_SPACEDIM==3)
    case 2:
        FORT_ZTRACE(BL_FAA(q), BL_FAA(qq), BL_FAA(c), BL_FAA(enth),
                    BL_FAA(qbarl), BL_FAA(qbarr), BL_FAA(dloga),BL_FAA(courno),
                    BL_BOXARG(qqbx), dx, &dt, bc, gBndry, &numCVar);
        break;
#endif /*BL_SPACEDIM==3*/
    default:
        BoxLib::Error("Invalid value of sweep");
    }
}

static
void
Update (FArrayBox&  state,
        FArrayBox&  flux,
        FArrayBox&  pgdnv,
        FArrayBox&  divu,
        FArrayBox&  area,
        FArrayBox&  vol,
        const Box&  bx,
        const Real* dx,
        Real        dt,
        int         numComp,
        int         sweep,
        int*        bc,
        int*        gBndry)
{
    switch (sweep)
    {
    case 0:
        FORT_UPDATEX(BL_FAA(state), BL_FAA(flux), BL_FAA(pgdnv), BL_FAA(divu),
                     BL_FAA(area), BL_FAA(vol), BL_BOXARG(bx), dx, &dt,
                     &numComp, &sweep, bc, gBndry);
        break;
    case 1:
        FORT_UPDATEY(BL_FAA(state), BL_FAA(flux), BL_FAA(pgdnv), BL_FAA(divu),
                     BL_FAA(area), BL_FAA(vol), BL_BOXARG(bx), dx, &dt,
                     &numComp, &sweep, bc, gBndry);
        break;
#if (BL_SPACEDIM==3)
    case 2:
        FORT_UPDATEZ(BL_FAA(state), BL_FAA(flux), BL_FAA(pgdnv), BL_FAA(divu),
                     BL_FAA(area), BL_FAA(vol), BL_BOXARG(bx), dx, &dt,
                     &numComp, &sweep, bc, gBndry);
        break;
#endif /*BL_SPACEDIM==3*/
    default:
        BoxLib::Error("Invalid value of sweep");
    }
}

static
Real
Sweep (FArrayBox&  state,
       FArrayBox&  flux,
       FArrayBox&  divu,
       const Box&  bx,
       int*        bc,
       int*        gBndry,
       int         numComp,
       int         numCVar,
       Real        dt,
       Real        time,
       const Real* dx,
       FArrayBox&  vol,
       FArrayBox&  area,
       FArrayBox&  dloga,
       int         sweep)
{
    Box bx_g2(bx);
    Box bx_g3(bx);
    Box bx_g4(bx);
    Box bx_g5(bx);

    bx_g2.grow(sweep,2);
    bx_g3.grow(sweep,3);
    bx_g4.grow(sweep,4);
    bx_g5.grow(sweep,5);

    Box flxbx(flux.box());
    Box qbarbx(flxbx);

    qbarbx.grow(sweep,1);

#ifdef BL_ADD_GRAVITY
    if (sweep == 2)
    {
        //
        // add gravity
        int ng(4);
        FORT_GRAVITY_UPDATE(BL_FAA(state), BL_BOXARG(bx), &dt, &numComp, bc, &ng);
    }
#endif
    //
    // Derive primitive variables.
    //
    FArrayBox q(bx_g4,numCVar);
    FArrayBox gamc(bx_g4,1);
    FArrayBox p(bx_g4,1);
    FArrayBox c(bx_g4,1);
    FArrayBox csml(bx_g4,1);

    FORT_PRIMITIVES(BL_FAA(state), BL_FAA(q), BL_FAA(p), BL_FAA(c),
                    BL_FAA(csml), BL_FAA(gamc), BL_BOXARG(bx_g4), bc,
                    &numComp, &numCVar, &sweep);
    //
    // Fourth order slope calculation for x-sweep.
    //
    Box qqbx(bx);
    qqbx.grow(sweep,1);
    FArrayBox qq(qqbx,numCVar);
    FArrayBox flatn(qqbx,1);
    {
        //
        // Define temp space for 4th order slopes.
        //
        FArrayBox qcen(bx_g3,1);
        FArrayBox qsgn(bx_g3,1);
        FArrayBox dqlim(bx_g3,1);
        FArrayBox dqf(bx_g3,1);
        FArrayBox dp(bx_g2,1);
        FArrayBox z(bx_g2,1);
        FArrayBox chi(bx_g2,1);

        Flaten(q, flatn, dp, z, chi, p, c, numCVar,
               qq, qcen, qsgn, dqlim, dqf, gBndry, sweep);
    }

    Real cfl = 0;
    FArrayBox qbarl(qbarbx,numCVar);
    FArrayBox qbarr(qbarbx,numCVar);
    {
        //
        // Allocate space for temp arrays in tracing.
        //
        FArrayBox enth(qqbx,1);
        FArrayBox courno(qqbx,1);

        Trace(q,qq,c,enth,qbarl,qbarr,dloga,courno,qqbx,
              dx,dt,bc,gBndry,numCVar,sweep);

        cfl = courno.max();
    }
    //
    // Allocate space for arrays in Riemann solver.
    //
    FArrayBox rgdnv(flxbx,1);
    FArrayBox ugdnv(flxbx,1);
    FArrayBox pgdnv(flxbx,1);
    FArrayBox egdnv(flxbx,1);
    FArrayBox utgdnv(flxbx,BL_SPACEDIM-1);
#if (NADV>0)
    int ntrcomp = 1;
#if BL_USE_CHEM
    ntrcomp += HyperCLaw::getChemDriver().numSpecies();
#endif
    FArrayBox advgdnv(flxbx,ntrcomp);
#endif
    FArrayBox ustar(flxbx,1);
    int density = 0;
#if (BL_SPACEDIM==2)
    int energy = 4;
    int tracer = 5;
#elif (BL_SPACEDIM==3)
    int energy = 5;
    int tracer = 6;
#endif

    FORT_RIEMANN(BL_FAA(qbarl),
                 BL_FAA(qbarr),
                 BL_FAA(flux),
                 BL_FAAN(q,density),
                 BL_FAAN(q,energy),
                 BL_FAAN(q,tracer),
                 BL_FAA(c),
                 BL_FAA(csml),
                 BL_FAA(gamc),
                 BL_FAA(rgdnv),
                 BL_FAA(ugdnv),
                 BL_FAA(pgdnv),
                 BL_FAA(egdnv),
                 BL_FAA(utgdnv),
#if (NADV>0)
                 BL_FAA(advgdnv),
#endif
                 BL_FAA(ustar), BL_BOXARG(qqbx), &sweep, &numCVar, &numComp);
    //
    // Conservative update.
    //
    Update(state,flux,pgdnv,divu,area,vol,bx,dx,dt,numComp,sweep,bc,gBndry);

#ifdef BL_USE_CHEM
# ifdef BL_USE_MIB
    //
    // Do chemistry step, and set temperature.
    //
    FORT_MIB_CHEM(bx.loVect(),bx.hiVect(),
                  state.dataPtr(),ARLIM(state.loVect()), ARLIM(state.hiVect()));
# endif
#endif

    return cfl;
}

Real
LevelAdvance (BoxArray&     grids,       //  => BoxArray where state is defined
              MultiFab&     levelState,  // <=  state on bx[level]
              AmrLevel&     levelData,   //  => level Data 
              FluxRegister& finer,       //  => Flux register for next finer 
                                         //     level; initialized here.
                                         //     &finer = 0 if finest level.
              FluxRegister& current,     // <=> Flux level for current level;
                                         //     incremented here.
                                         //     &current = 0 if level = 0.
              int           State_Type,  //  => 
              int           strtComp,    //  => beginning component 
              int           numStateVar, //  => number of state components.
              int           do_reflux,   //  => refluxing flag.
              Real          dt,          //  => time step.
              Real          time,        //  => time
              const Real*   dx)          //  => dx[BL_SPACEDIM] = mesh spacing.
{
    Real D_DECL(cflx, cfly, cflz);
    Real      cfl     = 0;
    const int numCVar = numStateVar + 1;
    const int nstep   = levelData.nStep();
    bool      xySweep = 1 - nstep % 2;
    int       i;

    Geometry g = levelData.Geom();
    MultiFab levelVolume;
    g.GetVolume(levelVolume,grids,HYP_GROW);

#if (BL_SPACEDIM==2)
    MultiFab levelDLogA[BL_SPACEDIM];

    for (i = 0; i < BL_SPACEDIM ; i++)
        g.GetDLogA(levelDLogA[i],grids,i,HYP_GROW);
#endif
        
    MultiFab levelArea[BL_SPACEDIM];
    for (i = 0; i < BL_SPACEDIM ; i++)
        g.GetFaceArea(levelArea[i],grids,i,HYP_GROW);

    const int boxGrow = 5;

    FArrayBox divu, volume, xflux, yflux, zflux;;
    FArrayBox dloga[BL_SPACEDIM], area[BL_SPACEDIM];

    for (FillPatchIterator fpi(levelData, levelState, boxGrow,
                               time, State_Type, strtComp, numStateVar);
         fpi.isValid();
         ++fpi)
    {
        int mfiindex = fpi.index();
        //
        // Determine whether the grid is an interior grid, touching the
        // boundary, or HYP_GROW cells away from the boundary.
        //
        int gridBoundary[2*BL_SPACEDIM];

        Box bx(fpi.UngrownBox());

        BoundaryStatus(bx, levelData.Domain(), gridBoundary);
        //
        // Look at each of the faces of this grid and do the right thing.
        //
        for (OrientationIter face; face; ++face)
            if (gridBoundary[face()] == Buffered)
                bx.grow(face(), HYP_GROW);

        Box bx_g5(BoxLib::grow(bx,5));
        Box bx_g4(BoxLib::grow(bx,4));
        Box bx_g3(BoxLib::grow(bx,3));
        Box bx_g2(BoxLib::grow(bx,2));
        //
        // Create FAB for extended grid values (including boundaries) and fill.
        //
        FArrayBox& state = fpi();
        Array<int> bc = levelData.getBCArray(State_Type, mfiindex,
                                             strtComp, numStateVar);
        //
        // Calculate node-centered divergence for artificial viscosity.
        //
        divu.resize(BoxLib::surroundingNodes(bx_g4));

        FORT_DIVUNODE(BL_FAA(state),BL_FAA(divu),dx,&numStateVar,gridBoundary);

        volume.resize(bx_g4,1);
        volume.copy(levelVolume[fpi]);

        for (i = 0; i < BL_SPACEDIM ; i++)
        {
            dloga[i].resize(bx_g4);
#if (BL_SPACEDIM==2)
            dloga[i].copy(levelDLogA[i][mfiindex]);
#else
            //
            // Hardwired for 3d Cartesian coords.
            //
            dloga[i].setVal(0.0);
#endif
        }
        for (i = 0; i < BL_SPACEDIM ; i++)
        {
            area[i].resize(BoxLib::surroundingNodes(bx_g4,i));
            area[i].copy(levelArea[i][mfiindex]);
        }
        //
        // Allocate fabs for fluxes.
        //
        Box fbx0 = BoxLib::surroundingNodes(bx,0); 
        xflux.resize(fbx0,numStateVar);
        Box fbx1 = BoxLib::surroundingNodes(bx,1);
        yflux.resize(fbx1,numStateVar);
#if (BL_SPACEDIM == 3)
        Box fbx2 = BoxLib::surroundingNodes(bx,2);
        zflux.resize(fbx2,numStateVar);
#endif
        if (xySweep)
        {
            Box fbx00(fbx0);
            fbx00.grow(1,4);
#if (BL_SPACEDIM == 3)
            fbx00.grow(2,4);
            Box fbx01(fbx1);
            fbx01.grow(2,4);
            yflux.resize(fbx01,numStateVar);
#endif
            xflux.resize(fbx00,numStateVar);
       
            Box bx_0(bx);
            Box bx_1(bx);
            Box bx_2(bx);
            bx_0.grow(1,4);
#if (BL_SPACEDIM == 3)
            bx_0.grow(2,4);
            bx_1.grow(2,4);
#endif
            D_TERM(cflx = Sweep(state, xflux, divu, bx_0, bc.dataPtr(), gridBoundary,
                                numStateVar, numCVar, dt, time, dx, volume,
                                area[0], dloga[0], 0);,
                   cfly = Sweep(state, yflux, divu, bx_1, bc.dataPtr(), gridBoundary,
                                numStateVar, numCVar, dt, time, dx, volume,
                                area[1], dloga[1], 1);,
                   cflz = Sweep(state, zflux, divu, bx_2, bc.dataPtr(), gridBoundary,
                                numStateVar, numCVar, dt, time, dx, volume,
                                area[2], dloga[2], 2););
        }
        else
        {
#if (BL_SPACEDIM == 3)
            Box fbx12(fbx2);
            fbx12.grow(0,4);
            fbx12.grow(1,4);
            zflux.resize(fbx12,numStateVar);

            Box fbx11(fbx1);
            fbx11.grow(0,4);
            yflux.resize(fbx11,numStateVar);

            Box bx_0(bx);
            Box bx_1(bx);
            Box bx_2(bx);
            bx_2.grow(0,4);
            bx_2.grow(1,4);
            bx_1.grow(0,4);

            cflz = Sweep(state, zflux, divu, bx_2, bc.dataPtr(), gridBoundary,
                         numStateVar, numCVar, dt, time, dx, volume,
                         area[2], dloga[2], 2);
            cfly = Sweep(state, yflux, divu, bx_1, bc.dataPtr(), gridBoundary,
                         numStateVar, numCVar, dt, time, dx, volume,
                         area[1], dloga[1], 1);

#elif (BL_SPACEDIM == 2)
            Box fbx10(fbx1);
            fbx10.grow(0,4);
            yflux.resize(fbx10,numStateVar);

            Box bx_0(bx);
            Box bx_1(bx);
            bx_1.grow(0,4);

            cfly = Sweep(state, yflux, divu, bx_1, bc.dataPtr(), gridBoundary,
                         numStateVar, numCVar, dt, time, dx, volume,
                         area[1], dloga[1], 1);
#endif
            cflx = Sweep(state, xflux, divu, bx_0, bc.dataPtr(), gridBoundary,
                         numStateVar, numCVar, dt, time, dx, volume,
                         area[0], dloga[0], 0);
        }
        //
        // Copy local state back to list.
        //
        levelState[mfiindex].copy(state,grids[mfiindex],0,grids[mfiindex],0,numStateVar);

        D_EXPR(cfl=std::max(cflx,cfl), cfl=std::max(cfly,cfl), cfl=std::max(cflz,cfl));

        if (do_reflux)
        {
            //
            // Fluxes expected to be in extensive form,
            // i.e. multiplied by the area of the edge.
            //
            if (&finer)
            {
                D_TERM(finer.CrseInit(xflux,xflux.box(),0,0,strtComp,numStateVar,-1);,
                       finer.CrseInit(yflux,yflux.box(),1,0,strtComp,numStateVar,-1);,
                       finer.CrseInit(zflux,zflux.box(),2,0,strtComp,numStateVar,-1););
            }
            if (&current)
            {
                D_TERM(current.FineAdd(xflux,0,mfiindex,0,strtComp,numStateVar,1);,
                       current.FineAdd(yflux,1,mfiindex,0,strtComp,numStateVar,1);,
                       current.FineAdd(zflux,2,mfiindex,0,strtComp,numStateVar,1););
            }
        }
    }

    if (&finer)
        finer.CrseInitFinish();

    ParallelDescriptor::ReduceRealMax(cfl);

#if 0
#if BL_USE_CHEM
    //
    // Do chemistry step, and set temperature
    //
    for (MultiFabIterator mfi(levelState); mfi.isValid(); ++mfi)
    {
        const Box& box = mfi.validbox();
        FArrayBox& state = mfi();
        FORT_MIB_CHEM(box.loVect(),box.hiVect(),
                      state.dataPtr(),ARLIM(state.loVect()), ARLIM(state.hiVect()));
    }
#endif
#endif

    return dt/cfl;
}
