# vim: set et sw=4 sts=4 fileencoding=utf-8:
#
# An alternate Python Minecraft library for the Rasperry-Pi
# Copyright (c) 2013-2015 Dave Jones <dave@waveform.org.uk>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * 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.
#     * Neither the name of the copyright holder nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior 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 COPYRIGHT HOLDER OR 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.

"""
The vector module defines the :class:`Vector` class, which is the usual method
of represent coordinates or vectors when dealing with the Minecraft world. It
also provides functions like :func:`vector_range` for generating sequences of
vectors.

.. note::

    All items in this module are available from the :mod:`picraft` namespace
    without having to import :mod:`picraft.vector` directly.

The following items are defined in the module:


Vector
======

.. autoclass:: Vector(x=0, y=0, z=0)


Short-hand variants
===================

The :class:`Vector` class is used sufficiently often to justify the inclusion
of some shortcuts. The class itself is also available as ``V``, and vectors
representing the three axes are each available as ``X``, ``Y``, and ``Z``.
Finally, a vector representing the origin is available as ``O``::

    >>> from picraft import V, O, X, Y, Z
    >>> O
    Vector(x=0, y=0, z=0)
    >>> 2 * X
    Vector(x=2, y=0, z=0)
    >>> X + Y
    Vector(x=1, y=1, z=0)
    >>> (X + Y).angle_between(X)
    45.00000000000001
    >>> V(3, 4, 5).projection(X)
    3.0
    >>> X.rotate(90, about=Y)
    Vector(x=0.0, y=0.0, z=1.0)


vector_range
============

.. autoclass:: vector_range
    :members:


line
====

.. autofunction:: line


lines
=====

.. autofunction:: lines
"""

from __future__ import (
    unicode_literals,
    absolute_import,
    print_function,
    division,
    )
str = type('')
from .compat import range


import math
from functools import total_ordering
from collections import namedtuple, Sequence
try:
    from itertools import zip_longest, islice
except ImportError:
    # Py2 compat
    from itertools import izip_longest as zip_longest, islice


