module IcosahedralLorentzGrid_Manager

  use dc_types, only: &
    & DP

  use IcGrid2D_FVM_Manager, only: &
    & IcGrid2D_FVM, &
    & IcGrid2D_FVM_Init, IcGrid2D_FVM_Final, &
    & malloc_GP_array, malloc_CV_array

  use IcGrid3D_FVM_Manager, only: &
    & IcGrid3D_FVM, &
    & IcGrid3D_FVM_Init, IcGrid3D_FVM_Final

  use Field_IcGrid2D_Manager, only: &
    & Field_IcGrid2D

  use TFCoord_IcGrid3D_helper, only: &
    & TFCoord_IcGrid3D, &
    & TFCoord_IcGrid3D_Init, TFCoord_IcGrid3D_Final, &
    & calc_metric_G_1div2, calc_metric_G_Z

  use Derivate_Field_IcGrid2D_Manager, only: &
    & Derivate_Field_IcGrid2D, &
    & Derivate_Field_IcGrid2D_Init, Derivate_Field_IcGrid2D_Final

  use VDerivate_Field_IcGrid3D_Manager, only: &
    & VDerivate_Field_IcGrid3D, &
    & VDerivate_Field_IcGrid3D_Init, VDerivate_Field_IcGrid3D_Final

  implicit none
  private

  type, public :: IcosahedralLorentzGrid
    type(IcGrid2D_FVM) :: icgrid2D
    type(IcGrid3D_FVM) :: icgrid3Dr
    type(IcGrid3D_FVM) :: icgrid3Dz
    type(TFCoord_IcGrid3D) :: tfcoordz
    type(TFCoord_IcGrid3D) :: tfcoordr

    type(Derivate_Field_IcGrid2D) :: icgrid2d_FVMHDerivate
    type(VDerivate_Field_IcGrid3D) :: icgrid3dz_VDerivate
    type(VDerivate_Field_IcGrid3D) :: icgrid3dr_VDerivate

    logical :: tfcoord_enable = .false.
    logical :: derivate_enable = .false.

  end type IcosahedralLorentzGrid

  integer, parameter, public :: LORENTZGRID_HARF_LEVEL = 1
  integer, parameter, public :: LORENTZGRID_FULL_LEVEL = 2

  public :: IcosahedralLorentzGrid_Init, IcosahedralLorentzGrid_Final
  public :: get_HorizontalGrid, get_HVIcGrid3D, &
    & get_HorizontalDerivateTool, get_VerticalDerivateTool, &
    & get_TerrainFollowingCoordinate

  public :: prepare_TerrainFollowingCoordinate, prepare_DerivateTool

  interface IcosahedralLorentzGrid_Init
    module procedure IcoshedralLorentzGrid_Init1
  end interface IcosahedralLorentzGrid_Init

contains
subroutine IcoshedralLorentzGrid_Init1( &
  & self, glevel, ic_base_radius, vertical_layers_num, &
  & mpi_enable_flag  &
  & )

  type(IcosahedralLorentzGrid), intent(inout) :: self
  integer, intent(in) :: glevel
  real(DP), intent(in) :: ic_base_radius
  integer, intent(in) :: vertical_layers_num
  logical, optional :: mpi_enable_flag

  logical :: mpi_flag

  if ( present(mpi_enable_flag) ) then
    mpi_flag = mpi_enable_flag
  else
    mpi_flag = .false.
  end if

  call IcGrid2D_FVM_Init(self%icgrid2D, glevel, ic_base_radius, mpi_flag)
  call malloc_GP_array(self%icgrid2D)
  call malloc_CV_array(self%icgrid2D)

  call IcGrid3D_FVM_Init(self%icgrid3Dz, self%icgrid2D, vertical_layers_num)
  call IcGrid3D_FVM_Init(self%icgrid3Dr, self%icgrid2D, vertical_layers_num+1)

end subroutine IcoshedralLorentzGrid_Init1

subroutine IcosahedralLorentzGrid_Final( &
  & self &
  & )

  type(IcosahedralLorentzGrid), intent(inout) :: self

  if ( self%derivate_enable ) then
    call Derivate_Field_IcGrid2D_Final(self%icgrid2D_FVMHDerivate)
    call VDerivate_Field_IcGrid3D_Final(self%icgrid3Dz_VDerivate)
    call VDerivate_Field_IcGrid3D_Final(self%icgrid3Dr_VDerivate)
  end if

  if (self%tfcoord_enable) then
    call TFCoord_IcGrid3D_Final(self%tfcoordz)
    call TFCoord_IcGrid3D_Final(self%tfcoordr)
  end if

  call IcGrid3D_FVM_Final(self%icgrid3Dz)
  call IcGrid3D_FVM_Final(self%icgrid3Dr)
  call IcGrid2D_FVM_Final(self%icgrid2D)

