! Copyright 2024 - The Minton Group at Purdue University
! This file is part of Swiftest.
! Swiftest 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, either version 3 of the License, or (at your option) any later version.
! Swiftest 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 Swiftest. 
! If not, see: https://www.gnu.org/licenses. 

submodule (netcdf_io) s_netcdf_io_implementations
   use netcdf
contains

   module subroutine netcdf_io_check(status, call_identifier)
      !! author: Carlisle A. Wishard, Dana Singh, and David A. Minton
      !!
      !! Checks the status of all NetCDF operations to catch errors
      implicit none
      ! Arguments
      integer, intent (in) :: status 
         !! The status code returned by a NetCDF function
      character(len=*), intent(in), optional :: call_identifier 
         !! String that indicates which calling function caused the error for diagnostic purposes

      if(status /= NF90_NOERR) then
         if (present(call_identifier)) write(*,*) "NetCDF error in ",trim(call_identifier)
         write(*,*) trim(nf90_strerror(status))
         call base_util_exit(FAILURE)
      end if

      return
   end subroutine netcdf_io_check


   module subroutine netcdf_io_close(self)
      !! author: Carlisle A. Wishard, Dana Singh, and David A. Minton
      !!
      !! Closes a NetCDF file
      implicit none
      ! Arguments
      class(netcdf_parameters),   intent(inout) :: self   !! Parameters used to identify a particular NetCDF dataset
      character(namelen) :: message

      if (self%lfile_is_open) then
#ifdef COARRAY
         write(message,*) this_image()
         message = "netcdf_io_close on image " // trim(adjustl(message))
#else
         message = "netcdf_io_close"
#endif
         call netcdf_io_check( nf90_close(self%id), message)
         self%lfile_is_open = .false.
      end if

      return
   end subroutine netcdf_io_close


   module subroutine netcdf_io_find_tslot(self, t, tslot)
      !! author: David A. Minton
      !! 
      !! Given an open NetCDF file and a value of time t, finds the index of the time value (aka the time slot) to place a new set 
      !! of data.  The returned value of tslot will correspond to the first index value where the value of t is greater than or 
      !! equal to the saved time value.
      use, intrinsic :: ieee_exceptions
      use, intrinsic :: ieee_arithmetic
      implicit none
      ! Arguments
      class(netcdf_parameters), intent(inout) :: self  !! Parameters used to identify a particular NetCDF dataset
      real(DP),                 intent(in)    :: t     !! The value of time to search for
      integer(I4B),             intent(out)   :: tslot !! The index of the time slot where this data belongs
      ! Internals
      real(DP), dimension(:), allocatable :: tvals
      integer(I4B) :: i
      logical, dimension(size(IEEE_ALL))      :: fpe_halting_modes

      call ieee_get_halting_mode(IEEE_ALL,fpe_halting_modes)  ! Save the current halting modes so we can turn them off temporarily
      call ieee_set_halting_mode(IEEE_ALL,.false.)

      if (.not.self%lfile_is_open) return
      tslot = 0

      call netcdf_io_check( nf90_inquire_dimension(self%id, self%time_dimid, self%time_dimname, len=self%max_tslot), &
                             "netcdf_io_find_tslot nf90_inquire_dimension max_tslot"  )
      if (self%max_tslot > 0) then
         allocate(tvals(self%max_tslot))
         call netcdf_io_check( nf90_get_var(self%id, self%time_varid, tvals(:), start=[1]), "netcdf_io_find_tslot get_var"  )
         where(.not.ieee_is_normal(tvals(:))) tvals(:) = huge(1.0_DP)
      else
         allocate(tvals(1))
         tvals(1) = huge(1.0_DP)
         self%max_tslot = 1
      end if

      tslot = 1
      do i = 1, self%max_tslot
         if (t <= tvals(tslot)) exit
         tslot = tslot + 1
      end do
      self%max_tslot = max(self%max_tslot, tslot)
      self%tslot = tslot

      call ieee_set_halting_mode(IEEE_ALL,fpe_halting_modes)

      return
   end subroutine netcdf_io_find_tslot


   module subroutine netcdf_io_find_idslot(self, id, idslot)
      !! author: David A. Minton
      !! 
      !! Given an open NetCDF file and a value of id, finds the index of the id value (aka the id slot) to place a new set of data.
      !! The returned value of idslot will correspond to the first index value where the value of id is greater than or equal to the
      !! saved id value.
      implicit none
      ! Arguments
      class(netcdf_parameters), intent(inout) :: self   !! Parameters used to identify a particular NetCDF dataset
      integer(I4B),             intent(in)    :: id     !! The value of id to search for
      integer(I4B),             intent(out)   :: idslot !! The index of the id slot where this data belongs
      ! Internals
      integer(I4B), dimension(:), allocatable :: idvals

      if (.not.self%lfile_is_open) return

      if (.not.allocated(self%idvals)) call self%get_idvals()
      self%max_idslot = size(self%idvals)
      idslot = id + 1 
      if (idslot > self%max_idslot) then

         ! Update the idvals array
         allocate(idvals(idslot))
         idvals(:) = NF90_FILL_INT 
         idvals(1:self%max_idslot) = self%idvals(1:self%max_idslot)
         idvals(idslot) = id
         call move_alloc(idvals, self%idvals) 
         self%max_idslot = idslot
      end if

      self%idslot = idslot

      return
   end subroutine netcdf_io_find_idslot


   module subroutine netcdf_io_get_idvals(self)
      !! author: David A. Minton
      !! 
      !! Gets a full list of id values 
      implicit none
      ! Arguments
      class(netcdf_parameters), intent(inout) :: self   
         !! Parameters used to identify a particular NetCDF dataset
      ! Internals

      if (.not.self%lfile_is_open) return

      if (allocated(self%idvals)) deallocate(self%idvals)
      call netcdf_io_check( nf90_inquire_dimension(self%id, self%name_dimid, self%name_dimname, len=self%max_idslot), &
                           "netcdf_io_find_tslot nf90_inquire_dimension max_tslot"  )
      if (self%max_idslot > 0) then
         allocate(self%idvals(self%max_idslot))
         call netcdf_io_check( nf90_get_var(self%id, self%id_varid, self%idvals(:), start=[1]), "netcdf_io_find_idslot get_var" )
      else
         allocate(self%idvals(1))
         self%idvals(1) = 0
      end if

      return
   end subroutine netcdf_io_get_idvals


   module subroutine netcdf_io_sync(self)
      !! author: David A. Minton
      !!
      !! Syncrhonize the disk and memory buffer of the NetCDF file (e.g. commit the frame files stored in memory to disk) 
      !!    
      implicit none
      ! Arguments
      class(netcdf_parameters), intent(inout) :: self !! Parameters used to identify a particular NetCDF dataset

      call netcdf_io_check( nf90_sync(self%id), "netcdf_io_sync nf90_sync"  )

      return
   end subroutine netcdf_io_sync



end submodule s_netcdf_io_implementations
