subroutine gfits_load_header(hdict,check,getsymbol,error)
  use gfits_interfaces, except_this=>gfits_load_header
  use gfits_types
  !---------------------------------------------------------------------
  ! @ public
  ! Load the FITS header in the dictionary structure.
  !  COMMENT lines are ignored
  !  HISTORY lines are ignored
  !  HIERARCH lines are interpreted as appropriate
  ! This could be customized
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(out)   :: hdict      !
  logical,             intent(in)    :: check      !
  external                           :: getsymbol  ! Symbol translation routine
  logical,             intent(inout) :: error      !
  ! Local
  integer(kind=4) :: icard
  !
  ! Read cards until END is reached
  hdict%ncard = 0
  do
    call gfits_reallocate_dict(hdict,error)
    if (error)  return
    !
    icard = hdict%ncard+1
    call gfits_get(hdict%card(icard)%key,hdict%card(icard)%val,  &
      check,error,continue=.true.,comment=hdict%card(icard)%comment)
    if (error)  return
    call gfits_patch_key(hdict%card(icard))
    if (hdict%card(icard)%key.eq.'COMMENT')  cycle
    if (hdict%card(icard)%key.eq.'HISTORY')  cycle
    if (hdict%card(icard)%key.eq.'')         cycle  ! Must be a comment
    if (hdict%card(icard)%key.eq.'END')      exit
    if (hdict%card(icard)%key.eq.'HIERARCH') then
      call gfits_hierarch(hdict%card(icard)%key,hdict%card(icard)%val,error)
      if (error)  return
    endif
    ! That's a valid card: keep it
    hdict%ncard = icard
    hdict%sort(icard) = icard
  enddo
  !
  ! Compute the sorting array
  call gfits_setsort(hdict,error)
  if (error)  return
  !
contains
  !
  subroutine gfits_patch_key(card)
    type(fits_unkn_0d_t), intent(inout) :: card
    ! Local
    character(len=key_length) :: trans
    logical :: error  ! Local error, never set to .true.
    !
    error = .false.
    call getsymbol(card%key,trans,error)
    if (error) then
      error = .false.
    else
      card%key = trans
    endif
  end subroutine
  !
  subroutine print_header()
    !---------------------------------------------------------------------
    ! Debug
    !---------------------------------------------------------------------
    integer(kind=4) :: jcard
    do icard=1,hdict%ncard
      jcard = hdict%sort(icard)
      write(*,'(I0,A)') jcard,':'
      write(*,'(2X,A)') hdict%card(jcard)%key
      write(*,'(2X,A)') hdict%card(jcard)%val
      write(*,'(2X,A)') hdict%card(jcard)%comment
    enddo
  end subroutine print_header
  !
end subroutine gfits_load_header
!
subroutine gfits_setsort(fh,error)
  use gfits_dependencies_interfaces
  use gfits_interfaces, except_this=>gfits_setsort
  use gfits_types
  !---------------------------------------------------------------------
  ! @ private
  !  Compute the sorting list of the input fits header (fh%sort)
  !  Remark: fh%sort() must contain the unsorted elements before
  ! entering gi0_quicksort_index_with_user_gtge. It can be partially
  ! sorted, or not at all, this will work.
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(inout) :: fh
  logical,             intent(inout) :: error
  !
  call gi0_quicksort_index_with_user_gtge(fh%sort,fh%ncard,  &
    gfits_sort_gt,gfits_sort_ge,error)
  if (error)  return
  !
contains
  !
  ! Sorting functions: just sort the cards by key names
  function gfits_sort_gt(m,l)
    logical :: gfits_sort_gt
    integer(kind=4), intent(in) :: m,l
    gfits_sort_gt = lgt(fh%card(m)%key,fh%card(l)%key)
  end function gfits_sort_gt
  !
  function gfits_sort_ge(m,l)
    logical :: gfits_sort_ge
    integer(kind=4), intent(in) :: m,l
    gfits_sort_ge = lge(fh%card(m)%key,fh%card(l)%key)
  end function gfits_sort_ge
  !