class Vector(namedtuple('Vector', ('x', 'y', 'z'))):
    """
    Represents a 3-dimensional vector.

    This tuple derivative represents a 3-dimensional vector with x, y, z
    components. Instances can be constructed in a number of ways. By explicitly
    specifying the x, y, and z components (optionally with keyword
    identifiers), or leaving the empty to default to 0::

        >>> Vector(1, 1, 1)
        Vector(x=1, y=1, z=1)
        >>> Vector(x=2, y=0, z=0)
        Vector(x=2, y=0, z=0)
        >>> Vector()
        Vector(x=0, y=0, z=0)
        >>> Vector(y=10)
        Vector(x=0, y=10, z=0)

    Shortcuts are available for the X, Y, and Z axes::

        >>> X
        Vector(x=1, y=0, z=0)
        >>> Y
        Vector(x=0, y=1, z=0)

    Note that vectors don't much care whether their components are integers,
    floating point values, or ``None``::

        >>> Vector(1.0, 1, 1)
        Vector(x=1.0, y=1, z=1)
        >>> Vector(2, None, None)
        Vector(x=2, y=None, z=None)

    The class supports simple arithmetic operations with other vectors such as
    addition and subtraction, along with multiplication and division with
    scalars, raising to powers, bit-shifting, and so on. Such operations are
    performed element-wise [1]_::

        >>> v1 = Vector(1, 1, 1)
        >>> v2 = Vector(2, 2, 2)
        >>> v1 + v2
        Vector(x=3, y=3, z=3)
        >>> 2 * v2
        Vector(x=4, y=4, z=4)

    Simple arithmetic operations with scalars return a new vector with that
    operation performed on all elements of the original. For example::

        >>> v = Vector()
        >>> v
        Vector(x=0, y=0, z=0)
        >>> v + 1
        Vector(x=1, y=1, z=1)
        >>> 2 * (v + 2)
        Vector(x=4, y=4, z=4)
        >>> Vector(y=2) ** 2
        Vector(x=0, y=4, z=0)

    Within the Minecraft world, the X,Z plane represents the ground, while the
    Y vector represents height.

    .. note::

        Note that, as a derivative of tuple, instances of this class are
        immutable. That is, you cannot directly manipulate the x, y, and z
        attributes; instead you must create a new vector (for example, by
        adding two vectors together). The advantage of this is that vector
        instances can be used in sets or as dictionary keys.

    .. [1] I realize math purists will hate this (and demand that abs() should
       be magnitude and * should invoke matrix multiplication), but the
       element wise operations are sufficiently useful to warrant the
       short-hand syntax.

    .. automethod:: replace

    .. automethod:: ceil

    .. automethod:: floor

    .. automethod:: dot

    .. automethod:: cross

    .. automethod:: distance_to

    .. automethod:: angle_between

    .. automethod:: project

    .. automethod:: rotate

    .. attribute:: x

        The position or length of the vector along the X-axis. In the Minecraft
        world this can be considered to run left-to-right.

    .. attribute:: y

        The position or length of the vector along the Y-axis. In the Minecraft
        world this can be considered to run vertically up and down.

    .. attribute:: z

        The position or length of the vector along the Z-axis. In the Minecraft
        world this can be considered as depth (in or out of the screen).

    .. autoattribute:: magnitude

    .. autoattribute:: unit
    """

    def __new__(cls, x=0, y=0, z=0):
        return super(Vector, cls).__new__(cls, x, y, z)

    @classmethod
    def from_string(cls, s, type=int):
        x, y, z = s.split(',', 2)
        return cls(type(x), type(y), type(z))

    @property
    def __dict__(self):
        # This is required to work around a subtle issue encountered in Python
        # 3.3 and above. In these versions (probably deliberately), the
        # __dict__ property is not inherited by namedtuple descendents
        return super(Vector, self).__dict__

    def __str__(self):
        return '%s,%s,%s' % (self.x, self.y, self.z)

    def __add__(self, other):
        try:
            return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
        except AttributeError:
            return Vector(self.x + other, self.y + other, self.z + other)

    __radd__ = __add__

    def __sub__(self, other):
        try:
            return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
        except AttributeError:
            return Vector(self.x - other, self.y - other, self.z - other)

    def __mul__(self, other):
        try:
            return Vector(self.x * other.x, self.y * other.y, self.z * other.z)
        except AttributeError:
            return Vector(self.x * other, self.y * other, self.z * other)

    __rmul__ = __mul__

    def __truediv__(self, other):
        try:
            return Vector(self.x / other.x, self.y / other.y, self.z / other.z)
        except AttributeError:
            return Vector(self.x / other, self.y / other, self.z / other)

    def __floordiv__(self, other):
        try:
            return Vector(self.x // other.x, self.y // other.y, self.z // other.z)
        except AttributeError:
            return Vector(self.x // other, self.y // other, self.z // other)

    def __mod__(self, other):
        try:
            return Vector(self.x % other.x, self.y % other.y, self.z % other.z)
        except AttributeError:
            return Vector(self.x % other, self.y % other, self.z % other)

    def __pow__(self, other, modulo=None):
        if modulo is not None:
            try:
                # XXX What about other vector, modulo scalar, and other scalar, modulo vector?
                return Vector(
                        pow(self.x, other.x, modulo.x),
                        pow(self.y, other.y, modulo.y),
                        pow(self.z, other.z, modulo.z))
            except AttributeError:
                return Vector(
                        pow(self.x, other, modulo),
                        pow(self.y, other, modulo),
                        pow(self.z, other, modulo))
        try:
            return Vector(
                    pow(self.x, other.x),
                    pow(self.y, other.y),
                    pow(self.z, other.z))
        except AttributeError:
            return Vector(
                    pow(self.x, other),
                    pow(self.y, other),
                    pow(self.z, other))

    def __lshift__(self, other):
        try:
            return Vector(self.x << other.x, self.y << other.y, self.z << other.z)
        except AttributeError:
            return Vector(self.x << other, self.y << other, self.z << other)

    def __rshift__(self, other):
        try:
            return Vector(self.x >> other.x, self.y >> other.y, self.z >> other.z)
        except AttributeError:
            return Vector(self.x >> other, self.y >> other, self.z >> other)

    def __and__(self, other):
        try:
            return Vector(self.x & other.x, self.y & other.y, self.z & other.z)
        except AttributeError:
            return Vector(self.x & other, self.y & other, self.z & other)

    def __xor__(self, other):
        try:
            return Vector(self.x ^ other.x, self.y ^ other.y, self.z ^ other.z)
        except AttributeError:
            return Vector(self.x ^ other, self.y ^ other, self.z ^ other)

    def __or__(self, other):
        try:
            return Vector(self.x | other.x, self.y | other.y, self.z | other.z)
        except AttributeError:
            return Vector(self.x | other, self.y | other, self.z | other)

    def __neg__(self):
        return Vector(-self.x, -self.y, -self.z)

    def __pos__(self):
        return self

    def __abs__(self):
        return Vector(abs(self.x), abs(self.y), abs(self.z))

    def __bool__(self):
        return bool(self.x or self.y or self.z)

    def __trunc__(self):
        return Vector(math.trunc(self.x), math.trunc(self.y), math.trunc(self.z))

    # Py2 compat
    __nonzero__ = __bool__
    __div__ = __truediv__

    def replace(self, x=None, y=None, z=None):
        """
        Return the vector with the x, y, or z axes replaced with the specified
        values. For example::

            >>> Vector(1, 2, 3).replace(z=4)
            Vector(x=1, y=2, z=4)
        """
        # XXX What if I want to use None?
        return Vector(
            self.x if x is None else x,
            self.y if y is None else y,
            self.z if z is None else z)

    def floor(self):
        """
        Return the vector with the floor of each component. This is only useful
        for vectors containing floating point components::

            >>> Vector(0.5, -0.5, 1.9)
            Vector(0.0, -1.0, 1.0)
        """
        return Vector(
            int(math.floor(self.x)),
            int(math.floor(self.y)),
            int(math.floor(self.z)))

    def ceil(self):
        """
        Return the vector with the ceiling of each component. This is only
        useful for vectors containing floating point components::

            >>> Vector(0.5, -0.5, 1.2)
            Vector(1.0, 0.0, 2.0)
        """
        return Vector(
            int(math.ceil(self.x)),
            int(math.ceil(self.y)),
            int(math.ceil(self.z)))

    def round(self, ndigits=0):
        """
        Return the vector with the rounded value of each component. This is
        only useful for vectors containing floating point components::

            >>> Vector(0.5, -0.5, 1.2)
            Vector(1.0, -1.0, 1.0)

        The *ndigits* argument operates as it does in the built-in
        :func:`round` function, specifying the number of decimal (or integer)
        places to round to.
        """
        return Vector(
            round(self.x, ndigits),
            round(self.y, ndigits),
            round(self.z, ndigits))

    def dot(self, other):
        """
        Return the `dot product`_ of the vector with the *other* vector. The
        result is a scalar value. For example::

            >>> Vector(1, 2, 3).dot(Vector(2, 2, 2))
            12
            >>> Vector(1, 2, 3).dot(X)
            1

        .. _dot product: http://en.wikipedia.org/wiki/Dot_product
        """
        return self.x * other.x + self.y * other.y + self.z * other.z

    def cross(self, other):
        """
        Return the `cross product`_ of the vector with the *other* vector. The
        result is another vector. For example::

            >>> Vector(1, 2, 3).cross(Vector(2, 2, 2))
            Vector(x=-2, y=4, z=-2)
            >>> Vector(1, 2, 3).cross(X)
            Vector(x=0, y=3, z=-2)

        .. _cross product: http://en.wikipedia.org/wiki/Cross_product
        """
        return Vector(
                self.y * other.z - self.z * other.y,
                self.z * other.x - self.x * other.z,
                self.x * other.y - self.y * other.x)

    def distance_to(self, other):
        """
        Return the Euclidian distance between two three dimensional points
        (represented as vectors), calculated according to `Pythagoras'
        theorem`_. For example::

            >>> Vector(1, 2, 3).distance_to(Vector(2, 2, 2))
            1.4142135623730951
            >>> Vector().distance_to(X)
            1.0

        .. _Pythagoras' theorem: http://en.wikipedia.org/wiki/Pythagorean_theorem
        """
        return (other - self).magnitude

    def angle_between(self, other):
        """
        Returns the angle between this vector and the *other* vector on a plane
        that contains both vectors. The result is measured in degrees. For
        example::

            >>> X.angle_between(Y)
            90.0
            >>> (X + Y).angle_between(X)
            45.00000000000001
        """
        return math.degrees(math.acos(self.unit.dot(other.unit)))

    def project(self, other):
        """
        Return the `scalar projection`_ of this vector onto the *other* vector.
        This is a scalar indicating the length of this vector in the direction
        of the *other* vector. For example::

            >>> Vector(1, 2, 3).project(2 * Y)
            2.0
            >>> Vector(3, 4, 5).project(Vector(3, 4, 0))
            5.0

        .. _scalar projection: https://en.wikipedia.org/wiki/Scalar_projection
        """
        return self.dot(other.unit)

    def rotate(self, angle, about, origin=None):
        """
        Return this vector after `rotation`_ of *angle* degrees about the line
        passing through *origin* in the direction *about*. Origin defaults to
        the vector 0, 0, 0. Hence, if this parameter is omitted this method
        calculates rotation about the axis (through the origin) defined by
        *about*.  For example::

            >>> Y.rotate(90, about=X)
            Vector(x=0, y=6.123233995736766e-17, z=1.0)
            >>> Vector(3, 4, 5).rotate(30, about=X, origin=10 * Y)
            Vector(x=3.0, y=2.3038475772933684, z=1.330127018922194)

        Information about rotation around arbitrary lines was obtained from
        `Glenn Murray's informative site`_.

        .. _rotation: https://en.wikipedia.org/wiki/Rotation_group_SO%283%29
        .. _Glenn Murray's informative site: http://inside.mines.edu/fs_home/gmurray/ArbitraryAxisRotation/
        """
        r = math.radians(angle)
        sin = math.sin(r)
        cos = math.cos(r)
        x, y, z = self
        if origin is None:
            # Fast-paths: rotation about a specific unit axis
            if about == X:
                return Vector(x, y * cos - z * sin, y * sin + z * cos)
            elif about == Y:
                return Vector(z * sin + x * cos, y, z * cos - x * sin)
            elif about == Z:
                return Vector(x * cos - y * sin, x * sin + y * cos, z)
            elif about == negX:
                return Vector(x, y * cos + z * sin, z * cos - y * sin)
            elif about == negY:
                return Vector(x * cos - z * sin, y, z * cos + x * sin)
            elif about == negZ:
                return Vector(x * cos + y * sin, y * cos - x * sin, z)
            # Rotation about an arbitrary axis
            u, v, w = about.unit
            return Vector(
                u * (u * x + v * y + w * z) * (1 - cos) + x * cos + (-w * y + v * z) * sin,
                v * (u * x + v * y + w * z) * (1 - cos) + y * cos + ( w * x - u * z) * sin,
                w * (u * x + v * y + w * z) * (1 - cos) + z * cos + (-v * x + u * y) * sin)
        # Rotation about an arbitrary line
        a, b, c = origin
        u, v, w = about.unit
        return Vector(
            (a * (v ** 2 + w ** 2) - u * (b * v + c * w - u * x - v * y - w * z)) * (1 - cos) + x * cos + (-c * v + b * w - w * y + v * z) * sin,
            (b * (u ** 2 + w ** 2) - v * (a * u + c * w - u * x - v * y - w * z)) * (1 - cos) + y * cos + ( c * u - a * w + w * x - u * z) * sin,
            (c * (u ** 2 + v ** 2) - w * (a * u + b * v - u * x - v * y - w * z)) * (1 - cos) + z * cos + (-b * u + a * v - v * x + u * y) * sin)

    @property
    def magnitude(self):
        """
        Returns the magnitude of the vector. This could also be considered the
        distance of the vector from the origin, i.e. ``v.magnitude`` is
        equivalent to ``Vector().distance_to(v)``. For example::

            >>> Vector(2, 4, 4).magnitude
            6.0
            >>> Vector().distance_to(Vector(2, 4, 4))
            6.0
        """
        return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)

    @property
    def unit(self):
        """
        Return a `unit vector`_ (a vector with a magnitude of one) with the
        same direction as this vector::

            >>> X.unit
            Vector(x=1.0, y=0.0, z=0.0)
            >>> (2 * Y).unit
            Vector(x=0.0, y=1.0, z=0.0)

        .. note::

            If the vector's magnitude is zero, this property returns the
            original vector.

        .. _unit vector: http://en.wikipedia.org/wiki/Unit_vector
        """
        try:
            return self / self.magnitude
        except ZeroDivisionError:
            return self


# Short-hand variants
V = Vector
O = V()
X = V(x=1)
Y = V(y=1)
Z = V(z=1)
# These aren't exposed as short-hands; they're only pre-calculated here to
# speed up the fast-paths in the rotate() method
negX = V(x=-1)
negY = V(y=-1)
negZ = V(z=-1)


# TODO Yes, I'm being lazy with total_ordering ... probably ought to define all
# six comparison methods but I haven't got time right now ...

@total_ordering
class vector_range(Sequence):
    """
    Like :func:`range`, :class:`vector_range` is actually a type which
    efficiently represents a range of vectors. The arguments to the constructor
    must be :class:`Vector` instances (or objects which have integer ``x``,
    ``y``, and ``z`` attributes).

    If *step* is omitted, it defaults to ``Vector(1, 1, 1)``. If the *start*
    argument is omitted, it defaults to ``Vector(0, 0, 0)``. If any element
    of the *step* vector is zero, :exc:`ValueError` is raised.

    The contents of the range are largely determined by the *step* and *order*
    which specifies the order in which the axes of the range will be
    incremented.  For example, with the order ``'xyz'``, the X-axis will be
    incremented first, followed by the Y-axis, and finally the Z-axis. So, for
    a range with the default *start*, *step*, and *stop* set to ``Vector(3, 3,
    3)``, the contents of the range will be::

        >>> list(vector_range(Vector(3, 3, 3), order='xyz'))
        [Vector(0, 0, 0), Vector(1, 0, 0), Vector(2, 0, 0),
         Vector(0, 1, 0), Vector(1, 1, 0), Vector(2, 1, 0),
         Vector(0, 2, 0), Vector(1, 2, 0), Vector(2, 2, 0),
         Vector(0, 0, 1), Vector(1, 0, 1), Vector(2, 0, 1),
         Vector(0, 1, 1), Vector(1, 1, 1), Vector(2, 1, 1),
         Vector(0, 2, 1), Vector(1, 2, 1), Vector(2, 2, 1),
         Vector(0, 0, 2), Vector(1, 0, 2), Vector(2, 0, 2),
         Vector(0, 1, 2), Vector(1, 1, 2), Vector(2, 1, 2),
         Vector(0, 2, 2), Vector(1, 2, 2), Vector(2, 2, 2)]

    Vector ranges implement all common sequence operations except concatenation
    and repetition (due to the fact that range objects can only represent
    sequences that follow a strict pattern and repetition and concatenation
    usually cause the resulting sequence to violate that pattern).

    Vector ranges are extremely efficient compared to an equivalent
    :func:`list` or :func:`tuple` as they take a small (fixed) amount of
    memory, storing only the arguments passed in its construction and
    calculating individual items and sub-ranges as requested.

    Vector range objects implement the :class:`collections.Sequence` ABC,
    and provide features such as containment tests, element index lookup,
    slicing and support for negative indices.

    The default order (``'zxy'``) may seem an odd choice. This is primarily
    used as it's the order used by the Raspberry Juice server when returning
    results from the :ref:`world.getBlocks` call. In turn, Raspberry Juice
    probably uses this order as it results in returning a horizontal layer of
    vectors at a time (given the Y-axis is used for height in the Minecraft
    world).

    .. warning::

        Bear in mind that the ordering of a vector range may have a bearing on
        tests for its ordering and equality. Two ranges with different orders
        are unlikely to test equal even though they may have the same *start*,
        *stop*, and *step* attributes (and thus contain the same vectors, but
        in a different order).

    Vector ranges can be accessed by integer index, by :class:`Vector` index,
    or by a slice of vectors. For example::

        >>> v = vector_range(Vector() + 1, Vector() + 3)
        >>> list(v)
        [Vector(x=1, y=1, z=1),
         Vector(x=1, y=1, z=2),
         Vector(x=2, y=1, z=1),
         Vector(x=2, y=1, z=2),
         Vector(x=1, y=2, z=1),
         Vector(x=1, y=2, z=2),
         Vector(x=2, y=2, z=1),
         Vector(x=2, y=2, z=2)]
        >>> v[0]
        Vector(x=1, y=1, z=1)
        >>> v[Vector(0, 0, 0)]
        Vector(x=1, y=1, z=1)
        >>> v[Vector(1, 0, 0)]
        Vector(x=2, y=1, z=1)
        >>> v[-1]
        Vector(x=2, y=2, z=2)
        >>> v[Vector() - 1]
        Vector(x=2, y=2, z=2)
        >>> v[Vector(x=1):]
        vector_range(Vector(x=2, y=1, z=1), Vector(x=3, y=3, z=3),
                Vector(x=1, y=1, z=1), order='zxy')
        >>> list(v[Vector(x=1):])
        [Vector(x=2, y=1, z=1),
         Vector(x=2, y=1, z=2),
         Vector(x=2, y=2, z=1),
         Vector(x=2, y=2, z=2)]

    However, integer slices are not currently permitted.
    """

    def __init__(
            self, start, stop=None, step=None, order='zxy'):
        if stop is None:
            start, stop = Vector(), start
        if step is None:
            step = Vector(1, 1, 1)
        if (start != start // 1) or (stop != stop // 1) or (step != step // 1):
            raise TypeError('integer vectors are required')
        if order not in ('xyz', 'xzy', 'yxz', 'yzx', 'zxy', 'zyx'):
            raise ValueError('invalid order: %s' % order)
        if not (step.x and step.y and step.z):
            raise ValueError('no element of step may be zero')
        self._start = start
        self._stop = stop
        self._step = step
        self._order = order
        self._ranges = [
            range(
                getattr(self.start, axis),
                getattr(self.stop, axis),
                getattr(self.step, axis))
            for axis in order
            ]
        self._indexes = [
            order.index(axis)
            for axis in 'xyz'
            ]
        self._xrange, self._yrange, self._zrange = (
            self._ranges[i] for i in self._indexes
            )
        self._len = len(self._xrange) * len(self._yrange) * len(self._zrange)

    @property
    def start(self):
        return self._start

    @property
    def stop(self):
        return self._stop

    @property
    def step(self):
        return self._step

    @property
    def order(self):
        return self._order

    def __repr__(self):
        if self.start == Vector() and self.step == Vector() + 1:
            return 'vector_range(%r, order=%r)' % (self.stop, self.order)
        elif self.step == Vector() + 1:
            return 'vector_range(%r, %r, order=%r)' % (
                    self.start, self.stop, self.order)
        else:
            return 'vector_range(%r, %r, %r, order=%r)' % (
                    self.start, self.stop, self.step, self.order)

    def __len__(self):
        return self._len

    def __lt__(self, other):
        for v1, v2 in zip_longest(self, other):
            if v1 < v2:
                return True
            elif v1 > v2:
                return False
        return False

    def __eq__(self, other):
        # Fast-path: if the other object is an identical vector_range we
        # can quickly test whether we're equal
        if isinstance(other, vector_range):
            return (
                    self._xrange == other._xrange and
                    self._yrange == other._yrange and
                    self._zrange == other._zrange and
                    self.order == other.order
                    )
        # Normal case: test every element in each sequence
        for v1, v2 in zip_longest(self, other):
            if v1 != v2:
                return False
        return True

    def __ne__(self, other):
        return not self.__eq__(other)

    def __iter__(self):
        for i in range(len(self)):
            yield self[i]

    def __reversed__(self):
        for i in reversed(range(len(self))):
            yield self[i]

    def __contains__(self, value):
        try:
            self.index(value)
        except ValueError:
            return False
        else:
            return True

    def __bool__(self):
        return len(self) > 0

    # Py2 compat
    __nonzero__ = __bool__

    def __getitem__(self, index):
        if isinstance(index, slice):
            return self._get_slice(index)
        elif isinstance(index, Vector):
            try:
                return Vector(*(
                    self._ranges[i][j]
                    for i, j in zip(self._indexes, index)
                    ))
            except IndexError:
                raise IndexError('list index out of range')
        else:
            if index < 0:
                index += len(self)
            if not (0 <= index < len(self)):
                raise IndexError('list index out of range')
            v = (
                self._ranges[0][index % len(self._ranges[0])],
                self._ranges[1][(index // len(self._ranges[0])) % len(self._ranges[1])],
                self._ranges[2][index // (len(self._ranges[0]) * len(self._ranges[1]))],
                )
            return Vector(*(v[i] for i in self._indexes))

    def _get_slice(self, s):
        try:
            step = Vector() + 1 if s.step is None else s.step
            start = Vector(None, None, None) if s.start is None else s.start
            stop = Vector(None, None, None) if s.stop is None else s.stop
            if not (step.x and step.y and step.z):
                raise ValueError(
                    "every element of the slice's step must be non-zero")
            x_range = self._xrange[slice(start.x, stop.x, step.x)]
            y_range = self._yrange[slice(start.y, stop.y, step.y)]
            z_range = self._zrange[slice(start.z, stop.z, step.z)]
        except AttributeError:
            raise ValueError(
                "vector_range slices must be composed of Vectors")
        return vector_range(
            Vector(x_range.start, y_range.start, z_range.start),
            Vector(x_range.stop, y_range.stop, z_range.stop),
            Vector(x_range.step, y_range.step, z_range.step),
            self.order)

    def index(self, value):
        """
        Return the zero-based index of *value* within the range, or raise
        :exc:`ValueError` if *value* does not exist in the range.
        """
        ranges = self._ranges
        i, j, k = (getattr(value, axis) for axis in self.order)
        try:
            i_indexes = set(rmod(len(ranges[0]), ranges[0].index(i), range(len(self))))
            j_indexes = set(
                    b
                    for a in rmod(len(ranges[1]), ranges[1].index(j),
                        range(len(self) // len(ranges[0])))
                    for b in rdiv(len(ranges[0]), a)
                    )
            k_indexes = set(rdiv(len(ranges[0]) * len(ranges[1]), ranges[2].index(k)))
            result = i_indexes & j_indexes & k_indexes
            assert len(result) == 1
            result = next(iter(result))
        except ValueError:
            raise ValueError('%r is not in range' % (value,))
        else:
            return result

    def count(self, value):
        """
        Return the count of instances of *value* within the range (note this
        can only be 0 or 1 in the case of a range, and thus is equivalent to
        testing membership with ``in``).
        """
        # count is provided by the ABC but inefficiently; given no vectors in
        # the range can be duplicated we provide a more efficient version here
        if value in self:
            return 1
        else:
            return 0


def rmod(denom, result, num_range):
    """
    Calculates the inverse of a mod operation.

    The *denom* parameter specifies the denominator of the original mod (%)
    operation. In this implementation, *denom* must be greater than 0. The
    *result* parameter specifies the result of the mod operation. For obvious
    reasons this value must be in the range ``[0, denom)`` (greater than or
    equal to zero and less than the denominator).

    Finally, *num_range* specifies the range that the numerator of the original
    mode operation can lie in. This must be an iterable sorted in ascending
    order with unique values (e.g. most typically a :func:`range`).

    The function returns the set of potential numerators (guaranteed to be a
    subset of *num_range*).
    """
    if denom <= 0:
        raise ValueError('invalid denominator')
    if not (0 <= result < denom):
        return set()
    if len(num_range) == 0:
        return set()
    assert num_range[-1] >= num_range[0]
    start = num_range[0] + (result - num_range[0] % denom) % denom
    stop = num_range.stop
    return range(start, stop, denom)


def rdiv(denom, result):
    """
    Calculates the inverse of a div operation.

    The *denom* parameter specifies the denominator of the original div (//)
    operation. In this implementation, *denom* must be greater than 0. The
    *result* parameter specifies the result of the div operation.

    The function returns the set of potential numerators.
    """
    if denom <= 0:
        raise ValueError('invalid denominator')
    return range(result * denom, result * denom + denom)


def sign(v):
    """
    Returns the sign of v as -1, 0, or 1; works for scalar values or
    :class:`Vector` instances.
    """
    try:
        return Vector(sign(v.x), sign(v.y), sign(v.z))
    except AttributeError:
        return 1 if v > 0 else -1 if v < 0 else 0


def line(start, end):
    """
    A three-dimensional implementation of `Bresenham's line algorithm`_,
    derived largely from `Bob Pendelton's implementation`_ (public domain).
    Given the end points of the line as the *start* and *end* vectors, this
    generator function yields the coordinate of each block (inclusive of the
    *start* and *end* vectors) that should be filled in to render the line.

    For example::

        >>> list(line(O, V(10, 5, 0)))
        [Vector(x=0, y=0, z=0),
         Vector(x=1, y=1, z=0),
         Vector(x=2, y=1, z=0),
         Vector(x=3, y=2, z=0),
         Vector(x=4, y=2, z=0),
         Vector(x=5, y=3, z=0),
         Vector(x=6, y=3, z=0),
         Vector(x=7, y=4, z=0),
         Vector(x=8, y=4, z=0),
         Vector(x=9, y=5, z=0),
         Vector(x=10, y=5, z=0)]

    .. _Bresenham's line algorithm: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
    .. _Bob Pendelton's implementation: ftp://ftp.isc.org/pub/usenet/comp.sources.unix/volume26/line3d
    """
    delta = end - start
    # Calculate the amount to increment each axis by; only the dominant axis
    # will advance by this amount on *every* iteration. Other axes will only
    # increment when the error demands it
    pos_inc = sign(delta)
    # Set up a vector containing the error incrementor. This will be added to
    # values tracking the axis error on each iteration
    error_inc = abs(delta) << 1
    # Calculate the subordinate and dominant axes. The dominant axis is simply
    # the one in which we must move furthest
    sub_axis1, sub_axis2, dominant_axis = sorted(
            'xyz', key=lambda axis: getattr(error_inc, axis))
    # Set up the error decrementor. This will be subtracted from the error
    # values when they turn positive (indicating that the corresponding axis
    # should advance)
    error_dec = getattr(error_inc, dominant_axis)
    # Set up a vector to track the error (this is only really required for the
    # subordinate axes)
    error = error_inc - (error_dec >> 1)
    # Convert vectors to dicts for the remainder of the algorithm
    error = error._asdict()
    error_inc = error_inc._asdict()
    pos_inc = pos_inc._asdict()
    pos = start._asdict()
    end = getattr(end, dominant_axis)
    while True:
        yield Vector(**pos)
        if pos[dominant_axis] == end:
            break
        pos[dominant_axis] += pos_inc[dominant_axis]
        if error[sub_axis1] >= 0:
            pos[sub_axis1] += pos_inc[sub_axis1]
            error[sub_axis1] -= error_dec
        error[sub_axis1] += error_inc[sub_axis1]
        if error[sub_axis2] >= 0:
            pos[sub_axis2] += pos_inc[sub_axis2]
            error[sub_axis2] -= error_dec
        error[sub_axis2] += error_inc[sub_axis2]


def lines(points, closed=True):
    """
    Extension of the :func:`line` function which returns all vectors necessary
    to render the lines connecting the specified *points* (which is an iterable
    of :class:`Vector` instances).

    If the optional *closed* parameter is ``True`` (the default) the last point
    in the *points* sequence will be connected to the first point. Otherwise,
    the lines will be left disconnected (assuming the last point is not
    coincident with the first). For example::

        >>> points = [O, 4*X, 4*Z]
        >>> list(lines(points))
        [Vector(x=0, y=0, z=0),
         Vector(x=1, y=0, z=0),
         Vector(x=2, y=0, z=0),
         Vector(x=3, y=0, z=0),
         Vector(x=4, y=0, z=0),
         Vector(x=3, y=0, z=1),
         Vector(x=2, y=0, z=2),
         Vector(x=1, y=0, z=3),
         Vector(x=0, y=0, z=4),
         Vector(x=0, y=0, z=3),
         Vector(x=0, y=0, z=2),
         Vector(x=0, y=0, z=1),
         Vector(x=0, y=0, z=0)]
    """
    first = None
    start = None
    for point in points:
        if start is None:
            first = point
            yield first
        else:
            for v in islice(line(start, point), 1, None):
                yield v
        start = point
    if closed and first != point:
        for v in islice(line(point, first), 1, None):
            yield v

def circle(center, radius, plane=Y):
    """
    Generates the coordinates of a three-dimensional circle centered at the
    vector *center*. The *radius* parameter is a vector specifying the distance
    of the circumference from the center. The optional *plane* parameter
    specifies another vector which, in combination with the *radius* vector,
    gives the plane that the circle exists within.

    For example, to create a circle centered at (0, 10, 0), with a radius of 5
    units, existing in the X-Y plane::

        >>> circle(10*Y, 5*X, Y)

    To create another circle with the same center and radius, but existing in
    the X-Z (ground) plane::

        >>> circle(10*Y, 5*X, Z)

    The algorithm used by this function is based on the `differences of roots`_
    method, extended to three dimensions.
    """
    if radius.angle_between(plane) != 90:
        plane = radius.cross(-(radius.cross(plane)))
    perp = plane.unit
    r = radius.magnitude**2
    last_points = None
    result = set()
    for radial_point in line(-radius, radius):
        circum_v = (perp * math.sqrt(r - radial_point.magnitude**2)).floor()
        top_point = (radial_point + circum_v)
        bottom_point = (radial_point - circum_v)
        if last_points is not None:
            for p in line(last_points[0], top_point):
                result.add(center + p)
            for p in line(last_points[1], bottom_point):
                result.add(center + p)
        last_points = (top_point, bottom_point)
    return result

