/* Range object implementation */

#include "Python.h"

typedef struct {
       PyObject_HEAD
       long    start;
       long    step;
       long    len;
} rangeobject;

/* Return number of items in range/xrange (lo, hi, step).  step > 0
* required.  Return a value < 0 if & only if the true value is too
* large to fit in a signed long.
*/
static long
get_len_of_range(long lo, long hi, long step)
{
       /* -------------------------------------------------------------
       If lo >= hi, the range is empty.
       Else if n values are in the range, the last one is
       lo + (n-1)*step, which must be <= hi-1.  Rearranging,
       n <= (hi - lo - 1)/step + 1, so taking the floor of the RHS gives
       the proper value.  Since lo < hi in this case, hi-lo-1 >= 0, so
       the RHS is non-negative and so truncation is the same as the
       floor.  Letting M be the largest positive long, the worst case
       for the RHS numerator is hi=M, lo=-M-1, and then
       hi-lo-1 = M-(-M-1)-1 = 2*M.  Therefore unsigned long has enough
       precision to compute the RHS exactly.
       ---------------------------------------------------------------*/
       long n = 0;
       if (lo < hi) {
               unsigned long uhi = (unsigned long)hi;
               unsigned long ulo = (unsigned long)lo;
               unsigned long diff = uhi - ulo - 1;
               n = (long)(diff / (unsigned long)step + 1);
       }
       return n;
}

static PyObject *
range_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
       rangeobject *obj;
       long ilow = 0, ihigh = 0, istep = 1;
       long n;

       if (!_PyArg_NoKeywords("xrange()", kw))
               return NULL;

       if (PyTuple_Size(args) <= 1) {
               if (!PyArg_ParseTuple(args,
                               "l;xrange() requires 1-3 int arguments",
                               &ihigh))
                       return NULL;
       }
       else {
               if (!PyArg_ParseTuple(args,
                               "ll|l;xrange() requires 1-3 int arguments",
                               &ilow, &ihigh, &istep))
                       return NULL;
       }
       if (istep == 0) {
               PyErr_SetString(PyExc_ValueError, "xrange() arg 3 must not be zero");
               return NULL;
       }
       if (istep > 0)
               n = get_len_of_range(ilow, ihigh, istep);
       else
               n = get_len_of_range(ihigh, ilow, -istep);
       if (n < 0) {
               PyErr_SetString(PyExc_OverflowError,
                               "xrange() result has too many items");
               return NULL;
       }

       obj = PyObject_New(rangeobject, &PyRange_Type);
       if (obj == NULL)
               return NULL;
       obj->start = ilow;
       obj->len   = n;
       obj->step  = istep;
       return (PyObject *) obj;
}

PyDoc_STRVAR(range_doc,
"xrange([start,] stop[, step]) -> xrange object\n\
\n\
Like range(), but instead of returning a list, returns an object that\n\
generates the numbers in the range on demand.  For looping, this is \n\
slightly faster than range() and more memory efficient.");

static PyObject *
range_item(rangeobject *r, Py_ssize_t i)
{
       if (i < 0 || i >= r->len) {
               PyErr_SetString(PyExc_IndexError,
                               "xrange object index out of range");
               return NULL;
       }
       return PyInt_FromSsize_t(r->start + (i % r->len) * r->step);
}

static Py_ssize_t
range_length(rangeobject *r)
{
       return (Py_ssize_t)(r->len);
}

static PyObject *
range_repr(rangeobject *r)
{
       PyObject *rtn;

       if (r->start == 0 && r->step == 1)
               rtn = PyString_FromFormat("xrange(%ld)",
                                         r->start + r->len * r->step);

       else if (r->step == 1)
               rtn = PyString_FromFormat("xrange(%ld, %ld)",
                                         r->start,
                                         r->start + r->len * r->step);

       else
               rtn = PyString_FromFormat("xrange(%ld, %ld, %ld)",
                                         r->start,
                                         r->start + r->len * r->step,
                                         r->step);
       return rtn;
}

static PySequenceMethods range_as_sequence = {
       (lenfunc)range_length,  /* sq_length */
       0,                      /* sq_concat */
       0,                      /* sq_repeat */
       (ssizeargfunc)range_item, /* sq_item */
       0,                      /* sq_slice */
};

static PyObject * range_iter(PyObject *seq);
static PyObject * range_reverse(PyObject *seq);

PyDoc_STRVAR(reverse_doc,
"Returns a reverse iterator.");