end subroutine gfits_setsort
!
subroutine gfits_check_simple(card,error)
  use gbl_message
  use gfits_types
  use gfits_interfaces, except_this=>gfits_check_simple
  !---------------------------------------------------------------------
  ! @ public
  ! Sanity check of the SIMPLE card in FITS header. This is generic for
  ! any FITS file.
  !---------------------------------------------------------------------
  type(fits_unkn_0d_t), intent(in)    :: card   !
  logical,              intent(inout) :: error  !
  ! Local
  character(len=*), parameter :: rname='FITS'
  character(len=char0d_length) :: lval
  !
  lval = adjustl(card%val)  ! Insensitive to leading spaces, i.e. tolerate free format
  !
  if (card%key.ne.'SIMPLE') then
    call gfits_message(seve%e,rname,'First keyword is not SIMPLE (got '//trim(card%key)//')')
    error = .true.
    return
    !
  elseif (lval.eq.'F') then
    call gfits_message(seve%w,rname,'Not a SIMPLE FITS file, trying...')
    !
  elseif (lval.ne.'T') then
    call gfits_message(seve%e,rname,'Not a standard FITS file')
    error = .true.
    return
    !
  endif
  !
end subroutine gfits_check_simple
!
subroutine gfits_check_xtension(card,error,xkind)
  use gbl_message
  use gfits_types
  use gfits_interfaces, except_this=>gfits_check_xtension
  !---------------------------------------------------------------------
  ! @ public
  ! Sanity check of the XTENSION card in FITS header. This is generic
  ! for any FITS file. Optionally check for the XTENSION kind.
  !---------------------------------------------------------------------
  type(fits_unkn_0d_t),       intent(in)    :: card   !
  logical,                    intent(inout) :: error  !
  character(len=*), optional, intent(in)    :: xkind  ! Desired XTENSION kind
  ! Local
  character(len=*), parameter :: rname='FITS'
  character(len=80) :: value
  !
  if (card%key.ne.'XTENSION') then
    call gfits_message(seve%e,rname,'First keyword in extension is not XTENSION (got '//trim(card%key)//')')
    error = .true.
    return
  endif
  !
  if (present(xkind)) then
    value = gfits_unquote(card%val)
    if (value.ne.xkind) then
      call gfits_message(seve%e,rname,'XTENSION kind is not '//trim(xkind)//' (got '//trim(value)//')')
      error = .true.
      return
    endif
  endif
  !
end subroutine gfits_check_xtension
!
subroutine gfits_check_format(card,nbit,fmt,error)
  use gbl_format
  use gbl_message
  use gfits_types
  use gfits_interfaces, except_this=>gfits_check_format
  !---------------------------------------------------------------------
  ! @ public
  ! Translate BITPIX to internal format code. This is generic for any
  ! FITS file.
  !---------------------------------------------------------------------
  type(fits_unkn_0d_t), intent(in)    :: card   !
  integer(kind=4),      intent(out)   :: nbit   !
  integer(kind=4),      intent(out)   :: fmt    !
  logical,              intent(inout) :: error  !
  ! Local
  character(len=*), parameter :: rname='FITS'
  character(len=message_length) :: mess
  !
  if (card%key.ne.'BITPIX') then
    call gfits_message(seve%e,rname,'Second keyword is not BITPIX: '//card%key)
    error = .true.
    return
  endif
  !
  read(card%val,'(I20)') nbit
  select case (nbit)
  case (8)
    fmt = fmt_by
  case (16)
    fmt = eei_i2
  case (32)
    fmt = eei_i4
  case (-32)
    fmt = eei_r4
  case (-64)
    fmt = eei_r8
  case default
    write(mess,*) 'Unsupported BITPIX size: ',nbit
    call gfits_message(seve%e,rname,mess)
    error = .true.
    return
  end select
  !
end subroutine gfits_check_format
!
subroutine gfits_check_naxis(card,naxis,error)
  use gildas_def
  use gbl_message
  use gfits_types
  use gfits_interfaces, except_this=>gfits_check_naxis
  !---------------------------------------------------------------------
  ! @ public
  ! Translate NAXIS. This is generic for any FITS extension.
  !---------------------------------------------------------------------
  type(fits_unkn_0d_t), intent(in)    :: card   !
  integer(kind=4),      intent(out)   :: naxis  !
  logical,              intent(inout) :: error  !
  ! Local
  character(len=*), parameter :: rname='FITS'
  integer(kind=4) :: ier
  !
  if (card%key.ne.'NAXIS') then
    call gfits_message(seve%e,rname,'Third keyword is not NAXIS: '//card%key)
    error = .true.
    return
  endif
  !
  read(card%val,'(I20)',iostat=ier) naxis
  if (ier.ne.0) then
    call gfits_message(seve%e,rname,'Error decoding NAXIS = '//card%val)
    error = .true.
    return
  endif
  if (naxis.lt.0 .or. naxis.gt.sic_maxdims) then
    call gfits_message(seve%e,rname,'Unsupported NAXIS value: '//card%val)
    error = .true.
    return
  endif
  !
end subroutine gfits_check_naxis
!
subroutine gfits_check_naxisi(fhdict,dims,error)
  use gildas_def
  use gbl_message
  use gfits_types
  use gfits_interfaces, except_this=>gfits_check_naxisi
  !---------------------------------------------------------------------
  ! @ public
  ! Translate all the NAXISi. This is generic for any FITS extension.
  !---------------------------------------------------------------------
  type(gfits_hdict_t),        intent(in)    :: fhdict   !
  integer(kind=index_length), intent(out)   :: dims(:)  !
  logical,                    intent(inout) :: error    !
  ! Local
  character(len=*), parameter :: rname='FITS'
  integer(kind=4) :: iaxis
  character(len=6) :: key
  logical :: found
  !
  do iaxis=1,size(dims)
    write(key,'(A5,I1)')  'NAXIS',iaxis
    dims(iaxis) = 1  ! Default if missing (same as GDF default)
    call gfits_get_value(fhdict,key,found,dims(iaxis),error)
    if (error)  return
    !
    if (dims(iaxis).lt.0) then
      call gfits_message(seve%e,rname,'Unexpected '//trim(key)//' value')
      error = .true.
      return
    endif
    !
  enddo
  !
end subroutine gfits_check_naxisi
!
subroutine gfits_find_value(fh,key,found,value,error)
  use gfits_dependencies_interfaces
  use gfits_interfaces, except_this=>gfits_find_value
  use gfits_types
  !---------------------------------------------------------------------
  ! @ private
  ! Get the value in the dictionary from the given key. Output is the
  ! raw character string (not evaluated).
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(in)    :: fh
  character(len=*),    intent(in)    :: key
  logical,             intent(out)   :: found
  character(len=*),    intent(out)   :: value
  logical,             intent(inout) :: error
  ! Local
  integer(kind=4) :: icard
  !
  value = ''
  found = .false.
  !
  ! Unefficient access looping on all elements:
  ! do icard=1,fh%ncard
  !   if (fh%card(icard)%key.eq.key) then
  !     found = .true.
  !     value = fh%card(icard)%val
  !     return
  !   endif
  ! enddo
  !
  ! Efficient access using dichotomic search within the sorted cards
  if (llt(key,fh%card(fh%sort(1       ))%key)  .or.  &
      lgt(key,fh%card(fh%sort(fh%ncard))%key)) then
    ! Key is beyond the sorted cards list: nothing to find
    return
  endif
  call gi0_dicho_with_user_ltgt(fh%ncard,.false.,icard,  &
    find_key_lt,find_key_gt,error)
  if (error)  return
  ! Using floor mode, result satisfies X(ival) <= xval < X(ival+1)
  found = fh%card(fh%sort(icard))%key.eq.key
  if (found)  value = fh%card(fh%sort(icard))%val
  !
contains
  function find_key_lt(icard)
    logical :: find_key_lt
    integer(kind=4) :: icard
    find_key_lt = llt(fh%card(fh%sort(icard))%key,key)
  end function find_key_lt
  function find_key_gt(icard)
    logical :: find_key_gt
    integer(kind=4) :: icard
    find_key_gt = lgt(fh%card(fh%sort(icard))%key,key)
  end function find_key_gt
end subroutine gfits_find_value
!
subroutine gfits_get_char(fh,key,found,value,error)
  use gfits_interfaces, except_this=>gfits_get_char
  use gfits_types
  !---------------------------------------------------------------------
  ! @ public-generic gfits_get_value
  ! Get the value in the dictionary from the given key, and evaluate it
  ! as character string. Quotes are removed if any. The output value is
  ! not modified if its key is not found.
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(in)    :: fh
  character(len=*),    intent(in)    :: key
  logical,             intent(out)   :: found
  character(len=*),    intent(inout) :: value
  logical,             intent(inout) :: error
  ! Local
  character(len=80) :: string
  !
  call gfits_find_value(fh,key,found,string,error)
  if (error .or. .not.found)  return
  !
  value = gfits_unquote(string)
  !
end subroutine gfits_get_char
!
subroutine gfits_get_inte(fh,key,found,value,error)
  use gbl_message
  use gfits_dependencies_interfaces
  use gfits_interfaces, except_this=>gfits_get_inte
  use gfits_types
  !---------------------------------------------------------------------
  ! @ public-generic gfits_get_value
  ! Get the value in the dictionary from the given key, and evaluate it
  ! as an integer. The output value is not modified if its key is not
  ! found.
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(in)    :: fh
  character(len=*),    intent(in)    :: key
  logical,             intent(out)   :: found
  integer(kind=4),     intent(inout) :: value
  logical,             intent(inout) :: error
  ! Local
  integer(kind=4) :: ier
  character(len=80) :: string
  real(kind=8) :: tmp
  !
  call gfits_find_value(fh,key,found,string,error)
  if (error .or. .not.found)  return
  !
  read(string,'(I20)',iostat=ier)  value
  if (ier.eq.0)  return
  !
  ! Try to read as float, but must fit as integer afterwards
  read(string,*,iostat=ier)  tmp
  if (ier.ne.0) then
    call gfits_message(seve%e,'FITS','Error decoding '//trim(key)//' = '//trim(string)//' to I*4')
    error = .true.
    return
  endif
  !
  value = nint(tmp)
  if (.not.(nearly_equal(dble(value),tmp,1.d-6))) then
    call gfits_message(seve%e,'FITS','Error decoding '//trim(key)//' = '//trim(string)//' to I*4')
    error = .true.
    return
  endif
  !
end subroutine gfits_get_inte
!
subroutine gfits_get_long(fh,key,found,value,error)
  use gbl_message
  use gfits_dependencies_interfaces
  use gfits_interfaces, except_this=>gfits_get_long
  use gfits_types
  !---------------------------------------------------------------------
  ! @ public-generic gfits_get_value
  ! Get the value in the dictionary from the given key, and evaluate it
  ! as long integer. The output value is not modified if its key is not
  ! found.
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(in)    :: fh
  character(len=*),    intent(in)    :: key
  logical,             intent(out)   :: found
  integer(kind=8),     intent(inout) :: value
  logical,             intent(inout) :: error
  ! Local
  integer(kind=4) :: ier
  character(len=80) :: string
  real(kind=8) :: tmp
  !
  call gfits_find_value(fh,key,found,string,error)
  if (error .or. .not.found)  return
  !
  read(string,'(I20)',iostat=ier)  value
  if (ier.eq.0)  return
  !
  ! Try to read as float, but must fit as integer afterwards
  read(string,*,iostat=ier)  tmp
  if (ier.ne.0) then
    call gfits_message(seve%e,'FITS','Error decoding '//trim(key)//' = '//trim(string)//' to I*8')
    error = .true.
    return
  endif
  !
  value = nint(tmp)
  if (.not.(nearly_equal(dble(value),tmp,1.d-6))) then
    call gfits_message(seve%e,'FITS','Error decoding '//trim(key)//' = '//trim(string)//' to I*8')
    error = .true.
    return
  endif
  !
end subroutine gfits_get_long
!
subroutine gfits_get_real(fh,key,found,value,error)
  use gbl_message
  use gfits_interfaces, except_this=>gfits_get_real
  use gfits_types
  !---------------------------------------------------------------------
  ! @ public-generic gfits_get_value
  ! Get the value in the dictionary from the given key, and evaluate it
  ! as single precision float. The output value is not modified if its
  ! key is not found.
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(in)    :: fh
  character(len=*),    intent(in)    :: key
  logical,             intent(out)   :: found
  real(kind=4),        intent(inout) :: value
  logical,             intent(inout) :: error
  ! Local
  integer(kind=4) :: ier
  character(len=80) :: string
  !
  call gfits_find_value(fh,key,found,string,error)
  if (error .or. .not.found)  return
  !
  read(string,*,iostat=ier)  value
  if (ier.ne.0) then
    call gfits_message(seve%e,'FITS','Error decoding '//trim(key)//' = '//trim(string)//' to R*4')
    error = .true.
    return
  endif
  !
end subroutine gfits_get_real
!
subroutine gfits_get_dble(fh,key,found,value,error)
  use gbl_message
  use gfits_interfaces, except_this=>gfits_get_dble
  use gfits_types
  !---------------------------------------------------------------------
  ! @ public-generic gfits_get_value
  ! Get the value in the dictionary from the given key, and evaluate it
  ! as double precision float. The output value is not modified if its
  ! key is not found.
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(in)    :: fh
  character(len=*),    intent(in)    :: key
  logical,             intent(out)   :: found
  real(kind=8),        intent(inout) :: value
  logical,             intent(inout) :: error
  ! Local
  integer(kind=4) :: ier
  character(len=80) :: string
  !
  call gfits_find_value(fh,key,found,string,error)
  if (error .or. .not.found)  return
  !
  read(string,*,iostat=ier)  value
  if (ier.ne.0) then
    call gfits_message(seve%e,'FITS','Error decoding '//trim(key)//' = '//trim(string)//' to R*8')
    error = .true.
    return
  endif
  !
end subroutine gfits_get_dble
!
subroutine gfits_get_logi(fh,key,found,value,error)
  use gbl_message
  use gfits_interfaces, except_this=>gfits_get_logi
  use gfits_types
  !---------------------------------------------------------------------
  ! @ public-generic gfits_get_value
  ! Get the value in the dictionary from the given key, and evaluate it
  ! as a logical. The output value is not modified if its key is not
  ! found.
  !---------------------------------------------------------------------
  type(gfits_hdict_t), intent(in)    :: fh
  character(len=*),    intent(in)    :: key
  logical,             intent(out)   :: found
  logical,             intent(inout) :: value
  logical,             intent(inout) :: error
  ! Local
  integer(kind=4) :: ier
  character(len=80) :: string
  !
  call gfits_find_value(fh,key,found,string,error)
  if (error .or. .not.found)  return
  !
  read(string,*,iostat=ier)  value
  if (ier.ne.0) then
    call gfits_message(seve%e,'FITS','Error decoding '//trim(key)//' = '//trim(string)//' to logical')
    error = .true.
    return
  endif
  !
end subroutine gfits_get_logi
!
subroutine gfits_getnosymbol(symb,tran,error)
  !---------------------------------------------------------------------
  ! @ public-mandatory (because symbol is used elsewhere)
  ! No symbol translation. 2 main use cases:
  ! - no symbol dictionary available in the current context, or
  ! - user should not be able to modify the critical elements (e.g.
  !   NAXIS*) needed in the context
  !---------------------------------------------------------------------
  character(len=*), intent(in)  :: symb   ! Symbol name
  character(len=*), intent(in)  :: tran   ! Symbol translation
  logical,          intent(out) :: error  ! Logical error flag
  error = .true.
end subroutine gfits_getnosymbol
