!> Module containing all the time stepping schemes
module mod_advance

  implicit none
  private

  !> Whether to conserve fluxes at the current sub-step
  logical :: fix_conserve_at_step = .true.

  public :: advance
  public :: process
  public :: process_advanced

contains

  !> Advance all the grids over one time step, including all sources
  subroutine advance(iit)
    use mod_global_parameters
    use mod_particles, only: handle_particles
    use mod_source, only: add_split_source

    integer, intent(in) :: iit

    integer :: iigrid, igrid, idimsplit
    
    ! split source addition
    call add_split_source(prior=.true.)

    if (dimsplit) then
       if ((iit/2)*2==iit .or. typedimsplit=='xy') then
          ! do the sweeps in order of increasing idim,
          do idimsplit=1,ndim
             call advect(idimsplit,idimsplit)
          end do
       else
          ! If the parity of "iit" is odd and typedimsplit=xyyx,
          ! do sweeps backwards
          do idimsplit=ndim,1,-1
             call advect(idimsplit,idimsplit)
          end do
       end if
    else
       ! Add fluxes from all directions at once
       call advect(1,ndim)
    end if

    ! split source addition
    call add_split_source(prior=.false.)

    if(use_particles) call handle_particles

    ! do iigrid=1,igridstail; igrid=igrids(iigrid);
    !    
    ! end do
    
  end subroutine advance

  !> Advance all grids over one time step, but without taking dimensional
  !> splitting or split source terms into account
  subroutine advect(idimmin,idimmax)
    use mod_global_parameters
    use mod_fix_conserve
    use mod_ghostcells_update
    use mod_physics, only: phys_req_diagonal
    use mod_comm_lib, only: mpistop
    use mod_physicaldata

    integer, intent(in) :: idimmin,idimmax
    integer             :: iigrid, igrid, ix1,ix2,ix3, iw

    call init_comm_fix_conserve(idimmin,idimmax,nwflux)
    fix_conserve_at_step = time_advance .and. levmax>levmin
    
    ! copy w instead of wold because of potential use of dimsplit or sourcesplit
     
    do iigrid=1,igridstail; igrid=igrids(iigrid);
       
       do iw = 1, nw
          do ix3 = ixGlo3, ixGhi3 
             do ix2 = ixGlo2, ixGhi2 
                do ix1 = ixGlo1, ixGhi1 
                   bg(2)%w(ix1,ix2,ix3,iw,igrid) = bg(1)%w(ix1,ix2,ix3,iw,igrid)
                end do
             end do
          end do
       end do
    end do
    
    if(stagger_grid) then
        
       do iigrid=1,igridstail; igrid=igrids(iigrid);
          
          do iw = 1, nws
             do ix3 = ps(igrid)%ixGsmin3, ps(igrid)%ixGsmax3 
                do ix2 = ps(igrid)%ixGsmin2, ps(igrid)%ixGsmax2 
                   do ix1 = ps(igrid)%ixGsmin1, ps(igrid)%ixGsmax1 
                      ps1(igrid)%ws(ix1,ix2,ix3,iw) = ps(igrid)%ws(ix1,ix2,ix3,iw)
                   end do
                end do
             end do
          end do
       end do
    end if

 istep = 0

 select case (t_stepper)
  
    case (threestep)
       select case (t_integrator)
       ! AGILE this is our integrator (default threestep)
       case (ssprk3)
          ! this is SSPRK(3,3) Gottlieb-Shu 1998 or SSP(3,2) depending on ssprk_order (3 vs 2)
          
          call advect1(flux_method,rk_beta11, idimmin,idimmax,global_time,ps,&
               bg(1),global_time,ps1,bg(2))

           
          do iigrid=1,igridstail_active; igrid=igrids_active(iigrid);
             
             do iw = 1, nw
                do ix3 = ixGlo3, ixGhi3 
                   do ix2 = ixGlo2, ixGhi2 
                      do ix1 = ixGlo1, ixGhi1 
                         bg(3)%w(ix1,ix2,ix3,iw,igrid) = rk_alfa21 * bg(1)%w(ix1,ix2,&
                              ix3,iw,igrid) + rk_alfa22 * bg(2)%w(ix1,ix2,ix3,iw,igrid)
                      end do
                   end do
                end do
             end do
             if(stagger_grid) ps2(igrid)%ws=rk_alfa21*ps(igrid)%ws+&
                  rk_alfa22*ps1(igrid)%ws
          end do

          call advect1(flux_method,rk_beta22, idimmin,idimmax,&
               global_time+rk_c2*dt,ps1,bg(2),global_time+rk_alfa22*rk_c2*dt,ps2,&
               bg(3))

           
          do iigrid=1,igridstail_active; igrid=igrids_active(iigrid);
             
             do iw = 1, nw
                do ix3 = ixGlo3, ixGhi3 
                   do ix2 = ixGlo2, ixGhi2 
                      do ix1 = ixGlo1, ixGhi1 
                         bg(1)%w(ix1,ix2,ix3,iw,igrid) = rk_alfa31 * bg(1)%w(ix1,ix2,&
                              ix3,iw,igrid) + rk_alfa33 * bg(3)%w(ix1,ix2,ix3,iw,igrid)
                      end do
                   end do
                end do
             end do
             if(stagger_grid) ps(igrid)%ws=rk_alfa31*ps(igrid)%ws+&
                  rk_alfa33*ps2(igrid)%ws
          end do

          call advect1(flux_method,rk_beta33, idimmin,idimmax,&
               global_time+rk_c3*dt,ps2,bg(3),global_time+(1.0d0-rk_beta33)*dt,&
               ps,bg(1))

       case default
          call mpistop("unkown threestep time_integrator in advect")
       end select

    case default
       call mpistop("unkown time_stepper in advect")
    end select

  end subroutine advect

  !> Implicit global update step within IMEX schemes, advance psa=psb+dtfactor*qdt*F_im(psa)
  subroutine global_implicit_update(dtfactor,qdt,qtC,psa,psb)
    use mod_global_parameters
    use mod_ghostcells_update
    use mod_physics, only: phys_implicit_update, phys_req_diagonal

    type(state), target :: psa(max_blocks)
    !< Compute implicit part from this state and update it
    type(state), target :: psb(max_blocks)
    !< Will be unchanged, as on entry
    double precision, intent(in) :: qdt
    !< overall time step dt
    double precision, intent(in) :: qtC
    !< Both states psa and psb at this time level
    double precision, intent(in) :: dtfactor
    !< Advance psa=psb+dtfactor*qdt*F_im(psa)

    integer                        :: iigrid, igrid

    !> First copy all variables from a to b, this is necessary to account for
    ! quantities is w with no implicit sourceterm
    do iigrid=1,igridstail; igrid=igrids(iigrid);
       psa(igrid)%w = psb(igrid)%w
    end do

    if (associated(phys_implicit_update)) then
       call phys_implicit_update(dtfactor,qdt,qtC,psa,psb)
    end if

    ! enforce boundary conditions for psa
    call getbc(qtC,0.d0,psa,iwstart,nwgc,phys_req_diagonal)

  end subroutine global_implicit_update

  !> Evaluate Implicit part in place, i.e. psa==>F_im(psa)
  subroutine evaluate_implicit(qtC,psa)
    use mod_global_parameters
    use mod_physics, only: phys_evaluate_implicit

    type(state), target :: psa(max_blocks)
    !< Compute implicit part from this state and update it
    double precision, intent(in) :: qtC
    !< psa at this time level

    if (associated(phys_evaluate_implicit)) then
       call phys_evaluate_implicit(qtC,psa)
    end if

  end subroutine evaluate_implicit

  !> Integrate all grids by one partial step
  subroutine advect1(method,dtfactor,idimmin,idimmax,qtC,psa,bga,qt,psb,bgb)
    use mod_global_parameters
    use mod_ghostcells_update
    use mod_fix_conserve
    use mod_physics
    use mod_finite_volume, only: finite_volume_local
    use mod_usr_methods

    integer, intent(in)          :: idimmin,idimmax
    integer :: ixOmin1,ixOmin2,ixOmin3,ixOmax1,ixOmax2,ixOmax3
    type(state), target          :: psa(max_blocks)
    !< Compute fluxes based on this state
    type(state), target          :: psb(max_blocks)
    !< Update solution on this state
    type(block_grid_t)           :: bga
    !< Compute fluxes based on this state
    type(block_grid_t)           :: bgb
    !< Update solution on this state
    double precision, intent(in) :: dtfactor
    !< Advance over dtfactor * dt
    double precision, intent(in) :: qtC
    double precision, intent(in) :: qt
    integer, intent(in)          :: method(nlevelshi)
    double precision             :: qdt
    integer                      :: iigrid, igrid

    istep = istep+1

    ! AGILE doesn't happen in our test case
    ! if(associated(phys_special_advance)) then
    !  call phys_special_advance(qtC,psa)
    ! end if

    qdt=dtfactor*dt
    ! FIXME: AGILE The following replaces the `call advect1_grid` loop. The
    ! `advect1_grid` variable is a function pointer to the configured solver.
    ! Since NVidia doesn't support function pointers, we hard-code to use
    ! `finite_volume_all` here. In the future this needs to be replaced with
    ! some logic to select the desired method.

    ixOmin1=ixGlo1+nghostcells;ixOmin2=ixGlo2+nghostcells
    ixOmin3=ixGlo3+nghostcells;ixOmax1=ixGhi1-nghostcells
    ixOmax2=ixGhi2-nghostcells;ixOmax3=ixGhi3-nghostcells;
    call finite_volume_local( &
        qdt, dtfactor, & !some scalars related to time stepping
        ixGlo1,ixGlo2,ixGlo3,ixGhi1,ixGhi2,ixGhi3,ixOmin1,ixOmin2,ixOmin3,&
           ixOmax1,ixOmax2,ixOmax3, idimmin,idimmax, & !bounds for some arrays
        qtC, &                          ! scalar related to time stepping
        bga, &                          ! first block grid
        qt,  &                          ! scalar related to time stepping
        bgb  &                          ! second block grid
        )

    ! AGILE: todo
    ! if (fix_conserve_global .and. fix_conserve_at_step) then

    ! JESSENEW 
    call recvflux(idimmin,idimmax)
    call sendflux(idimmin,idimmax)
    call fix_conserve(bgb%istep,idimmin,idimmax,1,nwflux)

    !   if(stagger_grid) then
    !     call fix_edges(psb,idimmin,idimmax)
    !     ! fill the cell-center values from the updated staggered variables
    !     do iigrid=1,igridstail_active; igrid=igrids_active(iigrid);
    !       call phys_face_to_center(ixMlo1,ixMlo2,ixMlo3,ixMhi1,ixMhi2,ixMhi3,&
    !          psb(igrid))
    !     end do
    !   end if
    ! end if

    
    ! For all grids: fill ghost cells
    call getbc(qt+qdt,qdt,psb,iwstart,nwgc,phys_req_diagonal)

  end subroutine advect1

  !> process is a user entry in time loop, before output and advance
  !>         allows to modify solution, add extra variables, etc.
  !> Warning: CFL dt already determined (and is not recomputed)!
  subroutine process(iit,qt)
    use mod_usr_methods, only: usr_process_grid, usr_process_global
    use mod_global_parameters
    use mod_geometry, only: sync_positions_host
    use mod_ghostcells_update
    use mod_physics, only: phys_req_diagonal
    ! .. scalars ..
    integer,intent(in)          :: iit
    double precision, intent(in):: qt

    integer:: iigrid, igrid

    if (associated(usr_process_global)) then
       call usr_process_global(iit,qt)
    end if

    if (associated(usr_process_grid)) then
      ! usr_process_grid reads ps(igrid)%x on the host
      call sync_positions_host()
      do iigrid=1,igridstail; igrid=igrids(iigrid);
         ! next few lines ensure correct usage of routines like divvector etc
         dxlevel(1)=rnode(rpdx1_,igrid);dxlevel(2)=rnode(rpdx2_,igrid)
         dxlevel(3)=rnode(rpdx3_,igrid);
         block=>ps(igrid)
         call usr_process_grid(igrid,node(plevel_,igrid),ixGlo1,ixGlo2,ixGlo3,&
            ixGhi1,ixGhi2,ixGhi3,ixMlo1,ixMlo2,ixMlo3,ixMhi1,ixMhi2,ixMhi3, qt,&
            ps(igrid)%w,ps(igrid)%x)
      end do
      call getbc(qt,dt,ps,iwstart,nwgc,phys_req_diagonal)
    end if
  end subroutine process

  !> process_advanced is user entry in time loop, just after advance
  !>           allows to modify solution, add extra variables, etc.
  !>           added for handling two-way coupled PIC-MHD
  !> Warning: w is now at global_time^(n+1), global time and iteration at global_time^n, it^n
  subroutine process_advanced(iit,qt)
    use mod_usr_methods, only: usr_process_adv_grid, usr_process_adv_global
    use mod_global_parameters
    use mod_geometry, only: sync_positions_host
    use mod_ghostcells_update
    use mod_physics, only: phys_req_diagonal
    ! .. scalars ..
    integer,intent(in)          :: iit
    double precision, intent(in):: qt

    integer:: iigrid, igrid

    if (associated(usr_process_adv_global)) then
       call usr_process_adv_global(iit,qt)
    end if

    if (associated(usr_process_adv_grid)) then
      ! usr_process_adv_grid reads ps(igrid)%x on the host
      call sync_positions_host()
      do iigrid=1,igridstail; igrid=igrids(iigrid);
         ! next few lines ensure correct usage of routines like divvector etc
         dxlevel(1)=rnode(rpdx1_,igrid);dxlevel(2)=rnode(rpdx2_,igrid)
         dxlevel(3)=rnode(rpdx3_,igrid);
         block=>ps(igrid)

         call usr_process_adv_grid(igrid,node(plevel_,igrid),ixGlo1,ixGlo2,&
            ixGlo3,ixGhi1,ixGhi2,ixGhi3,ixMlo1,ixMlo2,ixMlo3,ixMhi1,ixMhi2,&
            ixMhi3, qt,ps(igrid)%w,ps(igrid)%x)
      end do
      call getbc(qt,dt,ps,iwstart,nwgc,phys_req_diagonal)
    end if
  end subroutine process_advanced

end module mod_advance
