module mod_grid_settings
  use mod_global_variables, only: dp, n_gauss
  implicit none

  private

  type, public :: grid_settings_t
    character(:), private, allocatable :: geometry
    integer, private :: gridpts
    integer, private :: gauss_gridpts
    integer, private :: ef_gridpts
    real(dp), private :: grid_start
    real(dp), private :: grid_end
    logical :: coaxial
    logical :: force_r0
    logical :: symmetric_grid

  contains

    procedure, public :: set_geometry
    procedure, public :: get_geometry
    procedure, public :: set_gridpts
    procedure, public :: get_gridpts
    procedure, public :: get_gauss_gridpts
    procedure, public :: get_ef_gridpts
    procedure, public :: set_grid_boundaries
    procedure, public :: get_grid_start
    procedure, public :: get_grid_end
    procedure, public :: delete

    procedure, private :: validate_grid_start
  end type grid_settings_t

  public :: new_grid_settings

contains

  function new_grid_settings() result(grid_settings)
    type(grid_settings_t) :: grid_settings

    call grid_settings%set_geometry("Cartesian")
    call grid_settings%set_gridpts(50)
    call grid_settings%set_grid_boundaries(0.0_dp, 1.0_dp)
    grid_settings%coaxial = .false.
    grid_settings%force_r0 = .false.
    grid_settings%symmetric_grid = .false.
  end function new_grid_settings


  pure subroutine set_geometry(this, geometry)
    class(grid_settings_t), intent(inout) :: this
    character(len=*), intent(in) :: geometry
    this%geometry = geometry
  end subroutine set_geometry


  pure function get_geometry(this) result(geometry)
    class(grid_settings_t), intent(in) :: this
    character(len=:), allocatable :: geometry
    geometry = trim(adjustl(this%geometry))
  end function get_geometry


  pure subroutine set_gridpts(this, gridpts)
    class(grid_settings_t), intent(inout) :: this
    integer, intent(in) :: gridpts
    this%gridpts = gridpts
    this%gauss_gridpts = n_gauss * (gridpts - 1)
    this%ef_gridpts = 2 * gridpts - 1
  end subroutine set_gridpts


  pure integer function get_gridpts(this)
    class(grid_settings_t), intent(in) :: this
    get_gridpts = this%gridpts
  end function get_gridpts


  pure integer function get_gauss_gridpts(this)
    class(grid_settings_t), intent(in) :: this
    get_gauss_gridpts = this%gauss_gridpts
  end function get_gauss_gridpts


  pure integer function get_ef_gridpts(this)
    class(grid_settings_t), intent(in) :: this
    get_ef_gridpts = this%ef_gridpts
  end function get_ef_gridpts


  subroutine set_grid_boundaries(this, grid_start, grid_end)
    class(grid_settings_t), intent(inout) :: this
    real(dp), intent(in) :: grid_start
    real(dp), intent(in) :: grid_end

    this%grid_start = grid_start
    if (this%get_geometry() == "cylindrical") call this%validate_grid_start()
    this%grid_end = grid_end
  end subroutine set_grid_boundaries


  pure real(dp) function get_grid_start(this)
    class(grid_settings_t), intent(in) :: this
    get_grid_start = this%grid_start
  end function get_grid_start


  pure real(dp) function get_grid_end(this)
    class(grid_settings_t), intent(in) :: this
    get_grid_end = this%grid_end
  end function get_grid_end


  subroutine validate_grid_start(this)
    use mod_check_values, only: is_zero
    use mod_logging, only: logger
    class(grid_settings_t), intent(inout) :: this

    if (this%grid_start < 0.0_dp) then
      call logger%error("grid start must be >= 0 in cylindrical geometry")
      return
    end if

    if (this%coaxial .and. is_zero(this%grid_start)) then
      call logger%error("grid start must be > 0 to introduce an inner wall boundary")
      return
    end if

    if (this%force_r0) then
      this%grid_start = 0.0_dp
      call logger%warning( &
        "forcing on-axis r in cylindrical geometry, may lead to spurious eigenvalues" &
      )
    else if (is_zero(this%grid_start)) then
      this%grid_start = 0.025_dp
      call logger%info( &
        "grid start set to 0.025 to avoid on-axis singularity. " // &
        "Set 'force_r0 = .true.' to force r=0. " &
      )
    end if
  end subroutine validate_grid_start


  pure subroutine delete(this)
    class(grid_settings_t), intent(inout) :: this
    if (allocated(this%geometry)) deallocate(this%geometry)
  end subroutine delete

end module mod_grid_settings
