module cubedag_find
  use gkernel_interfaces
  use cubedag_parameters
  use cubedag_flag
  use cubedag_link_type
  use cubedag_node_type
  use cubedag_messaging
  use cubedag_dag

  type user_find_t
     ! Type used for parsing FIND options. Get arguments as character strings
     ! (because strg_star is often accepted), interpret them elsewhere
     character(len=12)     :: centr(2) = strg_star  ! Entry range
     character(len=12)     :: ciden    = strg_star  ! Identifier
     character(len=12)     :: cobse    = strg_star  ! Observatory name
     character(len=12)     :: csour    = strg_star  ! Source name
     character(len=12)     :: cline    = strg_star  ! Line name
     character(len=base_l) :: cfami    = strg_star  ! Family name
     character(len=32)     :: cfreq    = strg_star  ! Frequency value
   ! To be reimplemented to support flag_t, if relevant
   ! character(len=12)     :: ciflag   = strg_star  ! Flag (string for flag name)
     character(len=flag_l) :: ccflag   = strg_star  ! Flag (character string/pattern)
  end type user_find_t

  integer(kind=entr_k), parameter :: minentr=1_entr_k
  integer(kind=entr_k), parameter :: maxentr=huge(1_entr_k)
  type :: cubedag_find_t
    !
    logical               :: lentr = .false.     ! Selection by entry range enabled?
    integer(kind=entr_k)  :: ientr(2) = [minentr,maxentr]  ! Desired entry range
    !
    logical               :: liden = .false.     ! Selection by identifier?
    integer(kind=iden_l)  :: iiden = -1          ! Desired identifier, if relevant
    !
    logical               :: lobse = .false.     ! Selection by cobse enabled?
    character(len=12)     :: cobse = strg_star   ! Desired observatory name, if relevant
    !
    logical               :: lsour = .false.     ! Selection by csour enabled?
    character(len=12)     :: csour = strg_star   ! Desired source name, if relevant
    !
    logical               :: lline = .false.     ! Selection by cline enabled?
    character(len=12)     :: cline = strg_star   ! Desired line name, if relevant
    !
    logical               :: lfami = .false.     ! Selection by cfami enabled?
    character(len=base_l) :: cfami = strg_star   ! Desired family  name, if relevant
    !
    logical               :: liflag  = .false.   ! Selection by iflag (flag_t) enabled?
    type(flag_t), allocatable :: iflags(:)       ! Desired flags, if relevant
    !
    logical               :: lcflag = .false.    ! Selection by ccflag (character string/pattern) enabled?
    character(len=base_l) :: ccflag = strg_star  ! Desired flag(s) name, if relevant
    !
    logical               :: lfreq = .false.     ! Selection by cfreq enabled?
    real(kind=coor_k)     :: rfreq = 0.d0        ! Desired frequency, if relevant
  end type cubedag_find_t

  interface cubedag_find_ix2optx
    module procedure cubedag_find_ix2optx_bycriter
  end interface cubedag_find_ix2optx

  public :: cubedag_find_command,cubedag_find_t,user_find_t
  public :: cubedag_find_ix2cx,cubedag_find_cx2optx,cubedag_find_ix2optx
  private

contains
  !
  subroutine cubedag_find_command(fuser,error)
    !---------------------------------------------------------------------
    !
    !---------------------------------------------------------------------
    type(user_find_t), intent(in)    :: fuser
    logical,           intent(inout) :: error  ! Logical error flag
    ! Local
    character(len=*), parameter :: rname='FIND'
    type(cubedag_find_t) :: criter
    !
    if (ix%n.le.1)  &  ! Ignore root
      call cubedag_message(seve%w,rname,'Input index is empty')
    !
    ! --- Fill CX from selection criteria --------------------------------
    call cubedag_find_criter(fuser,criter,error)
    if (error)  return
    !
    call cubedag_find_ix2cx(criter,error)
    if (error)  return
  end subroutine cubedag_find_command
  !
  subroutine cubedag_find_criter(user,criter,error)
    use cubetools_disambiguate
    use cubetools_user2prog
    use cubetools_unit
    type(user_find_t),    intent(in)    :: user
    type(cubedag_find_t), intent(out)   :: criter
    logical,              intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='FIND>CRITER'
    ! character(len=32) :: keyword
    type(flag_t) :: iflag
    integer(kind=entr_k) :: tmp
    integer(kind=4) :: ier
    type(unit_user_t) :: frequnit
    !
    ! /ENTRY [min|*] [max|*]
    call cubetools_user2prog_resolve_star(user%centr(1),minentr,criter%ientr(1),error)
    if (error)  return
    call cubetools_user2prog_resolve_star(user%centr(2),maxentr,criter%ientr(2),error)
    if (error)  return
    if (criter%ientr(1).gt.criter%ientr(2)) then
      tmp = criter%ientr(1)
      criter%ientr(1) = criter%ientr(2)
      criter%ientr(2) = tmp
    endif
    !
    ! /IDENTIFIER [number|*]
    call cubetools_user2prog_resolve_star(user%ciden,-1,criter%iiden,error)
    if (error)  return
    !
    ! /OBSERVATORY [obsname|*] ! case insensitive
    call cubetools_disambiguate_toupper(user%cobse,criter%cobse,error)
    if (error) return
    !
    ! /SOURCE [sourcename|*] ! case insensitive
    call cubetools_disambiguate_toupper(user%csour,criter%csour,error)
    if (error) return
    !
    ! /LINE [linename|*] ! case insensitive
    call cubetools_disambiguate_toupper(user%cline,criter%cline,error)
    if (error) return
    !
    ! /FAMILY [familyname|*]
    criter%cfami = user%cfami
    !
    ! FLAG [flagkeyword|*]  ! ZZZ No syntax to provide several flag codes