static PyMethodDef range_methods[] = {
       {"__reversed__",        (PyCFunction)range_reverse, METH_NOARGS, reverse_doc},
       {NULL,          NULL}           /* sentinel */
};

PyTypeObject PyRange_Type = {
       PyObject_HEAD_INIT(&PyType_Type)
       0,                      /* Number of items for varobject */
       "xrange",               /* Name of this type */
       sizeof(rangeobject),    /* Basic object size */
       0,                      /* Item size for varobject */
       (destructor)PyObject_Del, /* tp_dealloc */
       0,                      /* tp_print */
       0,                      /* tp_getattr */
       0,                      /* tp_setattr */
       0,                      /* tp_compare */
       (reprfunc)range_repr,   /* tp_repr */
       0,                      /* tp_as_number */
       &range_as_sequence,     /* tp_as_sequence */
       0,                      /* tp_as_mapping */
       0,                      /* tp_hash */
       0,                      /* tp_call */
       0,                      /* tp_str */
       PyObject_GenericGetAttr,  /* tp_getattro */
       0,                      /* tp_setattro */
       0,                      /* tp_as_buffer */
       Py_TPFLAGS_DEFAULT,     /* tp_flags */
       range_doc,              /* tp_doc */
       0,                      /* tp_traverse */
       0,                      /* tp_clear */
       0,                      /* tp_richcompare */
       0,                      /* tp_weaklistoffset */
       range_iter,             /* tp_iter */
       0,                      /* tp_iternext */
       range_methods,          /* 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 */
       range_new,              /* tp_new */
};

/*********************** Xrange Iterator **************************/

typedef struct {
       PyObject_HEAD
       long    index;
       long    start;
       long    step;
       long    len;
} rangeiterobject;

static PyObject *
rangeiter_next(rangeiterobject *r)
{
       if (r->index < r->len)
               return PyInt_FromLong(r->start + (r->index++) * r->step);
       return NULL;
}

static PyObject *
rangeiter_len(rangeiterobject *r)
{
       return PyInt_FromLong(r->len - r->index);
}

PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it)).");

static PyMethodDef rangeiter_methods[] = {
       {"__length_hint__", (PyCFunction)rangeiter_len, METH_NOARGS, length_hint_doc},
       {NULL,          NULL}           /* sentinel */
};

static PyTypeObject Pyrangeiter_Type = {
       PyObject_HEAD_INIT(&PyType_Type)
       0,                                      /* ob_size */
       "rangeiterator",                        /* tp_name */
       sizeof(rangeiterobject),                /* tp_basicsize */
       0,                                      /* tp_itemsize */
       /* methods */
       (destructor)PyObject_Del,               /* 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 */
       0,                                      /* tp_hash */
       0,                                      /* tp_call */
       0,                                      /* tp_str */
       PyObject_GenericGetAttr,                /* tp_getattro */
       0,                                      /* tp_setattro */
       0,                                      /* tp_as_buffer */
       Py_TPFLAGS_DEFAULT,                     /* tp_flags */
       0,                                      /* tp_doc */
       0,                                      /* tp_traverse */
       0,                                      /* tp_clear */
       0,                                      /* tp_richcompare */
       0,                                      /* tp_weaklistoffset */
       PyObject_SelfIter,                      /* tp_iter */
       (iternextfunc)rangeiter_next,           /* tp_iternext */
       rangeiter_methods,                      /* tp_methods */
       0,
};

static PyObject *
range_iter(PyObject *seq)
{
       rangeiterobject *it;

       if (!PyRange_Check(seq)) {
               PyErr_BadInternalCall();
               return NULL;
       }
       it = PyObject_New(rangeiterobject, &Pyrangeiter_Type);
       if (it == NULL)
               return NULL;
       it->index = 0;
       it->start = ((rangeobject *)seq)->start;
       it->step = ((rangeobject *)seq)->step;
       it->len = ((rangeobject *)seq)->len;
       return (PyObject *)it;
}

static PyObject *
range_reverse(PyObject *seq)
{
       rangeiterobject *it;
       long start, step, len;

       if (!PyRange_Check(seq)) {
               PyErr_BadInternalCall();
               return NULL;
       }
       it = PyObject_New(rangeiterobject, &Pyrangeiter_Type);
       if (it == NULL)
               return NULL;

       start = ((rangeobject *)seq)->start;
       step = ((rangeobject *)seq)->step;
       len = ((rangeobject *)seq)->len;

       it->index = 0;
       it->start = start + (len-1) * step;
       it->step = -step;
       it->len = len;

       return (PyObject *)it;
}