end subroutine IcosahedralLorentzGrid_Final

subroutine prepare_DerivateTool( &
  & self &
  & )

  type(IcosahedralLorentzGrid), intent(inout) :: self

  self%derivate_enable = .true.

  call Derivate_Field_IcGrid2D_Init(self%icgrid2D_FVMHDerivate, self%icgrid2D)
  call VDerivate_Field_IcGrid3D_Init(self%icgrid3Dz_VDerivate, self%icgrid3Dz)
  call VDerivate_Field_IcGrid3D_Init(self%icgrid3Dr_VDerivate, self%icgrid3Dr)

end subroutine prepare_DerivateTool

subroutine prepare_TerrainFollowingCoordinate( &
  & self, xy_Zs, Z_T &
  & )

  type(IcosahedralLorentzGrid), intent(inout) :: self
  type(Field_IcGrid2D), intent(in) :: xy_Zs
  real(DP), intent(in) :: Z_T

  self%tfcoord_enable = .true.

  call TFCoord_IcGrid3D_Init(self%tfcoordz, self%icgrid3Dz, xy_Zs, Z_T)
  call TFCoord_IcGrid3D_Init(self%tfcoordr, self%icgrid3Dr, xy_Zs, Z_T)

  call calc_metric_G_1div2(self%tfcoordz)
  call calc_metric_G_1div2(self%tfcoordr)

  call calc_metric_G_Z(self%tfcoordz, self%icgrid2D_FVMHDerivate)
  call calc_metric_G_Z(self%tfcoordr, self%icgrid2D_FVMHDerivate)

end subroutine prepare_TerrainFollowingCoordinate

function get_HorizontalGrid(self) result(icgrid2D)
  type(IcosahedralLorentzGrid), intent(in), target :: self
  type(IcGrid2D_FVM), pointer :: icgrid2D

  icgrid2D => self%icgrid2D

end function get_HorizontalGrid

function get_HVIcGrid3D(self, LorentzGridLevelType) result(icgrid3D)
  type(IcosahedralLorentzGrid), intent(in), target :: self
  integer, intent(in) :: LorentzGridLevelType
  type(IcGrid3D_FVM), pointer :: icgrid3D

  if ( LorentzGridLevelType == LORENTZGRID_HARF_LEVEL ) then
    icgrid3D => self%icgrid3Dr
  else if ( LorentzGridLevelType == LORENTZGRID_FULL_LEVEL ) then
    icgrid3D => self%icgrid3Dz
  else
    icgrid3D => null()
  end if

end function get_HVIcGrid3D

function get_HorizontalDerivateTool(self) result(derivate)
  type(IcosahedralLorentzGrid), intent(in), target :: self
  type(Derivate_Field_IcGrid2D), pointer :: derivate

  derivate => self%icgrid2D_FVMHDerivate

end function get_HorizontalDerivateTool

function get_VerticalDerivateTool(self, LorentzGridLevelType) result(derivate)
  type(IcosahedralLorentzGrid), intent(in), target :: self
  integer, intent(in) :: LorentzGridLevelType
  type(VDerivate_Field_IcGrid3D), pointer :: derivate

  if ( LorentzGridLevelType == LORENTZGRID_HARF_LEVEL ) then
    derivate => self%icgrid3Dr_VDerivate
  else if ( LorentzGridLevelType == LORENTZGRID_FULL_LEVEL ) then
    derivate => self%icgrid3Dr_VDerivate
  else
    derivate => null()
  end if
end function get_VerticalDerivateTool

function get_TerrainFollowingCoordinate(self, LorentzGridLevelType) result(tfcoord)
  type(IcosahedralLorentzGrid), intent(in), target :: self
  integer, intent(in) :: LorentzGridLevelType
  type(TFCoord_IcGrid3D), pointer :: tfcoord

  if ( LorentzGridLevelType == LORENTZGRID_HARF_LEVEL ) then
    tfcoord => self%tfcoordr
  else if ( LorentzGridLevelType == LORENTZGRID_FULL_LEVEL ) then
    tfcoord => self%tfcoordz
  else
    tfcoord => null()
  end if

end function get_TerrainFollowingCoordinate


end module IcosahedralLorentzGrid_Manager