!   if (user%ciflag.eq.strg_star) then
      iflag = flag_any
!   else
!     call cubetools_disambiguate_strict(user%ciflag,dag_flag_keys,iflag,keyword,error)
!     if (error)  return
!   endif
    allocate(criter%iflags(1),stat=ier)
    if (failed_allocate(rname,'iflags',ier,error)) return
    criter%iflags(1) = iflag
    !
    ! /FLAG [flagpattern|*]
    criter%ccflag = user%ccflag
    !
    ! /FREQUENCY [freqvalue|*]
    call frequnit%get_from_code(code_unit_freq,error)
    if (error)  return
    call cubetools_user2prog_resolve_star(user%cfreq,frequnit,0.d0,criter%rfreq,error)
    if (error)  return
  end subroutine cubedag_find_criter

  subroutine cubedag_find_lcriter(criter,error)
    type(cubedag_find_t), intent(inout) :: criter
    logical,              intent(inout) :: error
    !
    criter%lentr = criter%ientr(1).gt.minentr .or. criter%ientr(2).lt.maxentr
    criter%liden = criter%iiden.ge.0
    criter%lobse = criter%cobse.ne.strg_star
    criter%lsour = criter%csour.ne.strg_star
    criter%lline = criter%cline.ne.strg_star
    criter%lfami = criter%cfami.ne.strg_star
    if (allocated(criter%iflags)) then
      if (size(criter%iflags).eq.1 .and. criter%iflags(1).eq.flag_any) then
        criter%liflag = .false.
      else
        criter%liflag = .true.
      endif
    else
      criter%liflag = .false.
    endif
    criter%lcflag = criter%ccflag.ne.strg_star
    criter%lfreq = criter%rfreq.gt.0.d0
  end subroutine cubedag_find_lcriter

  subroutine cubedag_find_ix2cx(criter,error)
    type(cubedag_find_t), intent(inout) :: criter
    logical,              intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='FIND'
    character(len=message_length) :: mess
    !
    call cubedag_find_bycriter(criter,ix,cx,error)
    if (error)  return
    write(mess,'(I0,A)')  cx%n,' entries in Current indeX'
    call cubedag_message(seve%i,rname,mess)
  end subroutine cubedag_find_ix2cx

  subroutine cubedag_find_ix2optx_bycriter(criter,out,error)
    type(cubedag_find_t), intent(inout) :: criter
    type(cubedag_link_t), intent(inout) :: out
    logical,              intent(inout) :: error
    !
    call cubedag_find_bycriter(criter,ix,out,error)
    if (error)  return
  end subroutine cubedag_find_ix2optx_bycriter

  subroutine cubedag_find_cx2optx(criter,out,error)
    type(cubedag_find_t), intent(inout) :: criter
    type(cubedag_link_t), intent(inout) :: out
    logical,              intent(inout) :: error
    !
    call cubedag_find_bycriter(criter,cx,out,error)
    if (error)  return
  end subroutine cubedag_find_cx2optx
  !
  subroutine cubedag_find_bycriter(criter,in,out,error)
    use cubedag_allflags
    use cubedag_node
    type(cubedag_find_t), intent(inout) :: criter  !
    type(cubedag_link_t), intent(in)    :: in      !
    type(cubedag_link_t), intent(inout) :: out     !
    logical,              intent(inout) :: error   !
    ! Local
    character(len=*), parameter :: rname='FIND'
    integer(kind=entr_k) :: ient,nfound,nfoundall
    logical :: found
    integer(kind=4) :: iflag,iteles
    integer(kind=entr_k) :: list(in%n) ! Automatic array (assume in%n>0)
    class(cubedag_node_object_t), pointer :: obj
    character(len=256) :: nodeflag
    type(flag_t), pointer :: flag
    character(len=sour_l) :: mysource
    character(len=line_l) :: myline
    character(len=tele_l) :: myteles
    !
    call cubedag_find_lcriter(criter,error)
    if (error)  return
    !
    nfound = 0
    nfoundall = 0
    do ient=1,in%n
      obj => cubedag_node_ptr(in%list(ient)%p,error)
      if (error)  return
      !
      ! Do not find pure-node object, which are irrelevant (no data provided)
      if (obj%node%type.eq.code_type_node)  cycle
      !
      if (criter%liden) then
        if (obj%node%id.ne.criter%iiden) cycle
      endif
      !
      if (criter%lsour) then
        mysource = obj%node%head%spatial_source
        call sic_upper(mysource)
        if (.not.match_string(mysource,criter%csour)) cycle
      endif
      !
      if (criter%lline) then
        myline = obj%node%head%spectral_line
        call sic_upper(myline)
        if (.not.match_string(myline,criter%cline)) cycle
      endif
      !
      if (criter%lfami) then
        if (.not.match_string(obj%node%family,criter%cfami)) cycle
      endif
      !
      if (criter%lobse) then
        found = .false.
        do iteles=1,obj%node%head%obs%ntel
          myteles = obj%node%head%obs%tel(iteles)%name
          call sic_upper(myteles)
          if (match_string(myteles,criter%cobse)) then
            found = .true.
            exit
          endif
        enddo
        if (.not.found)  cycle
      endif
      !
      if (criter%liflag) then
        if (size(criter%iflags).ne.obj%node%flag%n)  cycle
        !
        found = .true.
        do iflag=1,obj%node%flag%n
          if (criter%iflags(iflag).eq.flag_any)  cycle
          flag => cubedag_flag_ptr(obj%node%flag%list(iflag)%p,error)
          if (error)  return
          if (criter%iflags(iflag).ne.flag) then
            found = .false.
            exit
          endif
        enddo
        if (.not.found)  cycle
      endif
      !
      if (criter%lcflag) then
        call obj%node%flag%repr(strflag=nodeflag,error=error)
        if (error)  return
        if (.not.match_string(nodeflag,criter%ccflag)) cycle
      endif
      !
      if (criter%lfreq) then
        if (.not.cubedag_find_byfreq(obj,criter%rfreq,error))  cycle
        if (error)  return
      endif
      !
      ! Must come last
      if (criter%lentr) then
        nfoundall = nfoundall+1
        if (nfoundall.lt.criter%ientr(1))  cycle
        if (nfoundall.gt.criter%ientr(2))  cycle
      endif
      !
      nfound = nfound+1
      list(nfound) = ient
    enddo
    !
    call cubedag_find_byentries(in,list,nfound,out,error)
    if (error)  return
    !
  end subroutine cubedag_find_bycriter
  !
  function cubedag_find_byfreq(obj,freq,error)
    use cubetools_header_types
    !-------------------------------------------------------------------
    ! Return .true. of the node covers the given frequency
    !-------------------------------------------------------------------
    logical :: cubedag_find_byfreq  ! Function value on return
    class(cubedag_node_object_t), intent(in)    :: obj
    real(kind=8),                 intent(in)    :: freq
    logical,                      intent(inout) :: error
    !
    type(cube_header_t) :: head
    !
    cubedag_find_byfreq = .false.
    !
    if (.not.associated(obj%node%head))  return  ! No header => no match
    !
    ! Convert interface to header for simplicity
    call head%init(error)
    if (error)  return
    call cubetools_header_import_and_derive(obj%node%head,head,error)
    if (error)  return
    cubedag_find_byfreq = head%spe%f%inside(freq)
    call cubetools_header_final(head,error)
    if (error)  return
  end function cubedag_find_byfreq
  !
  subroutine cubedag_find_byentries(in,entries,nentries,out,error)
    !-------------------------------------------------------------------
    ! FIND by list of entry numbers
    !-------------------------------------------------------------------
    type(cubedag_link_t), intent(in)    :: in          !
    integer(kind=entr_k), intent(in)    :: entries(:)  ! Entry numbers
    integer(kind=entr_k), intent(in)    :: nentries    !
    type(cubedag_link_t), intent(inout) :: out         !
    logical,              intent(inout) :: error       !
    ! Local
    integer(kind=entr_k) :: ient
    !
    call out%reallocate(nentries,error)
    if (error)  return
    !
    out%n = 0
    do ient=1,nentries
      out%n = out%n+1
      out%list(out%n)%p => in%list(entries(ient))%p
      out%flag(out%n)   =  in%flag(entries(ient))
    enddo
  end subroutine cubedag_find_byentries
  !
end module cubedag_find
