########################################################################
#
# License: BSD
# Created: May 18, 2006
# Author:  Francesc Alted - faltet@pytables.com
#
# $Id$
#
########################################################################

"""cython interface for keeping indexes classes.

Classes (type extensions):

    IndexArray
    CacheArray
    LastRowArray

Functions:

    keysort

Misc variables:

"""

import cython
import numpy
cimport numpy as cnp

from .exceptions import HDF5ExtError
from .hdf5extension cimport Array


# Types, constants, functions, classes & other objects from everywhere
from numpy cimport (import_array, ndarray, npy_int8, npy_int16, npy_int32,
                    npy_int64, npy_uint8, npy_uint16, npy_uint32, npy_uint64,
                    npy_float32, npy_float64, npy_float, npy_double,
                    npy_longdouble, PyArray_BYTES, PyArray_DATA)

# These two types are defined in npy_common.h but not in cython's numpy.pxd
ctypedef unsigned char npy_bool
ctypedef npy_uint16 npy_float16

from libc.stdlib cimport malloc, free
from libc.string cimport memcpy, strncmp

from .definitions cimport hid_t, herr_t, hsize_t, H5Screate_simple, H5Sclose
from .lrucacheextension cimport NumCache



#-------------------------------------------------------------------

# External C functions

# Functions for optimized operations with ARRAY for indexing purposes
cdef extern from "H5ARRAY-opt.h" nogil:
  herr_t H5ARRAYOinit_readSlice(
    hid_t dataset_id, hid_t *mem_space_id, hsize_t count)
  herr_t H5ARRAYOread_readSlice(
    hid_t dataset_id, hid_t type_id,
    hsize_t irow, hsize_t start, hsize_t stop, void *data)
  herr_t H5ARRAYOread_readSortedSlice(
    hid_t dataset_id, hid_t mem_space_id, hid_t type_id,
    hsize_t irow, hsize_t start, hsize_t stop, void *data)
  herr_t H5ARRAYOread_readBoundsSlice(
    hid_t dataset_id, hid_t mem_space_id, hid_t type_id,
    hsize_t irow, hsize_t start, hsize_t stop, void *data)
  herr_t H5ARRAYOreadSliceLR(
    hid_t dataset_id, hid_t type_id, hsize_t start, hsize_t stop, void *data)


# Functions for optimized operations for dealing with indexes
cdef extern from "idx-opt.h" nogil:
  int bisect_left_b(npy_int8 *a, long x, int hi, int offset)
  int bisect_left_ub(npy_uint8 *a, long x, int hi, int offset)
  int bisect_right_b(npy_int8 *a, long x, int hi, int offset)
  int bisect_right_ub(npy_uint8 *a, long x, int hi, int offset)
  int bisect_left_s(npy_int16 *a, long x, int hi, int offset)
  int bisect_left_us(npy_uint16 *a, long x, int hi, int offset)
  int bisect_right_s(npy_int16 *a, long x, int hi, int offset)
  int bisect_right_us(npy_uint16 *a, long x, int hi, int offset)
  int bisect_left_i(npy_int32 *a, long x, int hi, int offset)
  int bisect_left_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset)
  int bisect_right_i(npy_int32 *a, long x, int hi, int offset)
  int bisect_right_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset)
  int bisect_left_ll(npy_int64 *a, npy_int64 x, int hi, int offset)
  int bisect_left_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset)
  int bisect_right_ll(npy_int64 *a, npy_int64 x, int hi, int offset)
  int bisect_right_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset)
  int bisect_left_e(npy_float16 *a, npy_float64 x, int hi, int offset)
  int bisect_right_e(npy_float16 *a, npy_float64 x, int hi, int offset)
  int bisect_left_f(npy_float32 *a, npy_float64 x, int hi, int offset)
  int bisect_right_f(npy_float32 *a, npy_float64 x, int hi, int offset)
  int bisect_left_d(npy_float64 *a, npy_float64 x, int hi, int offset)
  int bisect_right_d(npy_float64 *a, npy_float64 x, int hi, int offset)
  int bisect_left_g(npy_longdouble *a, npy_longdouble x, int hi, int offset)
  int bisect_right_g(npy_longdouble *a, npy_longdouble x, int hi, int offset)


