/*
  A lightweight buffer class that works across Python 2 and 3.

  See the accompanying LICENSE file.
*/

/* Set to zero to disable buffer object recycling.  Even a small amount
   makes a big difference with diminishing returns based on how many
   the user program goes through without freeing and the interpreter
   gc intervals. */
#ifndef AB_NRECYCLE
#define AB_NRECYCLE 256
#endif

typedef struct APSWBuffer {
  PyObject_HEAD
  PyObject *base; /* the pybytes/pystring we are a view of */
  const char *data;
  Py_ssize_t length;
  long hash;
} APSWBuffer;

static PyTypeObject APSWBufferType;

#define APSWBuffer_Check(x) (Py_TYPE(x)==&APSWBufferType)
#define APSWBuffer_AS_STRING(x)  ( ((APSWBuffer*)(x)) -> data )
#define APSWBuffer_GET_SIZE(x)   ( ((APSWBuffer*)(x)) -> length )


#if AB_NRECYCLE > 0
static APSWBuffer* apswbuffer_recyclelist[AB_NRECYCLE];
static unsigned apswbuffer_nrecycle=0;

static void
_APSWBuffer_DECREF(PyObject *x)
{
  APSWBuffer *y=(APSWBuffer*)x;
  assert(APSWBuffer_Check(x));
  assert(Py_REFCNT(x)==1);

  if(apswbuffer_nrecycle<AB_NRECYCLE)
    {
      apswbuffer_recyclelist[apswbuffer_nrecycle++]=y;
      if(y->base)
        assert(!APSWBuffer_Check(y->base));
      Py_XDECREF(y->base);
      y->base=NULL;
    }
  else
    {
      Py_DECREF(y);
    }
}


#define APSWBuffer_XDECREF(x) \
do {                                                              \
  if(x)                                                           \
    {                                                             \
      if(Py_REFCNT(x)==1)                                         \
        { _APSWBuffer_DECREF(x); }                                \
      else                                                        \
        { Py_DECREF(x);                        }                  \
    }                                                             \
 } while(0)

/* Profiling of the test suite and speedtest was used to which locations
   were likely to meet the criteria for recycling the buffer object and
   which wouldn't */
#define APSWBuffer_XDECREF_likely APSWBuffer_XDECREF
#define APSWBuffer_XDECREF_unlikely Py_XDECREF

#ifdef APSW_TESTFIXTURES
static void
APSWBuffer_fini(void)
{
  while(apswbuffer_nrecycle)
    {
      PyObject *p=(PyObject*)apswbuffer_recyclelist[--apswbuffer_nrecycle];
      Py_DECREF(p);
    }
}
#endif

#else
#define APSWBuffer_XDECREF_likely Py_XDECREF
#define APSWBuffer_XDECREF_unlikely Py_XDECREF
#ifdef APSW_TESTFIXTURES
static void
APSWBuffer_fini(void)
{
}
#endif
#endif


/* This is the same algorithm as used for Python strings/bytes/buffer.  Note
   they can also use a hash seed which alters the value.  There is no need
   for this hash and strings/bytes etc be the same, but usually they are.  We
   add an extra 1 to put APSWBuffer into a different hash bucket. */
static Py_hash_t
APSWBuffer_hash(APSWBuffer *self)
{
  Py_uhash_t hash;
  unsigned char *p;
  Py_ssize_t len;

  if(self->hash!=-1)
    return self->hash;

  p=(unsigned char*)self->data;
  len=self->length;

  /* The python implementations all start the hash with the first byte
     even if the length is zero.  This checks there was a zero padding
     byte there as pystring/pyunicode do anyway */
  assert( (len==0)?(*p==0):1 );

  hash=*p << 7;

  while(--len>=0)
    hash=(1000003*hash) ^ *p++;

  hash ^= self->length;

  hash++; /* avoid collision */

  /* I tried to find a string that would have a hash of -2 but failed. */
  if(hash==-1)
    hash= -2;

  self->hash=hash;

  return (Py_hash_t)hash;
}


static PyObject *
APSWBuffer_FromObject(PyObject *base, Py_ssize_t offset, Py_ssize_t length)
{
  APSWBuffer *res=NULL;
#if AB_NRECYCLE > 0
  if(apswbuffer_nrecycle)
    {
      res=apswbuffer_recyclelist[--apswbuffer_nrecycle];
    }
  else
#endif
    {
      res=PyObject_New(APSWBuffer, &APSWBufferType);
      if(!res) return NULL;
    }

  assert(length>=0);

  /* the base object can be another apswbuffer */
  if(APSWBuffer_Check(base))
    {
      assert(PyBytes_Check(((APSWBuffer*)base)->base));
      assert(offset <= APSWBuffer_GET_SIZE(base));
      assert(offset+length <= APSWBuffer_GET_SIZE(base));

      res->base=((APSWBuffer*)base)->base;
      Py_INCREF(res->base);
      res->data=APSWBuffer_AS_STRING(base)+offset;
      res->length=length;
      res->hash= -1;

      return (PyObject*)res;
    }

  /* or pybytes/pystring */
  assert(PyBytes_Check(base));
  assert(offset<=PyBytes_GET_SIZE(base));
  assert(offset+length<=PyBytes_GET_SIZE(base));
  Py_INCREF(base);
  res->base=base;
  res->data=PyBytes_AS_STRING(base)+offset;
  res->length=length;
  res->hash= -1;

  return (PyObject*)res;
}

static void
APSWBuffer_dealloc(APSWBuffer *self)
{
  if(self->base)
    assert(!APSWBuffer_Check(self->base));
  Py_CLEAR(self->base);
  Py_TYPE(self)->tp_free((PyObject*)self);
}

/* Our instances are not publicly exposed and we are only compared
   for dictionary insertion/checking, so take some serious short cuts */
static PyObject *
APSWBuffer_richcompare(APSWBuffer *left, APSWBuffer *right, int op)
{
  assert(op==Py_EQ);
  assert(left->hash!=-1);
  assert(right->hash!=-1);

  if(left->hash != right->hash || left->length != right->length)
    goto notequal;

  if(left->data == right->data)
    goto equal;

  if(0==memcmp(left->data, right->data, left->length))
    goto equal;

 notequal:
  Py_RETURN_FALSE;

 equal:
  Py_RETURN_TRUE;
}

static PyTypeObject APSWBufferType =
  {
    APSW_PYTYPE_INIT
    "apsw.APSWBuffer",         /*tp_name*/
    sizeof(APSWBuffer),        /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)APSWBuffer_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    (hashfunc)APSWBuffer_hash, /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_VERSION_TAG, /*tp_flags*/
    "APSWBuffer object",       /* tp_doc */
    0,		               /* tp_traverse */
    0,		               /* tp_clear */
    (richcmpfunc)APSWBuffer_richcompare,    /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    0,		               /* tp_iter */
    0,		               /* tp_iternext */
    0,                         /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,                         /* tp_init */
    0,                         /* tp_alloc */
    0,                         /* tp_new */
    0,                         /* tp_free */
    0,                         /* tp_is_gc */
    0,                         /* tp_bases */
    0,                         /* tp_mro */
    0,                         /* tp_cache */
    0,                         /* tp_subclasses */
    0,                         /* tp_weaklist */
    0                          /* tp_del */
    APSW_PYTYPE_VERSION
};
