! Module  : log_range_m
! Purpose : define log_range_t type, representing a logarithmically
!           distributed range
!
! Copyright 2022 Rich Townsend & The MSG Team
!
! This file is part of MSG. MSG is free software: you can redistribute
! it and/or modify it under the terms of the GNU General Public
! License as published by the Free Software Foundation, version 3.
!
! MSG is distributed in the hope that it will be useful, but WITHOUT
! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
! or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
! License for more details.
!
! You should have received a copy of the GNU General Public License
! along with this program.  If not, see <http://www.gnu.org/licenses/>.

#:include 'forum.inc'

module log_range_m

   ! Uses

   use forum_m

   use file_m
   use range_m
   use stat_m

   use ISO_FORTRAN_ENV

   ! No implicit typing

   implicit none

   ! Derived-type definitions

   type, extends(range_t) :: log_range_t
      private
      real(RD) :: logx_0
      real(RD) :: dlogx
      integer  :: n
   contains
      private
      procedure, public :: get_n
      procedure, public :: get_x_min
      procedure, public :: get_x_max
      procedure, public :: get_ssize
      procedure, public :: unpack
      procedure, public :: subset
      procedure, public :: read
      procedure, public :: write 
  end type log_range_t

   ! Interfaces

   interface log_range_t
      module procedure log_range_t_
   end interface log_range_t

   ! Access specifiers

   private

   public :: log_range_t

contains

   function log_range_t_(logx_0, dlogx, n) result(range)

      real(RD), intent(in) :: logx_0
      real(RD), intent(in) :: dlogx
      integer, intent(in)  :: n
      type(log_range_t)    :: range
      
      ! Construct range from the supplied data

      @:ASSERT(n >= 1,'insufficient data')

      range%logx_0 = logx_0
      range%dlogx = dlogx

      range%n = n

      ! Finish

      return

   end function log_range_t_

   !****

   subroutine get_n(self, n)

      class(log_range_t), intent(in) :: self
      integer, intent(out)           :: n

      ! Get the size

      n = self%n

      ! Finish

      return

   end subroutine get_n

   !****

   subroutine get_x_min(self, x_min)

      class(log_range_t), intent(in) :: self
      real(RD), intent(out)          :: x_min

      ! Get the x-minimum

      x_min = EXP(self%logx_0)

      ! Finish

      return

   end subroutine get_x_min

   !****

   subroutine get_x_max(self, x_max)

      class(log_range_t), intent(in) :: self
      real(RD), intent(out)          :: x_max

      ! Get the x-maximum

      x_max = EXP(self%logx_0 + self%dlogx*(self%n-1))

      ! Finish

      return

   end subroutine get_x_max

   !****

   subroutine get_ssize(self, ssize)

      class(log_range_t), intent(in) :: self
      integer(ID), intent(out)       :: ssize

      ! Get the storage size

      ssize = STORAGE_SIZE(self)/8

      ! Finish

      return

   end subroutine get_ssize

   !****

   subroutine unpack(self, x)

      class(log_range_t), intent(in) :: self
      real(RD), intent(out)          :: x(:)

      integer :: i

      ! Unpack the range into the array

      @:CHECK_BOUNDS(SIZE(x), self%n)

      ! Unpack the range into the array

      do i = 1, self%n
         x(i) = EXP(self%logx_0 + self%dlogx*(i-1))
      end do

      ! Finish

      ! Finish

      return

   end subroutine unpack

   !****

   subroutine subset(self, i_a, i_b, range, stat)

      class(log_range_t), intent(in)           :: self
      integer, intent(in)                      :: i_a
      integer, intent(in)                      :: i_b
      class(range_t), allocatable, intent(out) :: range
      integer, intent(out), optional           :: stat

      ! Subset the range

      if (i_a < 1) then
         if (PRESENT(stat)) then
            stat = STAT_OUT_OF_BOUNDS_RANGE_LO
            return
         else
            @:ABORT('out of bounds (lo) range')
         end if
      end if

      if (i_b > self%n) then
         if (PRESENT(stat)) then
            stat = STAT_OUT_OF_BOUNDS_RANGE_HI
            return
         else
            @:ABORT('out of bounds (hi) range')
         end if
      end if

      if (i_b < i_a) then
         if (PRESENT(stat)) then
            stat = STAT_INVALID_ARGUMENT
            return
         else
            @:ABORT('invalid argument')
         end if
      end if

      range = log_range_t(self%logx_0+self%dlogx*(i_a-1), self%dlogx, i_b-i_a+1)

      ! Finish

      return

   end subroutine subset

   !****

   subroutine read(self, hdf5io, stat)

      class(log_range_t), intent(out) :: self
      type(hdf5io_t), intent(inout)   :: hdf5io
      integer, intent(out), optional  :: stat

      integer :: revision

      ! Read the range

      call check_type(hdf5io, 'log_range_t', stat)
      if (PRESENT(stat)) then
         if (stat /= STAT_OK) return
      end if

      call hdf5io%read_attr('REVISION', revision)

      select case(revision)
      case(1)
         call read_rev1_(self, hdf5io, stat)
      case default
         if (PRESENT(stat)) then
            stat = STAT_INVALID_GROUP_REVISION
            return
         else
            @:ABORT('invalid group revision')
         end if
      end select

      ! Finish

      return

   contains

      subroutine read_rev1_(self, hdf5io, stat)

         class(log_range_t), intent(out) :: self
         type(hdf5io_t), intent(inout)   :: hdf5io
         integer, intent(out), optional  :: stat

         real(RD) :: logx_0
         real(RD) :: dlogx
         integer  :: n

         call hdf5io%read_attr('logx_0', logx_0)
         call hdf5io%read_attr('dlogx', dlogx)
         call hdf5io%read_attr('n', n)

         select type(self)
         type is (log_range_t)
            self = log_range_t(logx_0, dlogx, n)
         class default
            @:ABORT('invalid type')
         end select

         if (PRESENT(stat)) stat = STAT_OK
         
         return

      end subroutine read_rev1_

   end subroutine read

   !****

   subroutine write(self, hdf5io, stat)

      class(log_range_t), intent(in) :: self
      type(hdf5io_t), intent(inout)  :: hdf5io
      integer, intent(out), optional :: stat

      ! Write range

      call hdf5io%write_attr('TYPE', 'log_range_t')
      call hdf5io%write_attr('REVISION', 1)

      call hdf5io%write_attr('logx_0', self%logx_0)
      call hdf5io%write_attr('dlogx', self%dlogx)
      call hdf5io%write_attr('n', self%n)
     
      ! Finish

      return

   end subroutine write

end module log_range_m