#----------------------------------------------------------------------------

# Initialization code

# The numpy API requires this function to be called before
# using any numpy facilities in an extension module.
import_array()

#---------------------------------------------------------------------------

ctypedef fused floating_type:
    npy_float32
    npy_float64
    npy_longdouble


ctypedef fused number_type:
    npy_int8
    npy_int16
    npy_int32
    npy_int64

    npy_uint8
    npy_uint16
    npy_uint32
    npy_uint64

    npy_float32
    npy_float64
    npy_longdouble

#===========================================================================
# Functions
#===========================================================================

#---------------------------------------------------------------------------
# keysort
#---------------------------------------------------------------------------

DEF PYA_QS_STACK = 100
DEF SMALL_QUICKSORT = 15

def keysort(ndarray array1, ndarray array2):
    """Sort array1 in-place. array2 is also sorted following the array1 order.

    array1 can be of any type, except complex or string.  array2 may be made of
    elements on any size.

    """
    cdef size_t size = cnp.PyArray_SIZE(array1)
    cdef size_t elsize1 = cnp.PyArray_ITEMSIZE(array1)
    cdef size_t elsize2 = cnp.PyArray_ITEMSIZE(array2)
    cdef int type_num = cnp.PyArray_TYPE(array1)

    # floating types
    if type_num == cnp.NPY_FLOAT16:
        _keysort[npy_float16](<npy_float16*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_FLOAT32:
        _keysort[npy_float32](<npy_float32*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_FLOAT64:
        _keysort[npy_float64](<npy_float64*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_LONGDOUBLE:
        _keysort[npy_longdouble](<npy_longdouble*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    # signed integer types
    elif type_num == cnp.NPY_INT8:
        _keysort[npy_int8](<npy_int8*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_INT16:
        _keysort[npy_int16](<npy_int16*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_INT32:
        _keysort[npy_int32](<npy_int32*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_INT64:
        _keysort[npy_int64](<npy_int64*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    # unsigned integer types
    elif type_num == cnp.NPY_UINT8:
        _keysort[npy_uint8](<npy_uint8*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_UINT16:
        _keysort[npy_uint16](<npy_uint16*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_UINT32:
        _keysort[npy_uint32](<npy_uint32*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_UINT64:
        _keysort[npy_uint64](<npy_uint64*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    # other
    elif type_num == cnp.NPY_BOOL:
        _keysort[npy_bool](<npy_bool*>PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size)
    elif type_num == cnp.NPY_STRING:
        _keysort_string(PyArray_BYTES(array1), elsize1, PyArray_BYTES(array2), elsize2, size)
    else:
        raise ValueError("Unknown array datatype")


cdef inline void swap_bytes(char *x, char *y, size_t n) nogil:
    if n == 8:
        (<npy_int64*>x)[0], (<npy_int64*>y)[0] = (<npy_int64*>y)[0], (<npy_int64*>x)[0]
    elif n == 4:
        (<npy_int32*>x)[0], (<npy_int32*>y)[0] = (<npy_int32*>y)[0], (<npy_int32*>x)[0]
    elif n == 2:
        (<npy_int16*>x)[0], (<npy_int16*>y)[0] = (<npy_int16*>y)[0], (<npy_int16*>x)[0]
    else:
        for i in range(n):
            x[i], y[i] = y[i], x[i]


cdef inline int less_than(number_type* a, number_type* b) nogil:
    if number_type in floating_type:
        return a[0] < b[0] or (b[0] != b[0] and a[0] == a[0])
    else:
        return a[0] < b[0]


@cython.cdivision(True)
cdef void _keysort(number_type* start1, char* start2, size_t elsize2, size_t n) nogil:
    cdef number_type *pl = start1
    cdef number_type *pr = start1 + (n - 1)

    cdef char *ipl = start2
    cdef char *ipr = start2 + (n - 1) * elsize2

    cdef number_type vp
    cdef char *ivp = <char *> malloc(elsize2)

    cdef number_type *stack[PYA_QS_STACK]
    cdef number_type **sptr = stack

    cdef char *istack[PYA_QS_STACK]
    cdef char **isptr = istack

    cdef size_t stack_index = 0

    cdef number_type *pm
    cdef number_type *pi
    cdef number_type *pj
    cdef number_type *pt
    cdef char *ipm
    cdef char *ipi
    cdef char *ipj
    cdef char *ipt

    while True:
        while pr - pl > SMALL_QUICKSORT:
            pm  = pl + ((pr - pl) >> 1)
            ipm  = ipl + ((ipr - ipl)//elsize2 >> 1)*elsize2

            if less_than(pm, pl):
                pm[0], pl[0] =  pl[0], pm[0]
                swap_bytes(ipm, ipl, elsize2)

            if less_than(pr, pm):
                pr[0], pm[0] =  pm[0], pr[0]
                swap_bytes(ipr, ipm, elsize2)

            if less_than(pm, pl):
                pm[0], pl[0] =  pl[0], pm[0]
                swap_bytes(ipm, ipl, elsize2)

            vp = pm[0]

            pi = pl
            ipi = ipl

            pj = pr - 1
            ipj = ipr - elsize2

            pm[0], pj[0] = pj[0], pm[0]
            swap_bytes(ipm, ipj, elsize2)

            while True:
                pi += 1
                ipi += elsize2
                while less_than(pi, &vp):
                    pi += 1
                    ipi += elsize2

                pj -= 1
                ipj -= elsize2
                while less_than(&vp, pj):
                    pj -= 1
                    ipj -= elsize2

                if pi >= pj:
                    break

                pi[0], pj[0] = pj[0], pi[0]
                swap_bytes(ipi, ipj, elsize2)

            pi[0], (pr-1)[0] = (pr-1)[0], pi[0]
            swap_bytes(ipi, ipr-elsize2, elsize2)

            # push largest partition on stack and proceed with the other
            if (pi - pl) < (pr - pi):
                sptr[0] = pi + 1
                sptr[1] = pr
                sptr += 2

                isptr[0] = ipi + elsize2
                isptr[1] = ipr
                isptr += 2

                pr = pi - 1
                ipr = ipi - elsize2
            else:
                sptr[0] = pl
                sptr[1] = pi - 1
                sptr += 2

                isptr[0] = ipl
                isptr[1] = ipi - elsize2
                isptr += 2

                pl = pi + 1
                ipl = ipi + elsize2

        pi = pl + 1
        ipi = ipl + elsize2
        while pi <= pr:
            vp = pi[0]
            memcpy(ivp, ipi, elsize2)

            pj = pi
            pt = pi - 1

            ipj = ipi
            ipt = ipi - elsize2

            while pj > pl and less_than(&vp, pt):
                pj[0] = pt[0]
                pj -= 1
                pt -= 1

                memcpy(ipj, ipt, elsize2)
                ipj -= elsize2
                ipt -= elsize2

            pj[0] = vp
            memcpy(ipj, ivp, elsize2)

            pi += 1
            ipi += elsize2

        if sptr == stack:
            break

        sptr -= 2
        pl = sptr[0]
        pr = sptr[1]

        isptr -= 2
        ipl = isptr[0]
        ipr = isptr[1]

    free(ivp)


@cython.cdivision(True)
cdef void _keysort_string(char* start1, size_t ss, char* start2, size_t ts, size_t n) nogil:
    cdef char *pl = start1
    cdef char *pr = start1 + (n - 1) * ss

    cdef char *ipl = start2
    cdef char *ipr = start2 + (n - 1) * ts

    cdef char *vp = <char *>malloc(ss)
    cdef char *ivp = <char *>malloc(ts)

    cdef char *stack[PYA_QS_STACK]
    cdef char **sptr = stack

    cdef char *istack[PYA_QS_STACK]
    cdef char **isptr = istack

    cdef size_t stack_index = 0

    cdef char *pm
    cdef char *pi
    cdef char *pj
    cdef char *pt

    cdef char *ipm
    cdef char *ipi
    cdef char *ipj
    cdef char *ipt

    while True:
        while pr - pl > SMALL_QUICKSORT * ss:
            pm  = pl + ((pr - pl)//ss >> 1)*ss
            ipm  = ipl + ((ipr - ipl)//ts >> 1)*ts

            if strncmp(pm, pl, ss) < 0:
                swap_bytes(pm, pl, ss)
                swap_bytes(ipm, ipl, ts)

            if strncmp(pr, pm, ss) < 0:
                swap_bytes(pr, pm, ss)
                swap_bytes(ipr, ipm, ts)

            if strncmp(pm, pl, ss) < 0:
                swap_bytes(pm, pl, ss)
                swap_bytes(ipm, ipl, ts)

            memcpy(vp, pm, ss)

            pi = pl
            ipi = ipl

            pj = pr - ss
            ipj = ipr - ts

            swap_bytes(pm, pj, ss)
            swap_bytes(ipm, ipj, ts)

            while True:
                pi += ss
                ipi += ts
                while strncmp(pi, vp, ss) < 0:
                    pi += ss
                    ipi += ts

                pj -= ss
                ipj -= ts
                while strncmp(vp, pj, ss) < 0:
                    pj -= ss
                    ipj -= ts

                if pi >= pj:
                    break

                swap_bytes(pi, pj, ss)
                swap_bytes(ipi, ipj, ts)

            swap_bytes(pi, pr-ss, ss)
            swap_bytes(ipi, ipr-ts, ts)

            # push largest partition on stack and proceed with the other
            if (pi - pl) < (pr - pi):
                sptr[0] = pi + ss
                sptr[1] = pr
                sptr += 2

                isptr[0] = ipi + ts
                isptr[1] = ipr
                isptr += 2

                pr = pi - ss
                ipr = ipi - ts
            else:
                sptr[0] = pl
                sptr[1] = pi - ss
                sptr += 2

                isptr[0] = ipl
                isptr[1] = ipi - ts
                isptr += 2

                pl = pi + ss
                ipl = ipi + ts

        pi = pl + ss
        ipi = ipl + ts

        while pi <= pr:
            memcpy(vp, pi, ss)
            memcpy(ivp, ipi, ts)

            pj = pi
            pt = pi - ss

            ipj = ipi
            ipt = ipi - ts

            while pj > pl and strncmp(vp, pt, ss) < 0:
                memcpy(pj, pt, ss)
                pj -= ss
                pt -= ss

                memcpy(ipj, ipt, ts)
                ipj -= ts
                ipt -= ts

            memcpy(pj, vp, ss)
            memcpy(ipj, ivp, ts)

            pi += ss
            ipi += ts

        if sptr == stack:
            break

        sptr -= 2
        pl = sptr[0]
        pr = sptr[1]

        isptr -= 2
        ipl = isptr[0]
        ipr = isptr[1]

    free(vp)
    free(ivp)

#---------------------------------------------------------------------------
# bisect
#---------------------------------------------------------------------------

# This has been copied from the standard module bisect.
# Checks for the values out of limits has been added at the beginning
# because I forsee that this should be a very common case.
# 2004-05-20
def _bisect_left(a, x, int hi):
  """Return the index where to insert item x in list a, assuming a is sorted.

  The return value i is such that all e in a[:i] have e < x, and all e in
  a[i:] have e >= x.  So if x already appears in the list, i points just
  before the leftmost x already there.

  """

  cdef int lo, mid

  lo = 0
  if x <= a[0]: return 0
  if a[-1] < x: return hi
  while lo < hi:
      mid = (lo+hi)//2
      if a[mid] < x: lo = mid+1
      else: hi = mid
  return lo


def _bisect_right(a, x, int hi):
  """Return the index where to insert item x in list a, assuming a is sorted.

  The return value i is such that all e in a[:i] have e <= x, and all e in
  a[i:] have e > x.  So if x already appears in the list, i points just
  beyond the rightmost x already there.

  """

  cdef int lo, mid

  lo = 0
  if x < a[0]: return 0
  if a[-1] <= x: return hi
  while lo < hi:
    mid = (lo+hi)//2
    if x < a[mid]: hi = mid
    else: lo = mid+1
  return lo


#===========================================================================
# Classes
#===========================================================================



cdef class Index:
  pass


cdef class CacheArray(Array):
  """Container for keeping index caches of 1st and 2nd level."""

  cdef hid_t mem_space_id

  cdef initread(self, int nbounds):
    # "Actions to accelerate the reads afterwards."

    # Precompute the mem_space_id
    if (H5ARRAYOinit_readSlice(self.dataset_id, &self.mem_space_id,
                               nbounds) < 0):
      raise HDF5ExtError("Problems initializing the bounds array data.")
    return

  cdef read_slice(self, hsize_t nrow, hsize_t start, hsize_t stop, void *rbuf):
    # "Read an slice of bounds."

    if (H5ARRAYOread_readBoundsSlice(
      self.dataset_id, self.mem_space_id, self.type_id,
      nrow, start, stop, rbuf) < 0):
      raise HDF5ExtError("Problems reading the bounds array data.")
    return

  def _g_close(self):
    super()._g_close()
    # Release specific resources of this class
    if self.mem_space_id > 0:
      H5Sclose(self.mem_space_id)


cdef class IndexArray(Array):
  """Container for keeping sorted and indices values."""

  cdef void    *rbufst
  cdef void    *rbufln
  cdef void    *rbufrv
  cdef void    *rbufbc
  cdef void    *rbuflb
  cdef hid_t   mem_space_id
  cdef int     l_chunksize, l_slicesize, nbounds, indsize
  cdef CacheArray bounds_ext
  cdef NumCache boundscache, sortedcache
  cdef ndarray bufferbc, bufferlb

  def _read_index_slice(self, hsize_t irow, hsize_t start, hsize_t stop,
                      ndarray idx):
    cdef herr_t ret
    cdef void *buf = PyArray_DATA(idx)

    # Do the physical read
    with nogil:
        ret = H5ARRAYOread_readSlice(self.dataset_id, self.type_id,
                                     irow, start, stop, buf)

    if ret < 0:
      raise HDF5ExtError("Problems reading the index indices.")


  def _init_sorted_slice(self, index):
    """Initialize the structures for doing a binary search."""

    cdef long ndims
    cdef int  rank, buflen, cachesize
    cdef char *bname
    cdef hsize_t count[2]
    cdef ndarray starts, lengths, rvcache
    cdef object maxslots, rowsize

    dtype = self.atom.dtype
    # Create the buffer for reading sorted data chunks if not created yet
    if <object>self.bufferlb is None:
      # Internal buffers
      self.bufferlb = numpy.empty(dtype=dtype, shape=self.chunksize)
      # Get the pointers to the different buffer data areas
      self.rbuflb = PyArray_DATA(self.bufferlb)
      # Init structures for accelerating sorted array reads
      rank = 2
      count[0] = 1
      count[1] = self.chunksize
      self.mem_space_id = H5Screate_simple(rank, count, NULL)
      # Cache some counters in local extension variables
      self.l_chunksize = self.chunksize
      self.l_slicesize = self.slicesize

    # Get the addresses of buffer data
    starts = index.starts
    lengths = index.lengths
    self.rbufst = PyArray_DATA(starts)
    self.rbufln = PyArray_DATA(lengths)
    # The 1st cache is loaded completely in memory and needs to be reloaded
    rvcache = index.ranges[:]
    self.rbufrv = PyArray_DATA(rvcache)
    index.rvcache = <object>rvcache
    # Init the bounds array for reading
    self.nbounds = index.bounds.shape[1]
    self.bounds_ext = <CacheArray>index.bounds
    self.bounds_ext.initread(self.nbounds)
    if str(dtype) in self._v_parent.opt_search_types:
      # The next caches should be defined only for optimized search types.
      # The 2nd level cache will replace the already existing ObjectCache and
      # already bound to the boundscache attribute. This way, the cache will
      # not be duplicated (I know, this smells badly, but anyway).
      params = self._v_file.params
      rowsize = (self.bounds_ext._v_chunkshape[1] * dtype.itemsize)
      maxslots = params['BOUNDS_MAX_SIZE'] // rowsize
      self.boundscache = <NumCache>NumCache(
        (maxslots, self.nbounds), dtype, 'non-opt types bounds')
      self.bufferbc = numpy.empty(dtype=dtype, shape=self.nbounds)
      # Get the pointer for the internal buffer for 2nd level cache
      self.rbufbc = PyArray_DATA(self.bufferbc)
      # Another NumCache for the sorted values
      rowsize = (self.chunksize*dtype.itemsize)
      maxslots = params['SORTED_MAX_SIZE'] // (self.chunksize*dtype.itemsize)
      self.sortedcache = <NumCache>NumCache(
        (maxslots, self.chunksize), dtype, 'sorted')



  cdef void *_g_read_sorted_slice(self, hsize_t irow, hsize_t start,
                                hsize_t stop):
    """Read the sorted part of an index."""

    with nogil:
        ret = H5ARRAYOread_readSortedSlice(
          self.dataset_id, self.mem_space_id, self.type_id,
          irow, start, stop, self.rbuflb)

    if ret < 0:
      raise HDF5ExtError("Problems reading the array data.")

    return self.rbuflb

  # can't time machine since this function is cdef'd
  #_g_read_sorted_slice = prveious_api(_g_read_sorted_slice)

  # This is callable from python
  def _read_sorted_slice(self, hsize_t irow, hsize_t start, hsize_t stop):
    """Read the sorted part of an index."""

    self._g_read_sorted_slice(irow, start, stop)
    return self.bufferlb


  cdef void *get_lru_bounds(self, int nrow, int nbounds):
    """Get the bounds from the cache, or read them."""

    cdef void *vpointer
    cdef long nslot

    nslot = self.boundscache.getslot_(nrow)
    if nslot >= 0:
      vpointer = self.boundscache.getitem1_(nslot)
    else:
      # Bounds row is not in cache. Read it and put it in the LRU cache.
      self.bounds_ext.read_slice(nrow, 0, nbounds, self.rbufbc)
      self.boundscache.setitem_(nrow, self.rbufbc, 0)
      vpointer = self.rbufbc
    return vpointer

  # can't time machine since get_lru_bounds() function is cdef'd

  cdef void *get_lru_sorted(self, int nrow, int ncs, int nchunk, int cs):
    """Get the sorted row from the cache or read it."""

    cdef void *vpointer
    cdef npy_int64 nckey
    cdef long nslot
    cdef hsize_t start, stop

    # Compute the number of chunk read and use it as the key for the cache.
    nckey = nrow*ncs+nchunk
    nslot = self.sortedcache.getslot_(nckey)
    if nslot >= 0:
      vpointer = self.sortedcache.getitem1_(nslot)
    else:
      # The sorted chunk is not in cache. Read it and put it in the LRU cache.
      start = cs*nchunk
      stop = cs*(nchunk+1)
      vpointer = self._g_read_sorted_slice(nrow, start, stop)
      self.sortedcache.setitem_(nckey, vpointer, 0)
    return vpointer

  # can't time machine since get_lru_sorted() function is cdef'd

  # Optimized version for int8
  def _search_bin_na_b(self, long item1, long item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_int8 *rbufrv
    cdef npy_int8 *rbufbc = NULL
    cdef npy_int8 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_int8 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1

      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_int8 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_b(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_int8 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_b(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_int8 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_b(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_int8 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_b(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for uint8
  def _search_bin_na_ub(self, long item1, long item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_uint8 *rbufrv
    cdef npy_uint8 *rbufbc = NULL
    cdef npy_uint8 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_uint8 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1

      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_uint8 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_ub(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_uint8 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_ub(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_uint8 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_ub(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_uint8 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_ub(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for int16
  def _search_bin_na_s(self, long item1, long item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_int16 *rbufrv
    cdef npy_int16 *rbufbc = NULL
    cdef npy_int16 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_int16 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1
      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_int16 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_s(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_int16 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_s(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_int16 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_s(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_int16 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_s(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for uint16
  def _search_bin_na_us(self, long item1, long item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_uint16 *rbufrv
    cdef npy_uint16 *rbufbc = NULL
    cdef npy_uint16 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_uint16 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1
      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_uint16 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_us(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_uint16 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_us(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_uint16 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_us(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_uint16 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_us(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for int32
  def _search_bin_na_i(self, long item1, long item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_int32 *rbufrv
    cdef npy_int32 *rbufbc = NULL
    cdef npy_int32 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_int32 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1
      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_int32 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_i(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_int32 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_i(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_int32 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_i(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_int32 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_i(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for uint32
  def _search_bin_na_ui(self, npy_uint32 item1, npy_uint32 item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_uint32 *rbufrv
    cdef npy_uint32 *rbufbc = NULL
    cdef npy_uint32 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_uint32 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1
      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_uint32 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_ui(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_uint32 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_ui(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_uint32 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_ui(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_uint32 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_ui(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for int64
  def _search_bin_na_ll(self, npy_int64 item1, npy_int64 item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_int64 *rbufrv
    cdef npy_int64 *rbufbc = NULL
    cdef npy_int64 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_int64 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1
      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_int64 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_ll(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_int64 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_ll(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_int64 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_ll(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_int64 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_ll(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for uint64
  def _search_bin_na_ull(self, npy_uint64 item1, npy_uint64 item2):
    cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_uint64 *rbufrv
    cdef npy_uint64 *rbufbc = NULL
    cdef npy_uint64 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    rbufrv = <npy_uint64 *>self.rbufrv
    tlength = 0
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1
      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_uint64 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_ull(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_uint64 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_ull(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_uint64 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_ull(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_uint64 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_ull(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for float16
  def _search_bin_na_e(self, npy_float64 item1, npy_float64 item2):
    cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_float16 *rbufrv
    cdef npy_float16 *rbufbc = NULL
    cdef npy_float16 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    tlength = 0
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln
    # Limits not in cache, do a lookup
    rbufrv = <npy_float16 *>self.rbufrv
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1

      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_float16 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_e(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_float16 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_e(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_float16 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_e(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_float16 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_e(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for float32
  def _search_bin_na_f(self, npy_float64 item1, npy_float64 item2):
    cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln
    # Variables with specific type
    cdef npy_float32 *rbufrv
    cdef npy_float32 *rbufbc = NULL
    cdef npy_float32 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    tlength = 0
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln

    # Limits not in cache, do a lookup
    rbufrv = <npy_float32 *>self.rbufrv
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1
      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_float32 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_f(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_float32 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_f(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_float32 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_f(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_float32 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_f(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for float64
  def _search_bin_na_d(self, npy_float64 item1, npy_float64 item2):
    cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_float64 *rbufrv
    cdef npy_float64 *rbufbc = NULL
    cdef npy_float64 *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    tlength = 0
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln

    # Limits not in cache, do a lookup
    rbufrv = <npy_float64 *>self.rbufrv
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1

      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_float64 *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_d(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_float64 *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_d(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_float64 *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_d(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_float64 *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_d(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  # Optimized version for npy_longdouble/float96/float128
  def _search_bin_na_g(self, npy_longdouble item1, npy_longdouble item2):
    cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow
    cdef int start, stop, tlength, length, bread, nchunk, nchunk2
    cdef int *rbufst
    cdef int *rbufln

    # Variables with specific type
    cdef npy_longdouble *rbufrv
    cdef npy_longdouble *rbufbc = NULL
    cdef npy_longdouble *rbuflb = NULL

    cs = self.l_chunksize
    ss = self.l_slicesize
    ncs = ss // cs
    nbounds = self.nbounds
    nrows = self.nrows
    tlength = 0
    rbufst = <int *>self.rbufst
    rbufln = <int *>self.rbufln

    # Limits not in cache, do a lookup
    rbufrv = <npy_longdouble *>self.rbufrv
    for nrow from 0 <= nrow < nrows:
      rvrow = nrow*2
      bread = 0
      nchunk = -1

      # Look if item1 is in this row
      if item1 > rbufrv[rvrow]:
        if item1 <= rbufrv[rvrow+1]:
          # Get the bounds row from the LRU cache or read them.
          rbufbc = <npy_longdouble *>self.get_lru_bounds(nrow, nbounds)
          bread = 1
          nchunk = bisect_left_g(rbufbc, item1, nbounds, 0)
          # Get the sorted row from the LRU cache or read it.
          rbuflb = <npy_longdouble *>self.get_lru_sorted(nrow, ncs, nchunk, cs)
          start = bisect_left_g(rbuflb, item1, cs, 0) + cs*nchunk
        else:
          start = ss
      else:
        start = 0
      # Now, for item2
      if item2 >= rbufrv[rvrow]:
        if item2 < rbufrv[rvrow+1]:
          if not bread:
            # Get the bounds row from the LRU cache or read them.
            rbufbc = <npy_longdouble *>self.get_lru_bounds(nrow, nbounds)
          nchunk2 = bisect_right_g(rbufbc, item2, nbounds, 0)
          if nchunk2 <> nchunk:
            # Get the sorted row from the LRU cache or read it.
            rbuflb = <npy_longdouble *>self.get_lru_sorted(nrow, ncs, nchunk2, cs)
          stop = bisect_right_g(rbuflb, item2, cs, 0) + cs*nchunk2
        else:
          stop = ss
      else:
        stop = 0
      length = stop - start
      tlength = tlength + length
      rbufst[nrow] = start
      rbufln[nrow] = length
    return tlength


  def _g_close(self):
    super()._g_close()
    # Release specific resources of this class
    if self.mem_space_id > 0:
      H5Sclose(self.mem_space_id)


cdef class LastRowArray(Array):
  """
  Container for keeping sorted and indices values of last rows of an index.
  """

  def _read_index_slice(self, hsize_t start, hsize_t stop, ndarray idx):
    """Read the reverse index part of an LR index."""

    cdef void *buf = PyArray_DATA(idx)
    with nogil:
        ret = H5ARRAYOreadSliceLR(self.dataset_id, self.type_id,
                                  start, stop, buf)

    if ret < 0:
      raise HDF5ExtError("Problems reading the index data in Last Row.")


  def _read_sorted_slice(self, IndexArray sorted, hsize_t start, hsize_t stop):
    """Read the sorted part of an LR index."""

    cdef void  *rbuflb

    rbuflb = sorted.rbuflb  # direct access to rbuflb: very fast.
    with nogil:
        ret = H5ARRAYOreadSliceLR(self.dataset_id, self.type_id,
                                  start, stop, rbuflb)

    if ret < 0:
      raise HDF5ExtError("Problems reading the index data.")
    return sorted.bufferlb[:stop-start]



## Local Variables:
## mode: python
## py-indent-offset: 2
## tab-width: 2
## fill-column: 78
## End:
