class Vertex(namedtuple('Vertex', ('x', 'y', 'z', 'w'))):
    """
    Represents a geometric vertex in a Wavefront obj file. The w component is
    optional and defaults to 1.0.
    """

    __slots__ = ()

    def __new__(cls, x, y, z, w=1.0):
        x = float(x)
        y = float(y)
        z = float(z)
        w = float(w)
        if w <= 0.0:
            warnings.warn(NegativeWeight('negative or zero weight: %f'))
        return super(Vertex, cls).__new__(cls, x, y, z, w)

    @property
    def __dict__(self):
        return super(Vertex, self).__dict__ class VertexParameter(namedtuple('VertexParameter', ('u', 'v', 'w'))):
    """
    Represents a point in the parameter space of a curve or surface. The w
    component is optional and defaults to 1.0.
    """

    __slots__ = ()

    def __new__(cls, u, v=0.0, w=1.0):
        u = float(u)
        v = float(v)
        w = float(w)
        return super(VertexParameter, cls).__new__(cls, u, v, w)

    @property
    def __dict__(self):
        return super(VertexParameter, self).__dict__ class VertexNormal(namedtuple('VertexNormal', ('i', 'j', 'k'))):
    """
    Represents a normal vector with components i, j, and k.
    """

    __slots__ = ()

    def __new__(cls, i, j, k):
        i = float(i)
        j = float(j)
        k = float(k)
        return super(VertexNormal, cls).__new__(cls, i, j, k)

    @property
    def __dict__(self):
        return super(VertexNormal, self).__dict__ class VertexTexture(namedtuple('VertexTexture', ('u', 'v', 'w'))):
    """
    Represents a texture vertex and its coordinates. The v and w components
    are optional and default to 0.0.
    """

    __slots__ = ()

    def __new__(cls, u, v=0.0, w=0.0):
        u = float(u)
        v = float(v)
        w = float(w)
        return super(VertexTexture, cls).__new__(cls, u, v, w)

    @property
    def __dict__(self):
        return super(VertexTexture, self).__dict__ class FaceIndexes(object):
    """
    Represents a face containing an arbitrary number of vertices (>3). A
    :meth:`resolve` method is provided to permit resolution of negative indices
    but otherwise the resulting object is effectively an immutable list.
    """

    __slots__ = ('_items', '_material', '_groups')

    def __init__(self, *indexes):
        if len(indexes) < 3:
            raise ValueError('insufficient number of vertixes for face')
        self._items = [FaceIndex.from_string(i) for i in indexes]

    def __repr__(self):
        return '<FaceIndexes %d vertexes>' % len(self)

    def __len__(self):
        return len(self._items)

    def __iter__(self):
        return iter(self._items)

    def __getitem__(self, index):
        return self._items[index] class Group(object):
    """
    Represents a group, or groups as a list of :attr:`names`. Any number of
    groups may be "active" at a time, and if no group names are specified,
    "default" is used.
    """

    __slots__ = ('_names')

    def __init__(self, *names):
        if not names:
            names = ['default']
        self._names = frozenset(names)

    def __repr__(self):
        return '<Group %s>' % ', '.join(repr(n) for n in self._names)

    @property
    def names(self):
        return self._names class Material(str):
    """
    Represents a material (or more specifically a command to switch material).
    This is simply a derivative of Python's built-in :class:`str`.
    """

    def __new__(cls, *args):
        if len(args) == 0:
            raise ValueError('missing material name')
        return super(Material, cls).__new__(cls, args[0])

    def __repr__(self):
        return '<Material %s>' % super(Material, self).__repr__() class Parser(object):
    """
    Parser for the Alias|Wavefront Obj format. Based partially on the
    `specification`_ published in Appendix B1 of the Alias|Wavefront manual.
    Handles backslash continuation of any lines, ignoring comments and blank
    lines, and assumes commands always start in the first column.

    The Parser acts as an iterable object (akin to a generator). Constructed
    with a filename or file-like object (which must return unicode strings in
    Python 3, as opposed to bytes), the instance acts as an iterable yielding
    :class:`Vertex`, :class:`VertexParameter`, :class:`VertexNormal`,
    :class:`VertexTexture`, :class:`FaceIndex`, :class:`Group`, and
    :class:`Material` instances depending on the statement encountered.

    ASCII encoding of the source file is assumed (no other character sets are
    supported), and all other legitimate commands are recognized but ignored.
    Such commands will cause an :exc:`UnsupportedCommand` warning to be raised
    but this is ignored by default.

    .. _specification: www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
    """
    def __init__(self, source):
        if isinstance(source, bytes):
            source = source.decode('utf-8')
        self._opened = isinstance(source, str)
        if self._opened:
            self._source = io.open(source, 'r', encoding='ascii')
        else:
            self._source = source

    def close(self):
        if self._opened:
            self._source.close()
        self._source = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.close()

    def __iter__(self):
        compound = []
        for line_num, line in enumerate(self._source, start=1):
            line = line.rstrip()
            if line.endswith('\\'):
                compound.append(line[:-1])
            else:
                compound.append(line)
                line = ' '.join(compound)
                compound = []
                if line and not line.startswith('#'):
                    try:
                        command, params = line.split(None, 1)
                    except ValueError:
                        command, params = line, ''
                    params = params.split()
                    if command in IGNORED:
                        warnings.warn(UnsupportedCommand(
                            'line %d: unsupported command %s' % (line_num, command)))
                    elif not command in COMMANDS:
                        raise ValueError(
                            'line %d: unknown command %s' % (line_num, command))
                    else:
                        yield {
                            'v':      Vertex,
                            'vn':     VertexNormal,
                            'vp':     VertexParameter,
                            'vt':     VertexTexture,
                            'f':      FaceIndexes,
                            'g':      Group,
                            'usemtl': Material,
                            }[command](*params) class UnsupportedCommand(ParseWarning):
    "Warning raised when an unsupported statement is encountered" class NegativeWeight(ParseWarning):
    "Warning raised when a negative weight is encountered" class Vector(namedtuple('Vector', ('x', 'y', 'z'))):
    """
    Represents a 3-dimensional vector.

    This :func:`~collections.namedtuple` derivative represents a 3-dimensional
    vector with :attr:`x`, :attr:`y`, :attr:`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 them 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 vectors representing 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, 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)
        >>> v1 * v2
        Vector(x=2, y=2, z=2)

    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 :func:`~collections.namedtuple`,
        instances of this class are immutable. That is, you cannot directly
        manipulate the :attr:`x`, :attr:`y`, and :attr:`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
        members of a :class:`set` or keys in a :class:`dict`.

    .. [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
    """

    __slots__ = () # workaround python issue #24931

    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(',')
        return cls(type(x), type(y), type(z))

    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.
        """
        if ndigits <= 0:
            return Vector(
                int(round(self.x, ndigits)),
                int(round(self.y, ndigits)),
                int(round(self.z, ndigits)))
        else:
            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
            >>> O.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 between 0
        and 180. 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
            s = u * x + v * y + w * z
            return Vector(
                u * s * (1 - cos) + x * cos + (-w * y + v * z) * sin,
                v * s * (1 - cos) + y * cos + ( w * x - u * z) * sin,
                w * s * (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 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 affect 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(start, axis),
                getattr(stop, axis),
                getattr(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 O = V() X = V(x=1) Y = V(y=1) Z = V(z=1) class Block(namedtuple('Block', ('id', 'data'))):
    """
    Represents a block within the Minecraft world.

    Blocks within the Minecraft world are represented by two values: an *id*
    which defines the type of the block (air, stone, grass, wool, etc.) and an
    optional *data* value (defaults to 0) which means different things for
    different block types (e.g.  for wool it defines the color of the wool).

    Blocks are represented by this library as a :func:`~collections.namedtuple`
    of the *id* and the *data*. Calculated properties are provided to look up
    the :attr:`name` and :attr:`description` of the block from a database
    derived from the Minecraft wiki, and classmethods are defined to construct
    a block definition from an :meth:`id <from_id>` or from alternate things
    like a :meth:`name <from_name>` or an :meth:`RGB color <from_color>`::

        >>> Block.from_id(0, 0)
        <Block "air" id=0 data=0>
        >>> Block.from_id(2, 0)
        <Block "grass" id=2 data=0>
        >>> Block.from_name('stone')
        <Block "stone" id=1 data=0>
        >>> Block.from_color('#ffffff')
        <Block "wool" id=35 data=0>

    The default constructor attempts to guess which classmethod to call based
    on the following rules (in the order given):

    1. If the constructor is passed a string beginning with '#' that is 7
       characters long, it calls :meth:`from_color`

    2. If the constructor is passed a tuple containing 3 values, it calls
       :meth:`from_color`

    3. If the constructor is passed a string (not matching the case above)
       it calls :meth:`from_name`

    4. Otherwise the constructor calls :meth:`from_id` with all given
       parameters

    This means that the examples above can be more easily written::

        >>> Block(0, 0)
        <Block "air" id=0 data=0>
        >>> Block(2, 0)
        <Block "grass" id=2 data=0>
        >>> Block('stone')
        <Block "stone" id=1 data=0>
        >>> Block('#ffffff')
        <Block "wool" id=35 data=0>

    Aliases are provided for compatibility with the official reference
    implementation (AIR, GRASS, STONE, etc)::

        >>> import picraft.block
        >>> picraft.block.WATER
        <Block "flowing_water" id=8 data=0>

    .. automethod:: from_color

    .. automethod:: from_id

    .. automethod:: from_name

    .. attribute:: id

        The "id" or type of the block. Each block type in Minecraft has a
        unique value. For example, air blocks have the id 0, stone, has id 1,
        and so forth. Generally it is clearer in code to refer to a block's
        :attr:`name` but it may be quicker to use the id.

    .. attribute:: data

        Certain types of blocks have variants which are dictated by the data
        value associated with them. For example, the color of a wool block
        is determined by the *data* attribute (e.g. white is 0, red is 14,
        and so on).

    .. autoattribute:: pi

    .. autoattribute:: pocket

    .. autoattribute:: name

    .. autoattribute:: description

    .. attribute:: COLORS

        A class attribute containing a sequence of the colors available for
        use with :meth:`from_color`.

    .. attribute:: NAMES

        A class attribute containing a sequence of the names available for
        use with :meth:`from_name`.
    """

    __slots__ = ()

    _BLOCKS_DB = {
        (id, data): (pi, pocket, name, description)
        for (id, data, pi, pocket, name, description) in
            _read_block_data(resource_stream(__name__, 'block.data'))
        }

    _BLOCKS_BY_ID = {
        id: (pi, pocket, name)
        for (id, data), (pi, pocket, name, description) in _BLOCKS_DB.items()
        if data == 0
        }

    _BLOCKS_BY_NAME = {
        name: id
        for (id, data), (pi, pocket, name, description) in _BLOCKS_DB.items()
        if data == 0
        }

    _BLOCKS_BY_COLOR = {
        color: (id, data)
        for (id, data, color) in
            _read_block_color(resource_stream(__name__, 'block.color'))
        }

    COLORS = _BLOCKS_BY_COLOR.keys()
    NAMES = _BLOCKS_BY_NAME.keys()

    def __new__(cls, *args, **kwargs):
        if len(args) >= 1:
            a = args[0]
            if isinstance(a, bytes):
                a = a.decode('utf-8')
            if isinstance(a, str) and len(a) == 7 and a.startswith('#'):
                return cls.from_color(*args, **kwargs)
            elif isinstance(a, tuple) and len(a) == 3:
                return cls.from_color(*args, **kwargs)
            elif isinstance(a, str):
                return cls.from_name(*args, **kwargs)
            else:
                return cls.from_id(*args, **kwargs)
        else:
            if 'id' in kwargs:
                return cls.from_id(**kwargs)
            elif 'name' in kwargs:
                return cls.from_name(**kwargs)
            elif 'color' in kwargs:
                return cls.from_color(**kwargs)
        raise TypeError('invalid combination of arguments for Block')

    @classmethod
    def from_string(cls, s):
        id_, data = s.split(',', 1)
        return cls.from_id(int(id_), int(data))

    @classmethod
    def from_id(cls, id, data=0):
        """
        Construct a :class:`Block` instance from an *id* integer. This may be
        used to construct blocks in the classic manner; by specifying a number
        representing the block's type, and optionally a data value. For
        example::

            >>> from picraft import *
            >>> Block.from_id(1)
            <Block "stone" id=1 data=0>
            >>> Block.from_id(2, 0)
            <Block "grass" id=2 data=0>

        The optional *data* parameter defaults to 0. Note that calling the
        default constructor with an integer parameter is equivalent to calling
        this method::

            >>> Block(1)
            <Block "stone" id=1" data=0>
        """
        return super(Block, cls).__new__(cls, id, data)

    @classmethod
    def from_name(cls, name, data=0):
        """
        Construct a :class:`Block` instance from a *name*, as returned by the
        :attr:`name` property. This may be used to construct blocks in a more
        "friendly" way within code. For example::

            >>> from picraft import *
            >>> Block.from_name('stone')
            <Block "stone" id=1 data=0>
            >>> Block.from_name('wool', data=2)
            <Block "wool" id=35 data=2>

        The optional *data* parameter can be used to specify the data component
        of the new :class:`Block` instance; it defaults to 0. Note that calling
        the default constructor with a string that doesn't start with "#" is
        equivalent to calling this method::

            >>> Block('stone')
            <Block "stone" id=1 data=0>
        """
        if isinstance(name, bytes):
            name = name.decode('utf-8')
        try:
            id_ = cls._BLOCKS_BY_NAME[name]
        except KeyError:
            raise ValueError('unknown name %s' % name)
        return cls(id_, data)

    @classmethod
    def from_color(cls, color, exact=False):
        """
        Construct a :class:`Block` instance from a *color* which can be
        represented as:

        * A tuple of ``(red, green, blue)`` integer byte values between 0 and
          255
        * A tuple of ``(red, green, blue)`` float values between 0.0 and 1.0
        * A string in the format '#rrggbb' where rr, gg, and bb are hexadecimal
          representations of byte values.

        If *exact* is ``False`` (the default), and an exact match for the
        requested color cannot be found, the nearest color (determined simply
        by Euclidian distance) is returned. If *exact* is ``True`` and an exact
        match cannot be found, a :exc:`ValueError` will be raised::

            >>> from picraft import *
            >>> Block.from_color('#ffffff')
            <Block "wool" id=35 data=0>
            >>> Block.from_color('#ffffff', exact=True)
            Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
              File "picraft/block.py", line 351, in from_color
                if exact:
            ValueError: no blocks match color #ffffff
            >>> Block.from_color((1, 0, 0))
            <Block "wool" id=35 data=14>

        Note that calling the default constructor with any of the formats
        accepted by this method is equivalent to calling this method::

            >>> Block('#ffffff')
            <Block "wool" id=35 data=0>
        """
        if isinstance(color, bytes):
            color = color.decode('utf-8')
        if isinstance(color, str):
            try:
                if not (color.startswith('#') and len(color) == 7):
                    raise ValueError()
                color = (
                        int(color[1:3], 16),
                        int(color[3:5], 16),
                        int(color[5:7], 16))
            except ValueError:
                raise ValueError('unrecognized color format: %s' % color)
        else:
            try:
                r, g, b = color
            except (TypeError, ValueError):
                raise ValueError('expected three values in color')
            if 0.0 <= r <= 1.0 and 0.0 <= g <= 1.0 and 0.0 <= b <= 1.0:
                color = tuple(int(n * 255) for n in color)
        try:
            id_, data = cls._BLOCKS_BY_COLOR[color]
        except KeyError:
            r, g, b = color
            if exact:
                raise ValueError(
                    'no blocks match color #%06x' % (r << 16 | g << 8 | b))
            diff = lambda block_color: sqrt(
                    sum((c1 - c2) ** 2 for c1, c2 in zip(color, block_color)))
            matched_color = sorted(cls._BLOCKS_BY_COLOR, key=diff)[0]
            id_, data = cls._BLOCKS_BY_COLOR[matched_color]
        return cls(id_, data)

    def __repr__(self):
        try:
            return '<Block "%s" id=%d data=%d>' % (self.name, self.id, self.data)
        except KeyError:
            return '<Block id=%d data=%d>' % (self.id, self.data)

    @property
    def pi(self):
        """
        Returns a bool indicating whether the block is present in the Pi
        Edition of Minecraft.
        """
        return self._BLOCKS_BY_ID[self.id][0]

    @property
    def pocket(self):
        """
        Returns a bool indicating whether the block is present in the Pocket
        Edition of Minecraft.
        """
        return self._BLOCKS_BY_ID[self.id][1]

    @property
    def name(self):
        """
        Return the name of the block. This is a unique identifier string which
        can be used to construct a :class:`Block` instance with
        :meth:`from_name`.
        """
        return self._BLOCKS_BY_ID[self.id][2]

    @property
    def description(self):
        """
        Return a description of the block. This string is not guaranteed to be
        unique and is only intended for human use.
        """
        try:
            return self._BLOCKS_DB[(self.id, self.data)][3]
        except KeyError:
            return self._BLOCKS_DB[(self.id, 0)][3] class Model(object):
    """
    Represents a three-dimensional model parsed from an Alias|Wavefront `object
    file`_ (.obj extension). The constructor accepts a *source* parameter which
    can be a filename or file-like object (in the latter case, this must be
    opened in text mode such that it returns unicode strings rather than bytes
    in Python 3).

    The optional *swap_yz* parameter specifies whether the Y and Z coordinates
    of each vertex in the model will be swapped; some models require this to
    render correctly in Minecraft, some do not.

    The :attr:`faces` attribute provides access to all object faces extracted
    from the file's content. The :attr:`materials` property enumerates all
    material names used by the object. The :attr:`groups` mapping maps group
    names to subsets of the available faces. The :attr:`bounds` attribute
    provides a range describing the bounding box of the unscaled model.

    Finally, the :meth:`render` method can be used to easily render the object
    in the Minecraft world at the specified scale, and with a given material
    mapping.

    .. _object file: https://en.wikipedia.org/wiki/Wavefront_.obj_file
    """

    def __init__(self, source, swap_yz=False):
        self._faces = []
        self._materials = set()
        self._groups = defaultdict(list)
        self._swap_yz = swap_yz
        self._parse(source)

    def _parse(self, source):
        vertexes = []
        textures = []
        normals = []
        active_groups = set()
        active_material = None
        for i in Parser(source):
            if isinstance(i, Vertex):
                vertexes.append(i)
            elif isinstance(i, VertexTexture):
                textures.append(i)
            elif isinstance(i, VertexNormal):
                normals.append(i)
            elif isinstance(i, Group):
                active_groups = i.names
            elif isinstance(i, Material):
                self._materials.add(i)
                active_material = i
            elif isinstance(i, FaceIndexes):
                if active_material is None:
                    self._materials.add(None)
                vectors = [
                    Vector(v.x, v.z, v.y) if self._swap_yz else
                    Vector(v.x, v.y, v.z)
                    for vi in i
                    for v in (vertexes[vi.v - 1 if vi.v > 0 else len(vertexes) + vi.v],)
                    ]
                face = ModelFace(vectors, active_material, active_groups)
                self._faces.append(face)
                for group in active_groups:
                    self._groups[group].append(face)

    @property
    def faces(self):
        """
        Returns the sequence of faces that make up the model. Each instance of
        this sequence is a :class:`ModelFace` instance which provides details
        of the coordinates of the face vertices, the face material, etc.
        """
        return self._faces

    @property
    def materials(self):
        """
        Returns the set of materials used by the model. This is derived from
        the :class:`~ModelFace.material` assigned to each face of the model.
        """
        return self._materials

    @property
    def groups(self):
        """
        A mapping of group names to sequences of :class:`ModelFace` instances.
        This can be used to extract a component of the model for further
        processing or rendering.
        """
        return self._groups

    @property
    def bounds(self):
        """
        Returns a vector range which completely encompasses the model at scale
        1.0. This can be useful for determining scaling factors when rendering.

        .. note::

            The bounding box returned is `axis-aligned`_ and is not guaranteed
            to be the minimal bounding box for the model.

        .. _axis-aligned: https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box
        """
        min_v = Vector(
            min(v.x for f in self.faces for v in f.vectors),
            min(v.y for f in self.faces for v in f.vectors),
            min(v.z for f in self.faces for v in f.vectors),
            ).floor()
        max_v = Vector(
            max(v.x for f in self.faces for v in f.vectors),
            max(v.y for f in self.faces for v in f.vectors),
            max(v.z for f in self.faces for v in f.vectors),
            ).floor()
        return vector_range(min_v, max_v + 1)

    def render(self, scale=1.0, materials=None, groups=None):
        """
        Renders the model as a :class:`dict` mapping vectors to block types.
        Effectively this rounds the vertices of each face to integers (after
        applying the *scale* multiplier, which defaults to 1.0), then calls
        :func:`~picraft.vector.filled` and :func:`~picraft.vector.lines` to
        obtain the complete coordinates of each face.

        Each coordinate then needs to be mapped to a block type. By default
        the material name is simply passed to the :class:`~picraft.block.Block`
        constructor. This assumes that material names are valid Minecraft
        block types (see :attr:`~picraft.block.Block.NAMES`).

        You can override this mechanism with the *materials* parameter. This
        can be set to a mapping (e.g. a :class:`dict`) which maps material
        names to :class:`~picraft.block.Block` instances. For example::

            from picraft import Model, Block

            m = Model('airboat.obj')
            d = m.render(materials={
                'bluteal':  Block('diamond_block'),
                'bronze':   Block('gold_block'),
                'dkdkgrey': Block((64, 64, 64)),
                'dkteal':   Block('#000080'),
                'red':      Block('#ff0000'),
                'silver':   Block.from_color('#ffffff'),
                'black':    Block(id=35, data=15),
                None:       Block('stone'),
                })

        .. note::

            Some object files may include faces with no associated material.
            In this case you will need to map ``None`` to a block type, as in
            the example above.

        Alternatively, *materials* can be a callable which will be called with
        the :class:`ModelFace` being rendered, which should return a block
        type. The following is equivalent to the default behaviour::

            from picraft import Model, Block

            m = Model('airboat.obj')
            d = m.render(materials=lambda f: Block(f.material))

        If you simply want to preview a shape without bothering with any
        material mapping you can use this method to map any face to a single
        material (in this case stone)::

            from picraft import Model, Block

            m = Model('airboat.obj')
            d = m.render(materials=lambda f: Block('stone'))

        If the *materials* mapping or callable returns ``None`` instead of a
        :class:`~picraft.block.Block` instance, the corresponding blocks will
        not be included in the result. This is a simple mechanism for excluding
        parts of a model. The other mechanism for achieving this is the
        *groups* parameter which specifies which sub-components of the model
        should be rendered. This can be specified as a string (indicating that
        only that sub-component should be rendered) or as a sequence of strings
        (indicating that all specified sub-components should be rendered).

        The result is a mapping of :class:`~picraft.vector.Vector` to
        :class:`~picraft.block.Block` instances. Rendering the result in the
        main world should be as trivial as the following code::

            from picraft import World, Model

            w = World()
            m = Model('airboat.obj').render(scale=2.0)
            with w.connection.batch_start():
                w.blocks[m.keys()] = m.values()

        Of course, you may choose to further transform the result first. This
        can be accomplished by modifying the vectors as you set them::

            from picraft import World, Model, Y

            w = World()
            m = Model('airboat.obj').render(scale=2.0)
            with w.connection.batch_start():
                for v, b in m.items():
                    w.blocks[v + 10*Y] = b

        Alternatively you may choose to use a dict-comprehension::

            from picraft import Model, Vector

            m = Model('airboat.obj').render(scale=2.0)
            offset = Vector(y=10)
            m = {v + offset: b for v, b in m.items()}

        Note that the Alias|Wavefront `object file`_ format is a relatively
        simple text based format that can be constructed by hand without
        too much difficulty. Combined with the default mapping of material
        names to block types, this enables another means of constructing
        objects in the Minecraft world. For example, see :ref:`models`.

        .. _object file: https://en.wikipedia.org/wiki/Wavefront_.obj_file
        """
        if materials is None:
            materials = lambda f: Block(f.material)
        if isinstance(groups, bytes):
            groups = groups.decode('utf-8')
        if groups is None:
            faces = self.faces
        elif isinstance(groups, str):
            faces = self.groups[groups]
        else:
            faces = chain(*(self.groups[g] for g in groups))
        result = {}
        for face in faces:
            try:
                b = materials[face.material]
            except KeyError:
                raise KeyError('missing mapping for material "%s"' % face.material)
            except TypeError:
                b = materials(face)
            if b is not None:
                points = ((p * scale).round() for p in face.vectors)
                for v in filled(lines(points)):
                    result[v] = b
        return result
import pytest
import io
import warnings
    import mock
from picraft.render import (
    Vertex,
    VertexParameter,
    VertexNormal,
    VertexTexture,
    FaceIndexes,
    Group,
    Material,
    Parser,
    )
from picraft import (
    Model,
    NegativeWeight,
    UnsupportedCommand,
    Vector,
    Block,
    vector_range,
    O, X, Y, Z,
    )
    from unittest import mock
# vim: set et sw=4 sts=4 fileencoding=utf-8:
#
# An alternate Python Minecraft library for the Rasperry-Pi
# Copyright (c) 2013-2016 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.

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


try:
except ImportError:


def test_parse_vertex():
    result = list(Parser(io.StringIO("v 0 0 0\nv 1 2 3 4")))
    assert len(result) == 2
    assert isinstance(result[0], Vertex)
    assert result[0] == (0.0, 0.0, 0.0, 1.0)
    assert isinstance(result[1], Vertex)
    assert result[1] == (1.0, 2.0, 3.0, 4.0)

def test_parse_vertex_zero_weight():
    with warnings.catch_warnings():
        warnings.simplefilter('error')
        with pytest.raises(NegativeWeight):
            m = Model(io.StringIO("v 1 1 1 0"))

def test_parse_vertex_param():
    result = list(Parser(io.StringIO("vp 0 0 0")))
    assert len(result) == 1
    assert isinstance(result[0], VertexParameter)
    assert result[0] == (0.0, 0.0, 0.0)

def test_parse_vertex_normal():
    result = list(Parser(io.StringIO("vn 0 0 0")))
    assert len(result) == 1
    assert isinstance(result[0], VertexNormal)
    assert result[0] == (0.0, 0.0, 0.0)

def test_parse_vertex_texture():
    result = list(Parser(io.StringIO("vt 0\nvt 1 2 3")))
    assert len(result) == 2
    assert isinstance(result[0], VertexTexture)
    assert result[0] == (0.0, 0.0, 0.0)
    assert isinstance(result[1], VertexTexture)
    assert result[1] == (1.0, 2.0, 3.0)

def test_parse_face_indexes():
    result = list(Parser(io.StringIO("f 1/0/0 2 3 -1")))
    assert len(result) == 1
    assert isinstance(result[0], FaceIndexes)
    assert len(result[0]) == 4
    assert result[0][0] == (1, 0, 0)
    assert result[0][1] == (2, None, None)
    assert result[0][2] == (3, None, None)
    assert result[0][3] == (-1, None, None)

def test_parse_face_indexes_bad1():
    with pytest.raises(ValueError):
        list(Parser(io.StringIO("f 1/0/0/0 2 3 4")))

def test_parse_face_indexes_bad2():
    with pytest.raises(ValueError):
        list(Parser(io.StringIO("f 1")))

def test_parse_group():
    result = list(Parser(io.StringIO("g group1 group2")))
    assert len(result) == 1

    assert isinstance(result[0], Group)