"""
ringbuf.pyx Pyrex interface to ringbuf.c It defines ring buffer class,
Ringbuf, on which various statistics are calculated as each entry is
added and a method runstats for computing a host of trailing
statistics over a numpy array
"""
include "c_ringbuf.pxi"
# ringbuf is a C library, but we can give it a pythonic object
# oriented API by managing the ringbuf pointers and function calls in
# a python class
cdef class Ringbuf:
   cdef ringbuf_t *rb_ptr
   def __new__(self, N):
      self.rb_ptr = new_ringbuf(N)
   def __dealloc__(self):
      delete_ringbuf(self.rb_ptr)
   def __getitem__(self, i):
      ret = ringbuf_getitem(self.rb_ptr, i)
      if ret == 1e38:
         raise IndexError("hit bad value marker")
      return ret
   def __len__(self):
      return ringbuf_N_filled(self.rb_ptr)
   def __getslice__(self, i0, i1):
      xx = list()
      i0 = max(0, i0)
      i1 = min(ringbuf_N_filled(self.rb_ptr), i1)
      for i in range(i0, i1):
         x = ringbuf_getitem(self.rb_ptr, i)
         if x == 1e38:
            raise IndexError("hit bad value marker")
         xx.append(x)
      return xx
   def empty(self):
      zero_ringbuf(self.rb_ptr)
   def add(self, d):
      ringbuf_add(self.rb_ptr, d)
   def min(self):
      return ringbuf_min(self.rb_ptr)
   def max(self):
      return ringbuf_max(self.rb_ptr)
   def median(self):
      return ringbuf_median(self.rb_ptr)
   def N_added(self):
      return ringbuf_N_added(self.rb_ptr)
   def N_good(self):
      return ringbuf_N_good(self.rb_ptr)
   def mean(self):
      return ringbuf_mean(self.rb_ptr)
   def sd(self):
      return ringbuf_sd(self.rb_ptr)
cdef class RingbufRecords:
   cdef ringbuf_t *rb_ptr
   cdef object records
   def __new__(self, N):
      self.rb_ptr = new_ringbuf(N)
      self.records = []
   def __dealloc__(self):
      delete_ringbuf(self.rb_ptr)
      del self.records[:]
   def __getitem__(self, i):
      r = self.records[i]
      ret = ringbuf_getitem(self.rb_ptr, i)
      if ret == 1e38:
         raise IndexError("hit bad value marker")
      return ret, r
   def __len__(self):
      return ringbuf_N_filled(self.rb_ptr)
   def __getslice__(self, i0, i1):
      xx = list()
      i0 = max(0, i0)
      i1 = min(ringbuf_N_filled(self.rb_ptr), i1)
      for i in range(i0, i1):
         x = ringbuf_getitem(self.rb_ptr, i)
         if x == 1e38:
            raise IndexError("hit bad value marker")
         xx.append(x)
      return xx, self.records[i0:i1]
   def empty(self):
      zero_ringbuf(self.rb_ptr)
      self.records = []
   def add(self, d, r):
      ringbuf_add(self.rb_ptr, d)
      self.records.append(r)
      if len(self.records) > self.rb_ptr.N_size:
         del self.records[0]
   def min(self):
      return ringbuf_min(self.rb_ptr)
   def max(self):
      return ringbuf_max(self.rb_ptr)
   def median(self):
      return ringbuf_median(self.rb_ptr)
   def N_added(self):
      return ringbuf_N_added(self.rb_ptr)
   def N_good(self):
      return ringbuf_N_good(self.rb_ptr)
   def mean(self):
      return ringbuf_mean(self.rb_ptr)
   def sd(self):
      return ringbuf_sd(self.rb_ptr)
# now we'll provide a numpy interface to the runstats function, which
# fills predimensioned arrays with the trailing mean, std, max, etc,
# for a data array
cimport c_numpy
import numpy
c_numpy.import_array()
def runstats(data, nrb):
    """
    Compute running stats on 1D array data over window length nrb
    """
    # we will be calling the ringbuf C function runstats, which
    # expects a bunch of c data pointers to the data arrays as well as
    # the output arrays for the mean, std, etc...  Create a array for
    # each C float array pointer the C function expects.  The type of
    # the array is c_numpy.ndarray
    cdef c_numpy.ndarray c_data
    cdef c_numpy.ndarray c_dmean
    cdef c_numpy.ndarray c_dstd
    cdef c_numpy.ndarray c_dmin
    cdef c_numpy.ndarray c_dmax
    cdef c_numpy.ndarray c_dmedian
    cdef c_numpy.ndarray c_ng
    # make sure that the input array is a 1D numpy array of floats.
    # asarray is used to copy and cast a python sequence or array to
    # the approriate type, with the advantage that if the data is
    # already the right type, no copy is performed
    data = numpy.asarray(data, dtype=numpy.float_)
    if data.ndim != 1:
        raise ValueError("data must be 1-D for now")
    nd = data.shape[0]
    # use numpy.empty_like to create and empty array of the same type
    # and shape as data for each of the return stats (mean, std, ...)
    # These are genuine numpy arrays we will be passing back to the
    # python level. Remember the ng (the number of good elements) is
    # an int, and everything else is a float
    dmean = numpy.empty_like(data)
    dstd = numpy.empty_like(data)
    dmin = numpy.empty_like(data)
    dmax = numpy.empty_like(data)
    dmedian = numpy.empty_like(data)
    ng = numpy.empty(data.shape, dtype=numpy.int_)
    # now we have to assign the c_data structures and friends to their
    # corresponding numpy arrays
    c_data = data
    c_dmean = dmean
    c_dstd = dstd
    c_dmin = dmin
    c_dmax = dmax
    c_dmedian = dmedian
    c_ng = ng
    # now we call the function and pass in the c data pointers to the
    # arrays.  The syntax <double *>c_data.data tells pyrex to pass
    # the numpy data memory block as a pointer to a float array.
    c_runstats(nrb, nd, <double *>c_data.data,
                        <double *>c_dmean.data,
                        <double *>c_dstd.data,
                        <double *>c_dmin.data,
                        <double *>c_dmax.data,
                        <double *>c_dmedian.data,
                        <int *>c_ng.data)
    # all done, return the arrays
    return dmean, dstd, dmin, dmax, dmedian, ng