!   
!                Parallel Sparse BLAS  version 3.5
!      (C) Copyright 2006-2018
!        Salvatore Filippone    
!        Alfredo Buttari      
!   
!    Redistribution and use in source and binary forms, with or without
!    modification, are permitted provided that the following conditions
!    are met:
!      1. Redistributions of source code must retain the above copyright
!         notice, this list of conditions and the following disclaimer.
!      2. Redistributions in binary form must reproduce the above copyright
!         notice, this list of conditions, and the following disclaimer in the
!         documentation and/or other materials provided with the distribution.
!      3. The name of the PSBLAS group or the names of its contributors may
!         not be used to endorse or promote products derived from this
!         software without specific written permission.
!   
!    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
!    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
!    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
!    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PSBLAS GROUP OR ITS CONTRIBUTORS
!    BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
!    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
!    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
!    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
!    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
!    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
!    POSSIBILITY OF SUCH DAMAGE.
!   
!    
!
!
! package: psb_hash_map_mod
!    Defines the HASH_MAP type.
!
! This is the index map of choice for large index spaces. 
!  If the global index space is very large (larger than the threshold value
!  which may be set by the user), then it is not advisable to have a full
!  GLOB_TO_LOC array; therefore we only record the global indices that do have a 
!  local counterpart, so that the local storage will be proportional to 
!  N_COL.
!  The idea is that  glb_lc(:,1) will hold sorted global indices, and
!  glb_lc(:,2) the corresponding local indices, so that we may do a binary search.
!  To cut down  the search time we partition glb_lc into a set of lists
!  addressed by  hashv(:) based on the value of the lowest
!  PSB_HASH_BITS bits of the  global index. 
!  During the build phase glb_lc() will store the indices of the internal points,
!  i.e. local indices 1:NROW, since those are known ad CDALL time.
!  The halo indices that we encounter during the build phase are put in
!  a PSB_HASH_TYPE data structure, which implements a very simple hash; this
!  hash  will nonetheless be quite fast at low occupancy rates.
!  At assembly time, we move everything into hashv(:) and glb_lc(:,:).
!
module psb_hash_map_mod
  use psb_const_mod
  use psb_desc_const_mod
  use psb_indx_map_mod
  use psb_hash_mod 
  
  type, extends(psb_indx_map) :: psb_hash_map

    integer(psb_ipk_) :: hashvsize, hashvmask
    integer(psb_ipk_), allocatable :: hashv(:), glb_lc(:,:), loc_to_glob(:)
    type(psb_hash_type)  :: hash

  contains

    procedure, pass(idxmap)  :: init_vl    => hash_init_vl
    procedure, pass(idxmap)  :: hash_map_init => hash_init_vg

    procedure, pass(idxmap)  :: sizeof    => hash_sizeof
    procedure, pass(idxmap)  :: asb       => hash_asb
    procedure, pass(idxmap)  :: free      => hash_free
    procedure, pass(idxmap)  :: clone     => hash_clone
    procedure, pass(idxmap)  :: reinit    => hash_reinit
    procedure, nopass        :: get_fmt   => hash_get_fmt

    procedure, nopass        :: row_extendable => hash_row_extendable

    procedure, pass(idxmap)  :: l2gs1     => hash_l2gs1
    procedure, pass(idxmap)  :: l2gs2     => hash_l2gs2
    procedure, pass(idxmap)  :: l2gv1     => hash_l2gv1
    procedure, pass(idxmap)  :: l2gv2     => hash_l2gv2

    procedure, pass(idxmap)  :: g2ls1     => hash_g2ls1
    procedure, pass(idxmap)  :: g2ls2     => hash_g2ls2
    procedure, pass(idxmap)  :: g2lv1     => hash_g2lv1
    procedure, pass(idxmap)  :: g2lv2     => hash_g2lv2

    procedure, pass(idxmap)  :: g2ls1_ins => hash_g2ls1_ins
    procedure, pass(idxmap)  :: g2ls2_ins => hash_g2ls2_ins
    procedure, pass(idxmap)  :: g2lv1_ins => hash_g2lv1_ins
    procedure, pass(idxmap)  :: g2lv2_ins => hash_g2lv2_ins

    procedure, pass(idxmap)  :: hash_cpy
    generic, public          :: assignment(=) => hash_cpy
    procedure, pass(idxmap)  :: bld_g2l_map => hash_bld_g2l_map

  end type psb_hash_map

  private :: hash_init_vl, hash_init_vg, hash_sizeof, hash_asb, &
       & hash_free, hash_get_fmt, hash_l2gs1, hash_l2gs2, &
       & hash_l2gv1, hash_l2gv2, hash_g2ls1, hash_g2ls2, &
       & hash_g2lv1, hash_g2lv2, hash_g2ls1_ins, hash_g2ls2_ins, &
       & hash_g2lv1_ins, hash_g2lv2_ins, hash_init_vlu, &
       & hash_bld_g2l_map,  hash_inner_cnvs1, hash_inner_cnvs2,&
       & hash_inner_cnv1, hash_inner_cnv2, hash_row_extendable 

  integer(psb_ipk_), private :: laddsz=500

  interface hash_inner_cnv 
    module procedure  hash_inner_cnvs1, hash_inner_cnvs2,&
         & hash_inner_cnv1, hash_inner_cnv2 
  end interface hash_inner_cnv
  private :: hash_inner_cnv

contains

  function hash_row_extendable() result(val)
    implicit none 
    logical :: val
    val = .true.
  end function hash_row_extendable

  function hash_sizeof(idxmap) result(val)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_long_int_k_) :: val

    val = idxmap%psb_indx_map%sizeof() 
    val = val + 2 * psb_sizeof_int
    if (allocated(idxmap%hashv)) &
         & val = val + size(idxmap%hashv)*psb_sizeof_int
    if (allocated(idxmap%glb_lc)) &
         & val = val + size(idxmap%glb_lc)*psb_sizeof_int
    if (allocated(idxmap%loc_to_glob)) &
         & val = val + size(idxmap%loc_to_glob)*psb_sizeof_int
    val = val + psb_sizeof(idxmap%hash)

  end function hash_sizeof


  subroutine hash_free(idxmap)
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_ipk_) :: info

    if (allocated(idxmap%hashv)) &
         & deallocate(idxmap%hashv)
    if (allocated(idxmap%glb_lc)) &
         & deallocate(idxmap%glb_lc)
    call psb_free(idxmap%hash,info) 
    call idxmap%psb_indx_map%free()

  end subroutine hash_free


  subroutine hash_l2gs1(idx,idxmap,info,mask,owned)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(inout) :: idx
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask
    logical, intent(in), optional :: owned
    integer(psb_ipk_) :: idxv(1)
    info = 0
    if (present(mask)) then 
      if (.not.mask) return
    end if

    idxv(1) = idx
    call idxmap%l2gip(idxv,info,owned=owned)
    idx = idxv(1)

  end subroutine hash_l2gs1

  subroutine hash_l2gs2(idxin,idxout,idxmap,info,mask,owned)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(in)    :: idxin
    integer(psb_ipk_), intent(out)   :: idxout
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask
    logical, intent(in), optional :: owned

    idxout = idxin
    call idxmap%l2gip(idxout,info,mask,owned)

  end subroutine hash_l2gs2


  subroutine hash_l2gv1(idx,idxmap,info,mask,owned)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(inout) :: idx(:)
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask(:)
    logical, intent(in), optional :: owned
    integer(psb_ipk_) :: i
    logical :: owned_
    info = 0

    if (present(mask)) then 
      if (size(mask) < size(idx)) then 
        info = -1
        return
      end if
    end if
    if (present(owned)) then 
      owned_ = owned
    else
      owned_ = .false.
    end if

    if (present(mask)) then 

      do i=1, size(idx)
        if (mask(i)) then 
          if ((1<=idx(i)).and.(idx(i) <= idxmap%local_rows)) then
            idx(i) = idxmap%loc_to_glob(idx(i))
          else if ((idxmap%local_rows < idx(i)).and.(idx(i) <= idxmap%local_cols)&
               & .and.(.not.owned_)) then
            idx(i) = idxmap%loc_to_glob(idx(i))
          else 
            idx(i) = -1
          end if
        end if
      end do

    else  if (.not.present(mask)) then 

      do i=1, size(idx)
        if ((1<=idx(i)).and.(idx(i) <= idxmap%local_rows)) then
          idx(i) = idxmap%loc_to_glob(idx(i))
        else if ((idxmap%local_rows < idx(i)).and.(idx(i) <= idxmap%local_cols)&
             & .and.(.not.owned_)) then
          idx(i) = idxmap%loc_to_glob(idx(i))
        else 
          idx(i) = -1
        end if
      end do

    end if

  end subroutine hash_l2gv1

  subroutine hash_l2gv2(idxin,idxout,idxmap,info,mask,owned)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(in)    :: idxin(:)
    integer(psb_ipk_), intent(out)   :: idxout(:)
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask(:)
    logical, intent(in), optional :: owned
    integer(psb_ipk_) :: is, im

    is = size(idxin)
    im = min(is,size(idxout))
    idxout(1:im) = idxin(1:im)
    call idxmap%l2gip(idxout(1:im),info,mask,owned)
    if (is > im) then 
      write(0,*) 'l2gv2 err -3'
      info = -3 
    end if

  end subroutine hash_l2gv2


  subroutine hash_g2ls1(idx,idxmap,info,mask,owned)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(inout) :: idx
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask
    logical, intent(in), optional :: owned
    integer(psb_ipk_) :: idxv(1)
    info = 0

    if (present(mask)) then 
      if (.not.mask) return
    end if

    idxv(1) = idx 
    call idxmap%g2lip(idxv,info,owned=owned)
    idx = idxv(1) 

  end subroutine hash_g2ls1

  subroutine hash_g2ls2(idxin,idxout,idxmap,info,mask,owned)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(in)    :: idxin
    integer(psb_ipk_), intent(out)   :: idxout
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask
    logical, intent(in), optional :: owned

    idxout = idxin
    call idxmap%g2lip(idxout,info,mask,owned)

  end subroutine hash_g2ls2


  subroutine hash_g2lv1(idx,idxmap,info,mask,owned)
    use psb_penv_mod
    use psb_sort_mod
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(inout) :: idx(:)
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask(:)
    logical, intent(in), optional :: owned
    integer(psb_ipk_) :: i, is, mglob, ip, lip, nrow, ncol, nrm 
    integer(psb_mpik_) :: ictxt, iam, np
    logical :: owned_

    info = 0
    ictxt = idxmap%get_ctxt()
    call psb_info(ictxt,iam,np) 

    if (present(mask)) then 
      if (size(mask) < size(idx)) then 
        info = -1
        return
      end if
    end if
    if (present(owned)) then 
      owned_ = owned
    else
      owned_ = .false.
    end if

    is = size(idx)

    mglob = idxmap%get_gr()
    nrow  = idxmap%get_lr()
    ncol  = idxmap%get_lc()
    if (owned_) then 
      nrm = nrow
    else
      nrm = ncol
    end if
    if (present(mask)) then 

      if (idxmap%is_asb()) then 

        call hash_inner_cnv(is,idx,idxmap%hashvmask,&
             & idxmap%hashv,idxmap%glb_lc,mask=mask, nrm=nrm)

      else if (idxmap%is_valid()) then 

        do i = 1, is
          if (mask(i)) then 
            ip = idx(i) 
            if ((ip < 1 ).or.(ip>mglob)) then 
              idx(i) = -1
              cycle
            endif
            call hash_inner_cnv(ip,lip,idxmap%hashvmask,idxmap%hashv,idxmap%glb_lc,nrm)
            if (lip < 0) &
                 &  call psb_hash_searchkey(ip,lip,idxmap%hash,info)
            if (owned_) then 
              if (lip<=nrow) then 
                idx(i) = lip
              else 
                idx(i) = -1
              endif
            else
              idx(i) = lip
            endif
          end if
        enddo

      else 
        write(0,*) 'Hash status: invalid ',idxmap%get_state()
        idx(1:is) = -1
        info = -1
      end if

    else  if (.not.present(mask)) then 

      if (idxmap%is_asb()) then 

        call hash_inner_cnv(is,idx,idxmap%hashvmask,&
             & idxmap%hashv,idxmap%glb_lc,nrm=nrm)

      else if (idxmap%is_valid()) then 

        do i = 1, is
          ip = idx(i) 
          if ((ip < 1 ).or.(ip>mglob)) then 
            idx(i) = -1
            cycle
          endif
          call hash_inner_cnv(ip,lip,idxmap%hashvmask,idxmap%hashv,idxmap%glb_lc,nrm)
          if (lip < 0) &
               &  call psb_hash_searchkey(ip,lip,idxmap%hash,info)
          if (owned_) then 
            if (lip<=nrow) then 
              idx(i) = lip
            else 
              idx(i) = -1
            endif
          else
            idx(i) = lip
          endif
        enddo

      else 
        write(0,*) 'Hash status: invalid ',idxmap%get_state()
        idx(1:is) = -1
        info = -1

      end if

    end if

  end subroutine hash_g2lv1

  subroutine hash_g2lv2(idxin,idxout,idxmap,info,mask,owned)
    implicit none 
    class(psb_hash_map), intent(in) :: idxmap
    integer(psb_ipk_), intent(in)    :: idxin(:)
    integer(psb_ipk_), intent(out)   :: idxout(:)
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask(:)
    logical, intent(in), optional :: owned

    integer(psb_ipk_) :: is, im

    is = size(idxin)
    im = min(is,size(idxout))
    idxout(1:im) = idxin(1:im)
    call idxmap%g2lip(idxout(1:im),info,mask,owned)
    if (is > im) then 
      write(0,*) 'g2lv2 err -3'
      info = -3 
    end if

  end subroutine hash_g2lv2



  subroutine hash_g2ls1_ins(idx,idxmap,info,mask,lidx)
    use psb_realloc_mod
    use psb_sort_mod
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_ipk_), intent(inout) :: idx
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask
    integer(psb_ipk_), intent(in), optional :: lidx

    integer(psb_ipk_) :: idxv(1), lidxv(1)

    info = 0
    if (present(mask)) then 
      if (.not.mask) return
    end if

    idxv(1) = idx
    if (present(lidx)) then 
      lidxv(1) = lidx
      call idxmap%g2lip_ins(idxv,info,lidx=lidxv)
    else
      call idxmap%g2lip_ins(idxv,info)
    end if
    idx = idxv(1) 

  end subroutine hash_g2ls1_ins

  subroutine hash_g2ls2_ins(idxin,idxout,idxmap,info,mask,lidx)
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_ipk_), intent(in)    :: idxin
    integer(psb_ipk_), intent(out)   :: idxout
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask
    integer(psb_ipk_), intent(in), optional :: lidx


    idxout = idxin
    call idxmap%g2lip_ins(idxout,info,mask=mask,lidx=lidx)

  end subroutine hash_g2ls2_ins


  subroutine hash_g2lv1_ins(idx,idxmap,info,mask,lidx)
    use psb_error_mod
    use psb_realloc_mod
    use psb_sort_mod
    use psb_penv_mod
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_ipk_), intent(inout) :: idx(:)
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional    :: mask(:)
    integer(psb_ipk_), intent(in), optional    :: lidx(:)

    integer(psb_ipk_) :: i, is, mglob, ip, lip, nrow, ncol, &
         & nxt, err_act
    integer(psb_ipk_) :: ictxt, me, np
    character(len=20)  :: name,ch_err

    info = psb_success_
    name = 'hash_g2l_ins'
    call psb_erractionsave(err_act)

    ictxt = idxmap%get_ctxt()
    call psb_info(ictxt, me, np)

    is = size(idx)

    if (present(mask)) then 
      if (size(mask) < size(idx)) then 
        info = -1
        return
      end if
    end if
    if (present(lidx)) then 
      if (size(lidx) < size(idx)) then 
        info = -1
        return
      end if
    end if


    mglob = idxmap%get_gr()
    nrow  = idxmap%get_lr()
    if (idxmap%is_bld()) then 

      if (present(lidx)) then
        if (present(mask)) then 
          do i = 1, is
            ncol  = idxmap%get_lc()
            if (mask(i)) then 
              ip = idx(i) 
              if ((ip < 1 ).or.(ip>mglob) ) then 
                idx(i) = -1
                cycle
              endif
              call hash_inner_cnv(ip,lip,idxmap%hashvmask,idxmap%hashv,idxmap%glb_lc,ncol)
              if (lip < 0) then 
                nxt = lidx(i)
                if (nxt <= nrow) then 
                  idx(i) = -1
                  cycle
                endif
                call psb_hash_searchinskey(ip,lip,nxt,idxmap%hash,info)
                if (info >=0) then 
                  if (nxt == lip) then 
                    ncol = max(ncol,nxt)
                    call psb_ensure_size(ncol,idxmap%loc_to_glob,info,pad=-ione,addsz=laddsz)
                    if (info /= psb_success_) then
                      info=1
                      ch_err='psb_ensure_size'
                      call psb_errpush(psb_err_from_subroutine_ai_,name,&
                           &a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
                      goto 9999
                    end if
                    idxmap%loc_to_glob(nxt)  = ip
                    call idxmap%set_lc(ncol)
                  endif
                  info = psb_success_
                else
                  ch_err='SearchInsKeyVal'
                  call psb_errpush(psb_err_from_subroutine_ai_,name,&
                       & a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
                  goto 9999
                end if
              end if
              idx(i) = lip
              info = psb_success_
            else
              idx(i) = -1
            end if
          enddo

        else if (.not.present(mask)) then 

          do i = 1, is
            ncol  = idxmap%get_lc()
            ip    = idx(i) 
            if ((ip < 1 ).or.(ip>mglob)) then 
              idx(i) = -1
              cycle
            endif
            call hash_inner_cnv(ip,lip,idxmap%hashvmask,idxmap%hashv,idxmap%glb_lc,ncol)
            if (lip < 0) then 
              nxt = lidx(i)
              if (nxt <= nrow) then 
                idx(i) = -1
                cycle
              endif
              call psb_hash_searchinskey(ip,lip,nxt,idxmap%hash,info)

              if (info >=0) then 
                if (nxt == lip) then 
                  ncol = max(nxt,ncol)
                  call psb_ensure_size(ncol,idxmap%loc_to_glob,info,pad=-ione,addsz=laddsz)
                  if (info /= psb_success_) then
                    info=1
                    ch_err='psb_ensure_size'
                    call psb_errpush(psb_err_from_subroutine_ai_,name,&
                         &a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
                    goto 9999
                  end if
                  idxmap%loc_to_glob(nxt)  = ip
                  call idxmap%set_lc(ncol)
                endif
                info = psb_success_
              else
                ch_err='SearchInsKeyVal'
                call psb_errpush(psb_err_from_subroutine_ai_,name,&
                     & a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
                goto 9999
              end if
            end if
            idx(i) = lip
            info = psb_success_
          enddo

        end if

      else if (.not.present(lidx)) then 

        if (present(mask)) then 
          do i = 1, is
            ncol  = idxmap%get_lc()
            if (mask(i)) then 
              ip = idx(i) 
              if ((ip < 1 ).or.(ip>mglob)) then 
                idx(i) = -1
                cycle
              endif
              nxt = ncol + 1 
              call hash_inner_cnv(ip,lip,idxmap%hashvmask,idxmap%hashv,idxmap%glb_lc,ncol)
              if (lip < 0) &
                   &  call psb_hash_searchinskey(ip,lip,nxt,idxmap%hash,info)

              if (info >=0) then 
                if (nxt == lip) then 
                  ncol = nxt
                  call psb_ensure_size(ncol,idxmap%loc_to_glob,info,pad=-ione,addsz=laddsz)
                  if (info /= psb_success_) then
                    info=1
                    ch_err='psb_ensure_size'
                    call psb_errpush(psb_err_from_subroutine_ai_,name,&
                         &a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
                    goto 9999
                  end if
                  idxmap%loc_to_glob(nxt)  = ip
                  call idxmap%set_lc(ncol)
                endif
                info = psb_success_
              else
                ch_err='SearchInsKeyVal'
                call psb_errpush(psb_err_from_subroutine_ai_,name,&
                     & a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
                goto 9999
              end if
              idx(i) = lip
              info = psb_success_
            else
              idx(i) = -1
            end if
          enddo

        else if (.not.present(mask)) then 

          do i = 1, is
            ncol  = idxmap%get_lc()
            ip = idx(i) 
            if ((ip < 1 ).or.(ip>mglob)) then 
              idx(i) = -1
              cycle
            endif
            nxt = ncol + 1 
            call hash_inner_cnv(ip,lip,idxmap%hashvmask,idxmap%hashv,idxmap%glb_lc,ncol)
            if (lip < 0) &
                 &  call psb_hash_searchinskey(ip,lip,nxt,idxmap%hash,info)

            if (info >=0) then 
              if (nxt == lip) then 
                ncol = nxt
                call psb_ensure_size(ncol,idxmap%loc_to_glob,info,pad=-ione,addsz=laddsz)
                if (info /= psb_success_) then
                  info=1
                  ch_err='psb_ensure_size'
                  call psb_errpush(psb_err_from_subroutine_ai_,name,&
                       &a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
                  goto 9999
                end if
                idxmap%loc_to_glob(nxt)  = ip
                call idxmap%set_lc(ncol)
              endif
              info = psb_success_
            else
              ch_err='SearchInsKeyVal'
              call psb_errpush(psb_err_from_subroutine_ai_,name,&
                   & a_err=ch_err,i_err=(/info,izero,izero,izero,izero/))
              goto 9999
            end if
            idx(i) = lip
            info = psb_success_
          enddo


        end if
      end if
    else 
      ! Wrong state
      idx = -1
      info = -1
    end if
    call psb_erractionrestore(err_act)
    return

9999 call psb_error_handler(ictxt,err_act)

    return

  end subroutine hash_g2lv1_ins

  subroutine hash_g2lv2_ins(idxin,idxout,idxmap,info,mask,lidx)
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_ipk_), intent(in)    :: idxin(:)
    integer(psb_ipk_), intent(out)   :: idxout(:)
    integer(psb_ipk_), intent(out)   :: info 
    logical, intent(in), optional :: mask(:)
    integer(psb_ipk_), intent(in), optional :: lidx(:)

    integer(psb_ipk_) :: is, im

    is = size(idxin)
    im = min(is,size(idxout))
    idxout(1:im) = idxin(1:im)
    call idxmap%g2lip_ins(idxout(1:im),info,mask=mask,lidx=lidx)
    if (is > im) then 
      write(0,*) 'g2lv2_ins err -3'
      info = -3 
    end if

  end subroutine hash_g2lv2_ins

  !
  ! init from VL, with checks on input.
  !
  subroutine hash_init_vl(idxmap,ictxt,vl,info)
    use psb_penv_mod
    use psb_error_mod
    use psb_sort_mod
    use psb_realloc_mod
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_mpik_), intent(in)  :: ictxt
    integer(psb_ipk_), intent(in)  :: vl(:)
    integer(psb_ipk_), intent(out) :: info
    !  To be implemented
    integer(psb_mpik_) :: iam, np
    integer(psb_ipk_) ::  i,  nlu, nl, m, nrt,int_err(5)
    integer(psb_ipk_), allocatable :: vlu(:), ix(:)
    character(len=20), parameter :: name='hash_map_init_vl'

    info = 0
    call psb_info(ictxt,iam,np) 
    if (np < 0) then 
      write(psb_err_unit,*) 'Invalid ictxt:',ictxt
      info = -1
      return
    end if

    nl = size(vl) 

    m   = maxval(vl(1:nl))
    nrt = nl
    call psb_sum(ictxt,nrt)
    call psb_max(ictxt,m)

    allocate(vlu(nl), ix(nl), stat=info) 
    if (info /= 0) then 
      info = -1
      return
    end if

    do i=1,nl
      if ((vl(i)<1).or.(vl(i)>m)) then 
        info = psb_err_entry_out_of_bounds_
        int_err(1) = i
        int_err(2) = vl(i)
        int_err(3) = nl
        int_err(4) = m
        exit
      endif
      vlu(i) = vl(i) 
    end do

    if ((m /= nrt).and.(iam == psb_root_))  then 
      write(psb_err_unit,*) trim(name),&
           & ' Warning: globalcheck=.false., but there is a mismatch'
      write(psb_err_unit,*) trim(name),&
           & '        : in the global sizes!',m,nrt

    end if

    call psb_msort(vlu,ix)
    nlu = 1
    do i=2,nl
      if (vlu(i) /= vlu(nlu)) then
        nlu = nlu + 1 
        vlu(nlu) = vlu(i)
        ix(nlu) = ix(i)
      end if
    end do
    call psb_msort(ix(1:nlu),vlu(1:nlu),flag=psb_sort_keep_idx_)
    
    nlu = nl
    call hash_init_vlu(idxmap,ictxt,m,nlu,vlu,info)    

  end subroutine hash_init_vl

  subroutine hash_init_vg(idxmap,ictxt,vg,info)
    use psb_penv_mod
    use psb_error_mod
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_mpik_), intent(in)  :: ictxt
    integer(psb_ipk_), intent(in)  :: vg(:)
    integer(psb_ipk_), intent(out) :: info
    !  To be implemented
    integer(psb_mpik_) :: iam, np
    integer(psb_ipk_) :: i, j, nl, n, int_err(5)
    integer(psb_ipk_), allocatable :: vlu(:)

    info = 0
    call psb_info(ictxt,iam,np) 
    if (np < 0) then 
      write(psb_err_unit,*) 'Invalid ictxt:',ictxt
      info = -1
      return
    end if

    n    = size(vg)
    nl   = 0
    do i=1, n
      if ((vg(i)<0).or.(vg(i)>=np)) then 
        info = psb_err_partfunc_wrong_pid_
        int_err(1) = 3
        int_err(2) = vg(i)
        int_err(3) = i
        exit
      endif
      if (vg(i) == iam) nl = nl + 1 
    end do

    allocate(vlu(nl), stat=info) 
    if (info /= 0) then 
      info = -1
      return
    end if

    j = 0
    do i=1, n
      if (vg(i) == iam) then 
        j      = j + 1 
        vlu(j) = i
      end if
    end do


    call hash_init_vlu(idxmap,ictxt,n,nl,vlu,info)    


  end subroutine hash_init_vg

  !
  ! init from VL, with no checks on input
  !
  subroutine hash_init_vlu(idxmap,ictxt,ntot,nl,vlu,info)
    use psb_penv_mod
    use psb_error_mod
    use psb_sort_mod
    use psb_realloc_mod
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_mpik_), intent(in)  :: ictxt
    integer(psb_ipk_), intent(in)  :: vlu(:), nl, ntot
    integer(psb_ipk_), intent(out) :: info
    !  To be implemented
    integer(psb_mpik_) :: iam, np
    integer(psb_ipk_) :: i, j, lc2, nlu, m, nrt,int_err(5)
    character(len=20), parameter :: name='hash_map_init_vlu'

    info = 0
    call psb_info(ictxt,iam,np) 
    if (np < 0) then 
      write(psb_err_unit,*) 'Invalid ictxt:',ictxt
      info = -1
      return
    end if

    idxmap%global_rows  = ntot
    idxmap%global_cols  = ntot
    idxmap%local_rows   = nl
    idxmap%local_cols   = nl
    idxmap%ictxt        = ictxt
    idxmap%state        = psb_desc_bld_
    call psb_get_mpicomm(ictxt,idxmap%mpic)

    lc2 = int(1.5*nl) 
    call psb_realloc(lc2,idxmap%loc_to_glob,info) 
    if (info /= 0)  then
      info = -2
      return
    end if

    call psb_hash_init(nl,idxmap%hash,info)
    if (info /= 0) then 
      write(0,*) 'from Hash_Init inside init_vlu',info
      info = -3
      return 
    endif

    do i=1, nl
      idxmap%loc_to_glob(i) = vlu(i) 
    end do

    call hash_bld_g2l_map(idxmap,info)
    call idxmap%set_state(psb_desc_bld_)

  end subroutine hash_init_vlu


  subroutine hash_bld_g2l_map(idxmap,info)
    use psb_penv_mod
    use psb_error_mod
    use psb_sort_mod
    use psb_realloc_mod
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_ipk_), intent(out) :: info
    !  To be implemented
    integer(psb_mpik_) :: ictxt, iam, np
    integer(psb_ipk_) :: i, j, m, nl
    integer(psb_ipk_) :: key, ih, nh, idx, nbits, hsize, hmask
    character(len=20), parameter :: name='hash_map_init_vlu'

    info = 0
    ictxt = idxmap%get_ctxt()

    call psb_info(ictxt,iam,np) 
    if (np < 0) then 
      write(psb_err_unit,*) 'Invalid ictxt:',ictxt
      info = -1
      return
    end if

    nl = idxmap%get_lc()

    call psb_realloc(nl,2,idxmap%glb_lc,info) 

    nbits = psb_hash_bits
    hsize = 2**nbits
    do 
      if (hsize < 0) then 
        ! This should never happen for sane values
        ! of psb_max_hash_bits.
        write(psb_err_unit,*) &
             & 'Error: hash size overflow ',hsize,nbits
        info = -2 
        return
      end if
      if (hsize > nl) exit
      if (nbits >= psb_max_hash_bits) exit
      nbits = nbits + 1
      hsize = hsize * 2 
    end do

    hmask = hsize - 1 
    idxmap%hashvsize = hsize
    idxmap%hashvmask = hmask

    if (info == psb_success_) &
         & call psb_realloc(hsize+1,idxmap%hashv,info,lb=0_psb_ipk_)
    if (info /= psb_success_) then 
      ! !$      ch_err='psb_realloc'
      ! !$      call psb_errpush(info,name,a_err=ch_err)
      ! !$      goto 9999
      info = -4 
      return
    end if

    idxmap%hashv(:) = 0

    do i=1, nl
      key = idxmap%loc_to_glob(i) 
      ih  = iand(key,hmask) 
      idxmap%hashv(ih) = idxmap%hashv(ih) + 1
    end do

    nh = idxmap%hashv(0) 
    idx = 1

    do i=1, hsize
      idxmap%hashv(i-1) = idx
      idx = idx + nh
      nh = idxmap%hashv(i)
    end do

    do i=1, nl
      key                  = idxmap%loc_to_glob(i)
      ih                   = iand(key,hmask)
      idx                  = idxmap%hashv(ih) 
      idxmap%glb_lc(idx,1) = key
      idxmap%glb_lc(idx,2) = i
      idxmap%hashv(ih)     = idxmap%hashv(ih) + 1
    end do

    do i = hsize, 1, -1 
      idxmap%hashv(i) = idxmap%hashv(i-1)
    end do

    idxmap%hashv(0) = 1
    do i=0, hsize-1 
      idx = idxmap%hashv(i)
      nh  = idxmap%hashv(i+1) - idxmap%hashv(i) 
      if (nh > 1) then 
        call psb_msort(idxmap%glb_lc(idx:idx+nh-1,1),&
             & ix=idxmap%glb_lc(idx:idx+nh-1,2),&
             & flag=psb_sort_keep_idx_)
      end if
    end do

  end subroutine hash_bld_g2l_map


  subroutine hash_asb(idxmap,info)
    use psb_penv_mod
    use psb_error_mod
    use psb_realloc_mod
    use psb_sort_mod
    implicit none 
    class(psb_hash_map), intent(inout) :: idxmap
    integer(psb_ipk_), intent(out) :: info

    integer(psb_mpik_) :: ictxt, iam, np 
    integer(psb_ipk_) :: nhal

    info = 0 
    ictxt = idxmap%get_ctxt()
    call psb_info(ictxt,iam,np)

    nhal = max(0,idxmap%local_cols-idxmap%local_rows)

    call hash_bld_g2l_map(idxmap,info)
    if (info /= 0) then 
      write(0,*) 'Error from bld_g2l_map', info
      return
    end if


    call psb_free(idxmap%hash,info)
    
    if (info /= 0) then
      write(0,*) 'Error from hash free', info
      return
    end if

    call idxmap%set_state(psb_desc_asb_)

  end subroutine hash_asb

  function hash_get_fmt() result(res)
    implicit none 
    character(len=5) :: res
    res = 'HASH'
  end function hash_get_fmt


  subroutine hash_inner_cnvs1(x,hashmask,hashv,glb_lc,nrm)

    integer(psb_ipk_), intent(in)    :: hashmask,hashv(0:),glb_lc(:,:)
    integer(psb_ipk_), intent(inout) :: x
    integer(psb_ipk_), intent(in)    :: nrm
    integer(psb_ipk_) :: ih, key, idx,nh,tmp,lb,ub,lm
    !
    ! When a large descriptor is assembled the indices 
    ! are kept in a (hashed) list of ordered lists. 
    ! Thus we first hash the index, then we do a binary search on the 
    ! ordered sublist. The hashing is based on the low-order bits 
    ! for a width of psb_hash_bits 
    !

    key = x
    ih  = iand(key,hashmask)
    idx = hashv(ih)
    nh  = hashv(ih+1) - hashv(ih) 
    if (nh > 0) then 
      tmp = -1 
      lb = idx
      ub = idx+nh-1
      do 
        if (lb>ub) exit
        lm = (lb+ub)/2
        if (key == glb_lc(lm,1)) then 
          tmp = lm
          exit
        else if (key<glb_lc(lm,1)) then 
          ub = lm - 1
        else
          lb = lm + 1
        end if
      end do
    else 
      tmp = -1
    end if
    if (tmp > 0) then 
      x = glb_lc(tmp,2)
      if (x > nrm) then 
        x = -1 
      end if
    else         
      x = tmp 
    end if
  end subroutine hash_inner_cnvs1

  subroutine hash_inner_cnvs2(x,y,hashmask,hashv,glb_lc,nrm)
    integer(psb_ipk_), intent(in)  :: hashmask,hashv(0:),glb_lc(:,:)
    integer(psb_ipk_), intent(in)  :: x
    integer(psb_ipk_), intent(out) :: y
    integer(psb_ipk_), intent(in)  :: nrm
    integer(psb_ipk_) :: ih, key, idx,nh,tmp,lb,ub,lm
    !
    ! When a large descriptor is assembled the indices 
    ! are kept in a (hashed) list of ordered lists. 
    ! Thus we first hash the index, then we do a binary search on the 
    ! ordered sublist. The hashing is based on the low-order bits 
    ! for a width of psb_hash_bits 
    !

    key = x
    ih  = iand(key,hashmask)
    idx = hashv(ih)
    nh  = hashv(ih+1) - hashv(ih) 
    if (nh > 0) then 
      tmp = -1 
      lb = idx
      ub = idx+nh-1
      do 
        if (lb>ub) exit
        lm = (lb+ub)/2
        if (key == glb_lc(lm,1)) then 
          tmp = lm
          exit
        else if (key<glb_lc(lm,1)) then 
          ub = lm - 1
        else
          lb = lm + 1
        end if
      end do
    else 
      tmp = -1
    end if
    if (tmp > 0) then 
      y = glb_lc(tmp,2)
      if (y > nrm) then 
        y = -1 
      end if
    else         
      y = tmp 
    end if
  end subroutine hash_inner_cnvs2


  subroutine hash_inner_cnv1(n,x,hashmask,hashv,glb_lc,mask,nrm)
    integer(psb_ipk_), intent(in)    :: n,hashmask,hashv(0:),glb_lc(:,:)
    logical, intent(in), optional  :: mask(:)
    integer(psb_ipk_), intent(in), optional  :: nrm
    integer(psb_ipk_), intent(inout) :: x(:)

    integer(psb_ipk_) :: i, ih, key, idx,nh,tmp,lb,ub,lm
    !
    ! When a large descriptor is assembled the indices 
    ! are kept in a (hashed) list of ordered lists. 
    ! Thus we first hash the index, then we do a binary search on the 
    ! ordered sublist. The hashing is based on the low-order bits 
    ! for a width of psb_hash_bits 
    !
    if (present(mask)) then 
      do i=1, n
        if (mask(i)) then 
          key = x(i) 
          ih  = iand(key,hashmask)
          idx = hashv(ih)
          nh  = hashv(ih+1) - hashv(ih) 
          if (nh > 0) then 
            tmp = -1 
            lb = idx
            ub = idx+nh-1
            do 
              if (lb>ub) exit
              lm = (lb+ub)/2
              if (key == glb_lc(lm,1)) then 
                tmp = lm
                exit
              else if (key<glb_lc(lm,1)) then 
                ub = lm - 1
              else
                lb = lm + 1
              end if
            end do
          else 
            tmp = -1
          end if
          if (tmp > 0) then 
            x(i) = glb_lc(tmp,2)
            if (present(nrm)) then 
              if (x(i) > nrm) then 
                x(i) = -1 
              end if
            end if
          else         
            x(i) = tmp 
          end if
        end if
      end do
    else
      do i=1, n
        key = x(i) 
        ih  = iand(key,hashmask)
        idx = hashv(ih)
        nh  = hashv(ih+1) - hashv(ih) 
        if (nh > 0) then 
          tmp = -1 
          lb = idx
          ub = idx+nh-1
          do 
            if (lb>ub) exit
            lm = (lb+ub)/2
            if (key == glb_lc(lm,1)) then 
              tmp = lm
              exit
            else if (key<glb_lc(lm,1)) then 
              ub = lm - 1
            else
              lb = lm + 1
            end if
          end do
        else 
          tmp = -1
        end if
        if (tmp > 0) then 
          x(i) = glb_lc(tmp,2)
          if (present(nrm)) then 
            if (x(i) > nrm) then 
              x(i) = -1 
            end if
          end if
        else         
          x(i) = tmp 
        end if
      end do
    end if
  end subroutine hash_inner_cnv1

  subroutine hash_inner_cnv2(n,x,y,hashmask,hashv,glb_lc,mask,nrm)
    integer(psb_ipk_), intent(in)  :: n, hashmask,hashv(0:),glb_lc(:,:)
    logical, intent(in), optional :: mask(:)
    integer(psb_ipk_), intent(in), optional :: nrm
    integer(psb_ipk_), intent(in)  :: x(:)
    integer(psb_ipk_), intent(out) :: y(:)

    integer(psb_ipk_) :: i, ih, key, idx,nh,tmp,lb,ub,lm
    !
    ! When a large descriptor is assembled the indices 
    ! are kept in a (hashed) list of ordered lists. 
    ! Thus we first hash the index, then we do a binary search on the 
    ! ordered sublist. The hashing is based on the low-order bits 
    ! for a width of psb_hash_bits 
    !
    if (present(mask)) then 
      do i=1, n
        if (mask(i)) then 
          key = x(i) 
          ih  = iand(key,hashmask)
          if (ih > ubound(hashv,1) ) then 
            write(psb_err_unit,*) ' In inner cnv: ',ih,ubound(hashv)
          end if
          idx = hashv(ih)
          nh  = hashv(ih+1) - hashv(ih) 
          if (nh > 0) then 
            tmp = -1 
            lb = idx
            ub = idx+nh-1
            do 
              if (lb>ub) exit
              lm = (lb+ub)/2
              if (key == glb_lc(lm,1)) then 
                tmp = lm
                exit
              else if (key<glb_lc(lm,1)) then 
                ub = lm - 1
              else
                lb = lm + 1
              end if
            end do
          else 
            tmp = -1
          end if
          if (tmp > 0) then 
            y(i) = glb_lc(tmp,2)
            if (present(nrm)) then 
              if (y(i) > nrm) then 
                y(i) = -1 
              end if
            end if
          else         
            y(i) = tmp 
          end if
        end if
      end do

    else

      do i=1, n
        key = x(i) 
        ih  = iand(key,hashmask)
        if (ih > ubound(hashv,1) ) then 
          write(psb_err_unit,*) ' In inner cnv: ',ih,ubound(hashv)
        end if
        idx = hashv(ih)
        nh  = hashv(ih+1) - hashv(ih) 
        if (nh > 0) then 
          tmp = -1 
          lb = idx
          ub = idx+nh-1
          do 
            if (lb>ub) exit
            lm = (lb+ub)/2
            if (key == glb_lc(lm,1)) then 
              tmp = lm
              exit
            else if (key<glb_lc(lm,1)) then 
              ub = lm - 1
            else
              lb = lm + 1
            end if
          end do
        else 
          tmp = -1
        end if
        if (tmp > 0) then 
          y(i) = glb_lc(tmp,2)
          if (present(nrm)) then 
            if (y(i) > nrm) then 
              y(i) = -1 
            end if
          end if
        else         
          y(i) = tmp 
        end if
      end do
    end if
  end subroutine hash_inner_cnv2


  subroutine hash_clone(idxmap,outmap,info)
    use psb_penv_mod
    use psb_error_mod
    use psb_realloc_mod
    implicit none 
    class(psb_hash_map), intent(inout)    :: idxmap
    class(psb_indx_map), allocatable, intent(out) :: outmap
    integer(psb_ipk_), intent(out) :: info
    integer(psb_ipk_) :: err_act
    character(len=20)  :: name='hash_clone'
    logical, parameter :: debug=.false.

    info = psb_success_
    call psb_get_erraction(err_act)
    if (allocated(outmap)) then 
      write(0,*) 'Error: should not be allocated on input'
      info = -87
      goto 9999
    end if

    allocate(psb_hash_map :: outmap, stat=info )
    if (info /= psb_success_) then 
      info = psb_err_alloc_dealloc_
      call psb_errpush(info,name)
      goto 9999
    end if

    select type (outmap)
    type is (psb_hash_map) 
      if (info == psb_success_) then 
        outmap%psb_indx_map = idxmap%psb_indx_map
        outmap%hashvsize    = idxmap%hashvsize
        outmap%hashvmask    = idxmap%hashvmask
      end if
      if (info == psb_success_)&
           &  call psb_safe_ab_cpy(idxmap%loc_to_glob,outmap%loc_to_glob,info)
      if (info == psb_success_)&
           &  call psb_safe_ab_cpy(idxmap%hashv,outmap%hashv,info)
      if (info == psb_success_)&
           &  call psb_safe_ab_cpy(idxmap%glb_lc,outmap%glb_lc,info)
      if (info == psb_success_)&
           &  call psb_hash_copy(idxmap%hash,outmap%hash,info)
!!$      outmap = idxmap
    class default
        ! This should be impossible 
      info = -1
    end select

    if (info /= psb_success_) then 
      info = psb_err_from_subroutine_
      call psb_errpush(info,name)
      goto 9999
    end if
    call psb_erractionrestore(err_act)
    return


9999 call psb_error_handler(err_act)

  return
  end subroutine hash_clone


  subroutine hash_cpy(outmap,idxmap)
    use psb_penv_mod
    use psb_error_mod
    use psb_realloc_mod
    class(psb_hash_map), intent(in) :: idxmap
    type(psb_hash_map), intent(out) :: outmap
    integer(psb_ipk_) :: info

    info = psb_success_
    outmap%psb_indx_map = idxmap%psb_indx_map
    outmap%hashvsize    = idxmap%hashvsize
    outmap%hashvmask    = idxmap%hashvmask
    if (info == psb_success_)&
         &  call psb_safe_ab_cpy(idxmap%loc_to_glob,outmap%loc_to_glob,info)
    if (info == psb_success_)&
         &  call psb_safe_ab_cpy(idxmap%hashv,outmap%hashv,info)
    if (info == psb_success_)&
         &  call psb_safe_ab_cpy(idxmap%glb_lc,outmap%glb_lc,info)
    if (info == psb_success_)&
         &  call psb_hash_copy(idxmap%hash,outmap%hash,info)
  end subroutine hash_cpy

    

  subroutine hash_reinit(idxmap,info)
    use psb_penv_mod
    use psb_error_mod
    use psb_realloc_mod
    implicit none 
    class(psb_hash_map), intent(inout)    :: idxmap
    integer(psb_ipk_), intent(out) :: info
    integer(psb_ipk_) :: err_act, nr,nc,k, nl, ntot
    integer(psb_mpik_) :: ictxt, me, np
    integer(psb_ipk_), allocatable :: idx(:),lidx(:)
    character(len=20)  :: name='hash_reinit'
    logical, parameter :: debug=.false.

    info = psb_success_
    call psb_get_erraction(err_act)
    ictxt = idxmap%get_ctxt()
    nr = idxmap%get_lr()
    nc = idxmap%get_lc()
    ntot = idxmap%get_gr()


    lidx = (/(k,k=1,nc)/)
    idx  = (/(k,k=1,nc)/)
    call idxmap%l2gip(idx,info)

    call idxmap%free()
    call hash_init_vlu(idxmap,ictxt,ntot,nr,idx(1:nr),info) 
    if (nc>nr) then 
      call idxmap%g2lip_ins(idx(nr+1:nc),info,lidx=lidx(nr+1:nc))
    end if


    if (info /= psb_success_) then 
      info = psb_err_from_subroutine_
      call psb_errpush(info,name)
      goto 9999
    end if
    call psb_erractionrestore(err_act)
    return


9999 call psb_error_handler(err_act)

    return
  end subroutine hash_reinit

  
end module psb_hash_map_mod
