module HandleNetCDF 

      use selectrealkind, only : kpr 
      use netcdf
      use HandleStrings

      implicit none 

      private 

      public :: & 
                output,&
                netcdfopen,&
                netcdfinquiry,&
                netcdfinquirevariable,&
                netcdfinquiredimension,&
                netcdfinquirevariableid

      type output 
        integer(kind=4) :: id
        integer(kind=4) :: id_variable
        integer(kind=4) :: nVariables 
        integer(kind=4) :: nAttributes
        integer(kind=4) :: nDimension
        integer(kind=4) :: unlimitedDimId 
        integer(kind=4) :: formatNum 
        integer(kind=4) :: status  
        character(len=:),allocatable ::netcdfroutine 
        character(len=:),allocatable ::variablename
         contains
           procedure,pass :: getVariable
           procedure,pass :: get3DVariable
           procedure,pass :: check => checkStatus
      end type output 

      contains

        subroutine checkStatus(self) 

          implicit none 
          class(output),intent(in) :: self 

          if(self.status/=nf90_noerr) then 

            write(*,*) " "
            write(*,*) " [checkStatus] called from ", self.netcdfroutine
            write(*,'(3x,a,3x)'), trim(nf90_strerror(self.status))
            stop "  [checkStatus] Stopped"

          end if

        end subroutine checkStatus

        function get3DVariable(self,dim1,dim2,dim3) result(output) 

          implicit none 
          class(output),intent(inout) :: self 
          integer, intent(in) :: dim1,dim2,dim3
          real(kind=kpr),dimension(:,:,:),allocatable :: output

          integer :: iAllocate
          logical :: lDebug

          lDebug=.true.

          if(lDebug) write(*,*) ' [get3DVariable] self.id = ', self.id
          if(lDebug) write(*,*) ' [get3DVariable] self.variablename = ', self.variablename
          if(lDebug) write(*,*) ' [get3DVariable] self.id_variable  = ', self.id_variable
          if(dim1.eq.0.or.&
             dim2.eq.0.or.&
             dim3.eq.0) stop' [get3DVariable] nDimension = 0 '
          allocate(output(dim1,dim2,dim3),stat=iAllocate)
          if(iAllocate/=0)stop' [get3DVariable] output not allocated '
          self.status=nf90_get_var(self.id,self.id_variable,output)
          self.netcdfroutine='nf90_get_var'
          call self.check()

        end function get3DVariable 

        function getVariable(self) result(output) 

          implicit none 
          class(output),intent(inout) :: self 
          real(kind=kpr),dimension(:),allocatable :: output

          integer :: iAllocate

          if(self.nDimension.eq.0)stop' [getVariable] nDimension = 0 '
          allocate(output(self.nDimension),stat=iAllocate)
          if(iAllocate/=0)stop' [getVariable] output not allocated '
          self.status=nf90_get_var(self.id,self.id_variable,output)
          call self.check()

        end function getVariable 

        subroutine netcdfinquire(self)

          implicit none 
          class(output),intent(inout) :: self

          !Local Variables 
          integer(kind=4) :: nDimensions,nVariables 

          self.status = nf90_inquire(ncid=self.id,& 
                                     nVariables=nVariables)
          self.netcdfroutine='nf90_inquire'
          self.nVariables=nVariables
          call self.check()

        end subroutine netcdfinquire 

        type(output) function netcdfinquiredimension(id,id_variable)  result(out)

          implicit none 
          integer(kind=4),intent(in) :: id,id_variable
          character(len=:),allocatable :: vname 
          integer(kind=4) :: xtype,ndims,nGrid
          integer(kind=4),dimension(2) :: dimids  
          character(len=50) :: xname

          out.id = id 
          out.status = nf90_inquire_dimension(id,id_variable,xname,ndims)
          out.netcdfroutine='nf90_inquire_dimension'
          out.nDimension = ndims
          out.id_variable=id_variable
          call out.check()

        end function netcdfinquiredimension

        type(output) function netcdfinquirevariableid(id,vname)  result(out)

          implicit none 
          integer(kind=4),intent(in) :: id 
          character(len=*),intent(in) :: vname 
          
          !Local Variables 
          integer(kind=4) :: id_variable
          integer(kind=4) :: xtype,nGrid,ndims
          integer(kind=4),dimension(8) :: dimids  
          character(len=iCharMax) :: xname
          logical :: lDebug 

          if(lDebug) write(*,*)' [netcdfinquirevariableid] Debugging Mode Activate '

          out.id = id 
          out.status = nf90_inq_varid(id,vname,id_variable)
          out.id_variable=id_variable
          out.variableName=vname
          out.netcdfroutine='nf90_inq_varid'
          call out.check()

        end function netcdfinquirevariableid

        type(output) function netcdfinquirevariable(id,id_variable)  result(out)

          implicit none 
          integer(kind=4),intent(in) :: id,id_variable

          integer(kind=4) :: xtype,nGrid,ndims
          integer(kind=4),dimension(8) :: dimids  
          character(len=iCharMax) :: xname
          character(len=iCharMax) :: vname 
          logical :: lDebug

          lDebug=.True.

          out.id = id 
          out.status = nf90_inquire_variable(id,id_variable,vname,xtype,ndims,dimids)
          out.variablename=strip(vname)
          if(lDebug)then 
            print*,' netcdfinquirevariable , variablename = ', out.variablename
            print*,' netcdfinquirevariable , ndims = ', ndims
          endif
          out.netcdfroutine='nf90_inquire_variable'
          call out.check()

        end function netcdfinquirevariable

        type(output) function netcdfinquiry(id) result(out)

          implicit none 
          integer(kind=4),intent(in) :: id
          integer(kind=4) :: formatNum,ndims_in,nvars_in,ngatts_in,unlim

          out.status = nf90_inquire(ncid=id,&
                          nDimensions=ndims_in,&
                          nVariables=nvars_in,&
                          nAttributes=ngatts_in,&
                          unlimitedDimId=unlim,&
                          formatNum=formatNum)
          
          out.id=id
          out.nDimension=ndims_in
          out.nVariables=nvars_in
          out.nAttributes=ngatts_in
          out.unlimitedDimId=unlim
          out.formatNum=formatNum
          out.netcdfroutine='nf90_inquire'
          call out.check()

        end function  netcdfinquiry

        type(output) function netcdfopen(fname,action) result(out)

         implicit none 
         character(len=*),intent(in) :: fname
         character(len=4),intent(in) :: action
         !Logical Variables 
         integer(kind=4) :: mode
         integer(kind=4) :: ncid 

         select case (lower(action)) 
          case ('read')
           mode=NF90_NOWRITE
          case default  
           stop' [netcdfopen] select case option action - undefined '
         end select

         out.status=nf90_open(path=fname,mode=mode,ncid=ncid) 
         out.id=ncid
         out.netcdfroutine='nf90_open'
         call out.check()

        end function netcdfopen 

        !subroutine check(status)

         !implicit none 
         !integer(kind=4),intent(in) :: status
        
         !if (status /= nf90_noerr) then
          !write(*,*)" "
          !write(*,'(a)')trim(nf90_strerror(status))
          !write(*,*)" "
          !stop" [check] Execution Halted "
         !endif

        !end subroutine check

end module HandleNetCDF 
