DEF LINE_CAP_NONE = 0
DEF LINE_CAP_SQUARE = 1
DEF LINE_CAP_ROUND = 2

DEF LINE_JOINT_NONE = 0
DEF LINE_JOINT_MITER = 1
DEF LINE_JOINT_BEVEL = 2
DEF LINE_JOINT_ROUND = 3

DEF LINE_MODE_POINTS = 0
DEF LINE_MODE_ELLIPSE = 1
DEF LINE_MODE_CIRCLE = 2
DEF LINE_MODE_RECTANGLE = 3
DEF LINE_MODE_ROUNDED_RECTANGLE = 4
DEF LINE_MODE_BEZIER = 5

from kivy.graphics.stencil_instructions cimport StencilUse, StencilUnUse, StencilPush, StencilPop

cdef float PI = 3.1415926535

cdef inline int line_intersection(double x1, double y1, double x2, double y2,
        double x3, double y3, double x4, double y4, double *px, double *py):
    cdef double u = (x1 * y2 - y1 * x2)
    cdef double v = (x3 * y4 - y3 * x4)
    cdef double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
    if denom == 0:
        return 0
    px[0] = (u * (x3 - x4) - (x1 - x2) * v) / denom
    py[0] = (u * (y3 - y4) - (y1 - y2) * v) / denom
    return 1

cdef class Line(VertexInstruction):
    '''A 2d line.

    Drawing a line can be done easily::

        with self.canvas:
            Line(points=[100, 100, 200, 100, 100, 200], width=10)

    The line has 3 internal drawing modes that you should be aware of
    for optimal results:

    #. If the :attr:`width` is 1.0, then the standard GL_LINE drawing from
       OpenGL will be used. :attr:`dash_length` and :attr:`dash_offset` will
       work, while properties for cap and joint have no meaning here.
    #. If the :attr:`width` is > 1.0, then a custom drawing method will be used,
       based on triangles. :attr:`dash_length` and :attr:`dash_offset` do not
       work in this mode.
       Additionally, if the current color has an alpha < 1.0, a stencil will be
       used internally to draw the line.

    .. image:: images/line-instruction.png
        :align: center

    :Parameters:
        `points`: list
            List of points in the format (x1, y1, x2, y2...)
        `dash_length`: int
            Length of a segment (if dashed), defaults to 1.
        `dash_offset`: int
            Offset between the end of a segments and the begining of the
            next one, defaults to 0. Changing this makes it dashed.
        `width`: float
            Width of the line, defaults to 1.0.
        `cap`: str, defaults to 'round'
            See :attr:`cap` for more information.
        `joint`: str, defaults to 'round'
            See :attr:`joint` for more information.
        `cap_precision`: int, defaults to 10
            See :attr:`cap_precision` for more information
        `joint_precision`: int, defaults to 10
            See :attr:`joint_precision` for more information
            See :attr:`cap_precision` for more information.
        `joint_precision`: int, defaults to 10
            See :attr:`joint_precision` for more information.
        `close`: bool, defaults to False
            If True, the line will be closed.
        `circle`: list
            If set, the :attr:`points` will be set to build a circle. Check
            :attr:`circle` for more information.
        `ellipse`: list
            If set, the :attr:`points` will be set to build an ellipse. Check
            :attr:`ellipse` for more information.
        `rectangle`: list
            If set, the :attr:`points` will be set to build a rectangle. Check
            :attr:`rectangle` for more information.
        `bezier`: list
            If set, the :attr:`points` will be set to build a bezier line. Check
            :attr:`bezier` for more information.
        `bezier_precision`: int, defaults to 180
            Precision of the Bezier drawing.

    .. versionchanged:: 1.0.8
        `dash_offset` and `dash_length` have been added

    .. versionchanged:: 1.4.1
        `width`, `cap`, `joint`, `cap_precision`, `joint_precision`, `close`,
        `ellipse`, `rectangle` have been added.

    .. versionchanged:: 1.4.1
        `bezier`, `bezier_precision` have been added.

    '''
    cdef int _cap
    cdef int _cap_precision
    cdef int _joint_precision
    cdef int _bezier_precision
    cdef int _joint
    cdef list _points
    cdef float _width
    cdef int _dash_offset, _dash_length
    cdef int _use_stencil
    cdef int _close
    cdef int _mode
    cdef Instruction _stencil_rect
    cdef Instruction _stencil_push
    cdef Instruction _stencil_use
    cdef Instruction _stencil_unuse
    cdef Instruction _stencil_pop
    cdef double _bxmin, _bxmax, _bymin, _bymax
    cdef tuple _mode_args

    def __init__(self, **kwargs):
        VertexInstruction.__init__(self, **kwargs)
        v = kwargs.get('points')
        self.points = v if v is not None else []
        self.batch.set_mode('line_strip')
        self._dash_length = kwargs.get('dash_length') or 1
        self._dash_offset = kwargs.get('dash_offset') or 0
        self._width = kwargs.get('width') or 1.0
        self.joint = kwargs.get('joint') or 'round'
        self.cap = kwargs.get('cap') or 'round'
        self._cap_precision = kwargs.get('cap_precision') or 10
        self._joint_precision = kwargs.get('joint_precision') or 10
        self._bezier_precision = kwargs.get('bezier_precision') or 180
        self._close = int(bool(kwargs.get('close', 0)))
        self._stencil_rect = None
        self._stencil_push = None
        self._stencil_use = None
        self._stencil_unuse = None
        self._stencil_pop = None
        self._use_stencil = 0

        if 'ellipse' in kwargs:
            self.ellipse = kwargs['ellipse']
        if 'circle' in kwargs:
            self.circle = kwargs['circle']
        if 'rectangle' in kwargs:
            self.rectangle = kwargs['rectangle']
        if 'bezier' in kwargs:
            self.bezier = kwargs['bezier']

    cdef void build(self):
        if self._mode == LINE_MODE_ELLIPSE:
            self.prebuild_ellipse()
        elif self._mode == LINE_MODE_CIRCLE:
            self.prebuild_circle()
        elif self._mode == LINE_MODE_RECTANGLE:
            self.prebuild_rectangle()
        elif self._mode == LINE_MODE_ROUNDED_RECTANGLE:
            self.prebuild_rounded_rectangle()
        elif self._mode == LINE_MODE_BEZIER:
            self.prebuild_bezier()
        if self._width == 1.0:
            self.build_legacy()
        else:
            self.build_extended()

    cdef void ensure_stencil(self):
        if self._stencil_rect == None:
            self._stencil_rect = Rectangle()
            self._stencil_push = StencilPush()
            self._stencil_pop = StencilPop()
            self._stencil_use = StencilUse(op='lequal')
            self._stencil_unuse = StencilUnUse()

    cdef void apply(self):
        if self._width == 1.:
            VertexInstruction.apply(self)
            return

        cdef double alpha = getActiveContext()['color'][-1]
        self._use_stencil = alpha < 1
        if self._use_stencil:
            self.ensure_stencil()

            self._stencil_push.apply()
            VertexInstruction.apply(self)
            self._stencil_use.apply()
            self._stencil_rect.pos = self._bxmin, self._bymin
            self._stencil_rect.size = self._bxmax - self._bxmin, self._bymax - self._bymin
            self._stencil_rect.apply()
            self._stencil_unuse.apply()
            VertexInstruction.apply(self)
            self._stencil_pop.apply()
        else:
            VertexInstruction.apply(self)

    cdef void build_legacy(self):
        cdef int i
        cdef long count = len(self.points) / 2
        cdef list p = self.points
        cdef vertex_t *vertices = NULL
        cdef unsigned short *indices = NULL
        cdef float tex_x
        cdef char *buf = NULL
        cdef Texture texture = self.texture

        if count < 2:
            self.batch.clear_data()
            return

        if self._close:
            p = p + [p[0], p[1]]
            count += 1

        self.batch.set_mode('line_strip')
        if self._dash_offset != 0:
            if texture is None or texture._width != \
                (self._dash_length + self._dash_offset) or \
                texture._height != 1:

                self.texture = texture = Texture.create(
                        size=(self._dash_length + self._dash_offset, 1))
                texture.wrap = 'repeat'

            # create a buffer to fill our texture
            buf = <char *>malloc(4 * (self._dash_length + self._dash_offset))
            memset(buf, 255, self._dash_length * 4)
            memset(buf + self._dash_length * 4, 0, self._dash_offset * 4)
            p_str = buf[:(self._dash_length + self._dash_offset) * 4]

            self.texture.blit_buffer(p_str, colorfmt='rgba', bufferfmt='ubyte')
            free(buf)

        elif texture is not None:
            self.texture = None

        vertices = <vertex_t *>malloc(count * sizeof(vertex_t))
        if vertices == NULL:
            raise MemoryError('vertices')

        indices = <unsigned short *>malloc(count * sizeof(unsigned short))
        if indices == NULL:
            free(vertices)
            raise MemoryError('indices')

        tex_x = 0
        for i in xrange(count):
            if self._dash_offset != 0 and i > 0:
                tex_x += sqrt(
                        pow(p[i * 2]     - p[(i - 1) * 2], 2)  +
                        pow(p[i * 2 + 1] - p[(i - 1) * 2 + 1], 2)) / (
                                self._dash_length + self._dash_offset)

                vertices[i].s0 = tex_x
                vertices[i].t0 = 0

            vertices[i].x = p[i * 2]
            vertices[i].y = p[i * 2 + 1]
            indices[i] = i

        self.batch.set_data(vertices, <int>count, indices, <int>count)

        free(vertices)
        free(indices)

    cdef void build_extended(self):
        cdef int i, j
        cdef long count = len(self.points) / 2
        cdef list p = self.points
        cdef vertex_t *vertices = NULL
        cdef unsigned short *indices = NULL
        cdef float tex_x
        cdef int cap
        cdef char *buf = NULL
        cdef Texture texture = self.texture

        self._bxmin = 999999999
        self._bymin = 999999999
        self._bxmax = -999999999
        self._bymax = -999999999

        if count < 2:
            self.batch.clear_data()
            return

        cap = self._cap
        if self._close and count > 2:
            p = p + p[0:4]
            count += 2
            cap = LINE_CAP_NONE

        self.batch.set_mode('triangles')
        cdef unsigned long vertices_count = (count - 1) * 4
        cdef unsigned long indices_count = (count - 1) * 6
        cdef unsigned int iv = 0, ii = 0

        if self._joint == LINE_JOINT_BEVEL:
            indices_count += (count - 2) * 3
            vertices_count += (count - 2)
        elif self._joint == LINE_JOINT_ROUND:
            indices_count += (self._joint_precision * 3) * (count - 2)
            vertices_count += (self._joint_precision) * (count - 2)
        elif self._joint == LINE_JOINT_MITER:
            indices_count += (count - 2) * 6
            vertices_count += (count - 2) * 2

        if cap == LINE_CAP_SQUARE:
            indices_count += 12
            vertices_count += 4
        elif cap == LINE_CAP_ROUND:
            indices_count += (self._cap_precision * 3) * 2
            vertices_count += (self._cap_precision) * 2

        vertices = <vertex_t *>malloc(vertices_count * sizeof(vertex_t))
        if vertices == NULL:
            raise MemoryError('vertices')

        indices = <unsigned short *>malloc(indices_count * sizeof(unsigned short))
        if indices == NULL:
            free(vertices)
            raise MemoryError('indices')

        cdef double ax, ay, bx, _by, cx, cy, angle, a1, a2
        cdef double x1, y1, x2, y2, x3, y3, x4, y4
        cdef double sx1, sy1, sx4, sy4, sangle
        cdef double pcx, pcy, px1, py1, px2, py2, px3, py3, px4, py4, pangle, pangle2
        cdef double w = self._width
        cdef double ix, iy
        cdef unsigned int piv, pii2, piv2
        cdef double jangle
        angle = sangle = 0
        piv = pcx = pcy = cx = cy = ii = iv = ix = iy = 0
        px1 = px2 = px3 = px4 = py1 = py2 = py3 = py4 = 0
        sx1 = sy1 = sx4 = sy4 = 0
        x1 = x2 = x3 = x4 = y1 = y2 = y3 = y4 = 0
        cdef double cos1 = 0, cos2 = 0, sin1 = 0, sin2 = 0
        for i in range(0, count - 1):
            ax = p[i * 2]
            ay = p[i * 2 + 1]
            bx = p[i * 2 + 2]
            _by = p[i * 2 + 3]

            if i > 0 and self._joint != LINE_JOINT_NONE:
                pcx = cx
                pcy = cy
                px1 = x1
                px2 = x2
                px3 = x3
                px4 = x4
                py1 = y1
                py2 = y2
                py3 = y3
                py4 = y4

            piv2 = piv
            piv = iv
            pangle2 = pangle
            pangle = angle

            # calculate the orientation of the segment, between pi and -pi
            cx = bx - ax
            cy = _by - ay
            angle = atan2(cy, cx)
            a1 = angle - PI2
            a2 = angle + PI2

            # calculate the position of the segment
            cos1 = cos(a1) * w
            sin1 = sin(a1) * w
            cos2 = cos(a2) * w
            sin2 = sin(a2) * w
            x1 = ax + cos1
            y1 = ay + sin1
            x4 = ax + cos2
            y4 = ay + sin2
            x2 = bx + cos1
            y2 = _by + sin1
            x3 = bx + cos2
            y3 = _by + sin2

            if i == 0:
                sx1 = x1
                sy1 = y1
                sx4 = x4
                sy4 = y4
                sangle = angle

            indices[ii    ] = iv
            indices[ii + 1] = iv + 1
            indices[ii + 2] = iv + 2
            indices[ii + 3] = iv
            indices[ii + 4] = iv + 2
            indices[ii + 5] = iv + 3
            ii += 6

            vertices[iv].x = x1
            vertices[iv].y = y1
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            iv += 1
            vertices[iv].x = x2
            vertices[iv].y = y2
            vertices[iv].s0 = 1
            vertices[iv].t0 = 0
            iv += 1
            vertices[iv].x = x3
            vertices[iv].y = y3
            vertices[iv].s0 = 1
            vertices[iv].t0 = 1
            iv += 1
            vertices[iv].x = x4
            vertices[iv].y = y4
            vertices[iv].s0 = 0
            vertices[iv].t0 = 1
            iv += 1

            # joint generation
            if i == 0 or self._joint == LINE_JOINT_NONE:
                continue

            # calculate the angle of the previous and current segment
            jangle = atan2(
                cx * pcy - cy * pcx,
                cx * pcx + cy * pcy)

            # in case of the angle is NULL, avoid the generation
            if jangle == 0:
                if self._joint == LINE_JOINT_ROUND:
                    vertices_count -= self._joint_precision
                    indices_count -= self._joint_precision * 3
                elif self._joint == LINE_JOINT_BEVEL:
                    vertices_count -= 1
                    indices_count -= 3
                elif self._joint == LINE_JOINT_MITER:
                    vertices_count -= 2
                    indices_count -= 6
                continue

            if self._joint == LINE_JOINT_BEVEL:
                vertices[iv].x = ax
                vertices[iv].y = ay
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                if jangle < 0:
                    indices[ii] = piv2 + 1
                    indices[ii + 1] = piv
                    indices[ii + 2] = iv
                else:
                    indices[ii] = piv2 + 2
                    indices[ii + 1] = piv + 3
                    indices[ii + 2] = iv
                ii += 3
                iv += 1

            elif self._joint == LINE_JOINT_MITER:
                vertices[iv].x = ax
                vertices[iv].y = ay
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                if jangle < 0:
                    if line_intersection(px1, py1, px2, py2, x1, y1, x2, y2, &ix, &iy) == 0:
                        vertices_count -= 2
                        indices_count -= 6
                        continue
                    vertices[iv + 1].x = ix
                    vertices[iv + 1].y = iy
                    vertices[iv + 1].s0 = 0
                    vertices[iv + 1].t0 = 0
                    indices[ii] = iv
                    indices[ii + 1] = iv + 1
                    indices[ii + 2] = piv2 + 1
                    indices[ii + 3] = iv
                    indices[ii + 4] = piv
                    indices[ii + 5] = iv + 1
                    ii += 6
                    iv += 2
                else:
                    if line_intersection(px3, py3, px4, py4, x3, y3, x4, y4, &ix, &iy) == 0:
                        vertices_count -= 2
                        indices_count -= 6
                        continue
                    vertices[iv + 1].x = ix
                    vertices[iv + 1].y = iy
                    vertices[iv + 1].s0 = 0
                    vertices[iv + 1].t0 = 0
                    indices[ii] = iv
                    indices[ii + 1] = iv + 1
                    indices[ii + 2] = piv2 + 2
                    indices[ii + 3] = iv
                    indices[ii + 4] = piv + 3
                    indices[ii + 5] = iv + 1
                    ii += 6
                    iv += 2



            elif self._joint == LINE_JOINT_ROUND:

                # cap end
                if jangle < 0:
                    a1 = pangle2 - PI2
                    a2 = angle + PI2
                    a0 = a2
                    step = (abs(jangle)) / float(self._joint_precision)
                    pivstart = piv + 3
                    pivend = piv2 + 1
                else:
                    a1 = angle - PI2
                    a2 = pangle2 + PI2
                    a0 = a1
                    step = -(abs(jangle)) / float(self._joint_precision)
                    pivstart = piv
                    pivend = piv2 + 2
                siv = iv
                vertices[iv].x = ax
                vertices[iv].y = ay
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                iv += 1
                for j in xrange(0, self._joint_precision - 1):
                    vertices[iv].x = ax - cos(a0 - step * j) * w
                    vertices[iv].y = ay - sin(a0 - step * j) * w
                    vertices[iv].s0 = 0
                    vertices[iv].t0 = 0
                    if j == 0:
                        indices[ii] = siv
                        indices[ii + 1] = pivstart
                        indices[ii + 2] = iv
                    else:
                        indices[ii] = siv
                        indices[ii + 1] = iv - 1
                        indices[ii + 2] = iv
                    iv += 1
                    ii += 3
                indices[ii] = siv
                indices[ii + 1] = iv - 1
                indices[ii + 2] = pivend
                ii += 3

        # caps
        if cap == LINE_CAP_SQUARE:
            vertices[iv].x = x2 + cos(angle) * w
            vertices[iv].y = y2 + sin(angle) * w
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            vertices[iv + 1].x = x3 + cos(angle) * w
            vertices[iv + 1].y = y3 + sin(angle) * w
            vertices[iv + 1].s0 = 0
            vertices[iv + 1].t0 = 0
            indices[ii] = piv + 1
            indices[ii + 1] = piv + 2
            indices[ii + 2] = iv + 1
            indices[ii + 3] = piv + 1
            indices[ii + 4] = iv
            indices[ii + 5] = iv + 1
            ii += 6
            iv += 2
            vertices[iv].x = sx1 - cos(sangle) * w
            vertices[iv].y = sy1 - sin(sangle) * w
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            vertices[iv + 1].x = sx4 - cos(sangle) * w
            vertices[iv + 1].y = sy4 - sin(sangle) * w
            vertices[iv + 1].s0 = 0
            vertices[iv + 1].t0 = 0
            indices[ii] = 0
            indices[ii + 1] = 3
            indices[ii + 2] = iv + 1
            indices[ii + 3] = 0
            indices[ii + 4] = iv
            indices[ii + 5] = iv + 1
            ii += 6
            iv += 2

        elif cap == LINE_CAP_ROUND:

            # cap start
            a1 = sangle - PI2
            a2 = sangle + PI2
            step = (a1 - a2) / float(self._cap_precision)
            siv = iv
            cx = p[0]
            cy = p[1]
            vertices[iv].x = cx
            vertices[iv].y = cy
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            iv += 1
            for i in xrange(0, self._cap_precision - 1):
                vertices[iv].x = cx + cos(a1 + step * i) * w
                vertices[iv].y = cy + sin(a1 + step * i) * w
                vertices[iv].s0 = 1
                vertices[iv].t0 = 1
                if i == 0:
                    indices[ii] = siv
                    indices[ii + 1] = 0
                    indices[ii + 2] = iv
                else:
                    indices[ii] = siv
                    indices[ii + 1] = iv - 1
                    indices[ii + 2] = iv
                iv += 1
                ii += 3
            indices[ii] = siv
            indices[ii + 1] = iv - 1
            indices[ii + 2] = 3
            ii += 3

            # cap end
            a1 = angle - PI2
            a2 = angle + PI2
            step = (a2 - a1) / float(self._cap_precision)
            siv = iv
            cx = p[-2]
            cy = p[-1]
            vertices[iv].x = cx
            vertices[iv].y = cy
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            iv += 1
            for i in xrange(0, self._cap_precision - 1):
                vertices[iv].x = cx + cos(a1 + step * i) * w
                vertices[iv].y = cy + sin(a1 + step * i) * w
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                if i == 0:
                    indices[ii] = siv
                    indices[ii + 1] = piv + 1
                    indices[ii + 2] = iv
                else:
                    indices[ii] = siv
                    indices[ii + 1] = iv - 1
                    indices[ii + 2] = iv
                iv += 1
                ii += 3
            indices[ii] = siv
            indices[ii + 1] = iv - 1
            indices[ii + 2] = piv + 2
            ii += 3

        # compute bbox
        for i in xrange(vertices_count):
            if vertices[i].x < self._bxmin:
                self._bxmin = vertices[i].x
            if vertices[i].x > self._bxmax:
                self._bxmax = vertices[i].x
            if vertices[i].y < self._bymin:
                self._bymin = vertices[i].y
            if vertices[i].y > self._bymax:
                self._bymax = vertices[i].y

        self.batch.set_data(vertices, <int>vertices_count,
                           indices, <int>indices_count)

        free(vertices)
        free(indices)



    property points:
        '''Property for getting/settings points of the line

        .. warning::

            This will always reconstruct the whole graphics from the new points
            list. It can be very CPU expensive.
        '''
        def __get__(self):
            return self._points
        def __set__(self, points):
            self._points = list(points)
            self.flag_update()

    property dash_length:
        '''Property for getting/setting the length of the dashes in the curve

        .. versionadded:: 1.0.8
        '''
        def __get__(self):
            return self._dash_length

        def __set__(self, value):
            if value < 0:
                raise GraphicException('Invalid dash_length value, must be >= 0')
            self._dash_length = value
            self.flag_update()

    property dash_offset:
        '''Property for getting/setting the offset between the dashes in the curve

        .. versionadded:: 1.0.8
        '''
        def __get__(self):
            return self._dash_offset

        def __set__(self, value):
            if value < 0:
                raise GraphicException('Invalid dash_offset value, must be >= 0')
            self._dash_offset = value
            self.flag_update()

    property width:
        '''Determine the width of the line, defaults to 1.0.

        .. versionadded:: 1.4.1
        '''
        def __get__(self):
            return self._width

        def __set__(self, value):
            if value <= 0:
                raise GraphicException('Invalid width value, must be > 0')
            self._width = value
            self.flag_update()

    property cap:
        '''Determine the cap of the line, defaults to 'round'. Can be one of
        'none', 'square' or 'round'

        .. versionadded:: 1.4.1
        '''
        def __get__(self):
            if self._cap == LINE_CAP_SQUARE:
                return 'square'
            elif self._cap == LINE_CAP_ROUND:
                return 'round'
            return 'none'

        def __set__(self, value):
            if value not in ('none', 'square', 'round'):
                raise GraphicException('Invalid cap, must be one of '
                        '"none", "square", "round"')
            if value == 'square':
                self._cap = LINE_CAP_SQUARE
            elif value == 'round':
                self._cap = LINE_CAP_ROUND
            else:
                self._cap = LINE_CAP_NONE
            self.flag_update()

    property joint:
        '''Determine the join of the line, defaults to 'round'. Can be one of
        'none', 'round', 'bevel', 'miter'.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            if self._joint == LINE_JOINT_ROUND:
                return 'round'
            elif self._joint == LINE_JOINT_BEVEL:
                return 'bevel'
            elif self._joint == LINE_JOINT_MITER:
                return 'miter'
            return 'none'

        def __set__(self, value):
            if value not in ('none', 'miter', 'bevel', 'round'):
                raise GraphicException('Invalid joint, must be one of '
                    '"none", "miter", "bevel", "round"')
            if value == 'round':
                self._joint = LINE_JOINT_ROUND
            elif value == 'bevel':
                self._joint = LINE_JOINT_BEVEL
            elif value == 'miter':
                self._joint = LINE_JOINT_MITER
            else:
                self._joint = LINE_JOINT_NONE
            self.flag_update()

    property cap_precision:
        '''Number of iteration for drawing the "round" cap, defaults to 10.
        The cap_precision must be at least 1.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            return self._cap_precision

        def __set__(self, value):
            if value < 1:
                raise GraphicException('Invalid cap_precision value, must be >= 1')
            self._cap_precision = int(value)
            self.flag_update()

    property joint_precision:
        '''Number of iteration for drawing the "round" joint, defaults to 10.
        The joint_precision must be at least 1.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            return self._joint_precision

        def __set__(self, value):
            if value < 1:
                raise GraphicException('Invalid joint_precision value, must be >= 1')
            self._joint_precision = int(value)
            self.flag_update()

    property close:
        '''If True, the line will be closed.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            return self._close

        def __set__(self, value):
            self._close = int(bool(value))
            self.flag_update()

    property ellipse:
        '''Use this property to build an ellipse, without calculating the
        :attr:`points`. You can only set this property, not get it.

        The argument must be a tuple of (x, y, width, height, angle_start,
        angle_end, segments):

        * x and y represent the bottom left of the ellipse
        * width and height represent the size of the ellipse
        * (optional) angle_start and angle_end are in degree. The default
            value is 0 and 360.
        * (optional) segments is the precision of the ellipse. The default
            value is calculated from the range between angle.

        Note that it's up to you to :attr:`close` the ellipse or not.

        For example, for building a simple ellipse, in python::

            # simple ellipse
            Line(ellipse=(0, 0, 150, 150))

            # only from 90 to 180 degrees
            Line(ellipse=(0, 0, 150, 150, 90, 180))

            # only from 90 to 180 degrees, with few segments
            Line(ellipse=(0, 0, 150, 150, 90, 180, 20))

        .. versionadded:: 1.4.1
        '''

        def __set__(self, args):
            if args == None:
                raise GraphicException(
                        'Invalid ellipse value: {0!r}'.format(args))
            if len(args) not in (4, 6, 7):
                raise GraphicException('Invalid number of arguments: '
                        '{0} instead of 4, 6 or 7.'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_ELLIPSE
            self.flag_update()

    cdef void prebuild_ellipse(self):
        cdef double x, y, w, h, angle_start = 0, angle_end = 360
        cdef int angle_dir, segments = 0
        cdef double angle_range
        cdef tuple args = self._mode_args

        if len(args) == 4:
            x, y, w, h = args
        elif len(args) == 6:
            x, y, w, h, angle_start, angle_end = args
        elif len(args) == 7:
            x, y, w, h, angle_start, angle_end, segments = args
            segments += 2
        else:
            x = y = w = h = 0
            assert(0)

        if angle_end > angle_start:
            angle_dir = 1
        else:
            angle_dir = -1
        if segments == 0:
            segments = int(abs(angle_end - angle_start) / 2) + 3
            if segments % 2 == 1:
                segments += 1
        # rad = deg * (pi / 180), where pi/180 = 0.0174...
        angle_start = angle_start * 0.017453292519943295
        angle_end = angle_end * 0.017453292519943295
        angle_range = abs(angle_end - angle_start) / (segments - 2)

        cdef list points = [0, ] * segments
        cdef double angle
        cdef double rx = w * 0.5
        cdef double ry = h * 0.5
        for i in xrange(0, segments, 2):
            angle = angle_start + (angle_dir * (i - 1) * angle_range)
            points[i] = (x + rx) + (rx * sin(angle))
            points[i + 1] = (y + ry) + (ry * cos(angle))

        self._points = points


    property circle:
        '''Use this property to build a circle, without calculate the
        :attr:`points`. You can only set this property, not get it.

        The argument must be a tuple of (center_x, center_y, radius, angle_start,
        angle_end, segments):

        * center_x and center_y represent the center of the circle
        * radius represent the radius of the circle
        * (optional) angle_start and angle_end are in degree. The default
            value is 0 and 360.
        * (optional) segments is the precision of the ellipse. The default
            value is calculated from the range between angle.

        Note that it's up to you to :attr:`close` the circle or not.

        For example, for building a simple ellipse, in python::

            # simple circle
            Line(circle=(150, 150, 50))

            # only from 90 to 180 degrees
            Line(circle=(150, 150, 50, 90, 180))

            # only from 90 to 180 degrees, with few segments
            Line(circle=(150, 150, 50, 90, 180, 20))

        .. versionadded:: 1.4.1
        '''

        def __set__(self, args):
            if args == None:
                raise GraphicException(
                        'Invalid circle value: {0!r}'.format(args))
            if len(args) not in (3, 5, 6):
                raise GraphicException('Invalid number of arguments: '
                        '{0} instead of 3, 5 or 6.'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_CIRCLE
            self.flag_update()

    cdef void prebuild_circle(self):
        cdef double x, y, r, angle_start = 0, angle_end = 360
        cdef int angle_dir, segments = 0
        cdef double angle_range
        cdef tuple args = self._mode_args

        if len(args) == 3:
            x, y, r = args
        elif len(args) == 5:
            x, y, r, angle_start, angle_end = args
        elif len(args) == 6:
            x, y, r, angle_start, angle_end, segments = args
            segments += 1
        else:
            x = y = r = 0
            assert(0)

        if angle_end > angle_start:
            angle_dir = 1
        else:
            angle_dir = -1
        if segments == 0:
            segments = int(abs(angle_end - angle_start) / 2) + 3
        
        segmentpoints = segments * 2
        
        # rad = deg * (pi / 180), where pi/180 = 0.0174...
        angle_start = angle_start * 0.017453292519943295
        angle_end = angle_end * 0.017453292519943295
        angle_range = abs(angle_end - angle_start) / (segmentpoints - 2)

        cdef list points = [0, ] * segmentpoints
        cdef double angle
        for i in xrange(0, segmentpoints, 2):
            angle = angle_start + (angle_dir * i * angle_range)
            points[i] = x + (r * sin(angle))
            points[i + 1] = y + (r * cos(angle))
        self._points = points

    property rectangle:
        '''Use this property to build a rectangle, without calculating the
        :attr:`points`. You can only set this property, not get it.

        The argument must be a tuple of (x, y, width, height)
        angle_end, segments):

        * x and y represent the bottom-left position of the rectangle
        * width and height represent the size

        The line is automatically closed.

        Usage::

            Line(rectangle=(0, 0, 200, 200))

        .. versionadded:: 1.4.1
        '''

        def __set__(self, args):
            if args == None:
                raise GraphicException(
                        'Invalid rectangle value: {0!r}'.format(args))
            if len(args) != 4:
                raise GraphicException('Invalid number of arguments: '
                        '{0} instead of 4.'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_RECTANGLE
            self.flag_update()

    cdef void prebuild_rectangle(self):
        cdef double x, y, width, height
        cdef int angle_dir, segments = 0
        cdef double angle_range
        cdef tuple args = self._mode_args

        if args == None:
            raise GraphicException(
                    'Invalid ellipse value: {0!r}'.format(args))

        if len(args) == 4:
            x, y, width, height = args
        else:
            x = y = width = height = 0
            assert(0)

        self._points = [x, y, x + width, y, x + width, y + height, x, y + height]
        self._close = 1

    property rounded_rectangle:
        '''Use this property to build a rectangle, without calculating the
        :attr:`points`. You can only set this property, not get it.

        The argument must be a tuple of one of the following forms:

        * (x, y, width, height, corner_radius)
        * (x, y, width, height, corner_radius, resolution)
        * (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4)
        * (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4, resolution)

        * x and y represent the bottom-left position of the rectangle
        * width and height represent the size
        * corner_radius is the number of pixels between two borders and the center of the circle arc joining them
        * resolution is the numper of line segment that will be used to draw the circle arc at each corner (defaults to 30)

        The line is automatically closed.

        Usage::

            Line(rounded_rectangle=(0, 0, 200, 200, 10, 20, 30, 40, 100))

        .. versionadded:: 1.9.0
        '''
        def __set__(self, args):
            if args == None:
                raise GraphicException(
                    'Invlid rounded rectangle value: {0!r}'.format(args))
            if len(args) not in (5, 6, 8, 9):
                raise GraphicException('invalid number of arguments:'
                        '{0} not in (5, 6, 8, 9)'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_ROUNDED_RECTANGLE
            self.flag_update()

    cdef void prebuild_rounded_rectangle(self):
        cdef float a, px, py, x, y, w, h, c1, c2, c3, c4
        cdef resolution = 30
        cdef int l = len(self._mode_args)

        self._points = []
        a = -PI
        x, y, w, h = self._mode_args [:4]

        if l == 5:
            c1 = c2 = c3 = c4 = self._mode_args[4]
        elif l == 6:
            c1 = c2 = c3 = c4 = self._mode_args[4]
            resolution = self._mode_args[5]
        elif l == 8:
            c1, c2, c3, c4 = self._mode_args[4:]
        else:  # l == 9, but else make the compiler happy about uninitialization
            c1, c2, c3, c4 = self._mode_args[4:8]
            resolution = self._mode_args[8]

        px = x + c1
        py = y + c1

        while a < - PI / 2.:
            a += pi / resolution
            self._points.extend([
                px + cos(a) * c1,
                py + sin(a) * c1])

        px = x + w - c2
        py = y + c2

        while a < 0:
            a += PI / resolution
            self._points.extend([
                px + cos(a) * c2,
                py + sin(a) * c2])

        px = x + w - c3
        py = y + h - c3

        while a < PI / 2.:
            a += PI / resolution
            self._points.extend([
                px + cos(a) * c3,
                py + sin(a) * c3])

        px = x + c4
        py = y + h - c4

        while a < PI:
            a += PI / resolution
            self._points.extend([
                px + cos(a) * c4,
                py + sin(a) * c4])

        self._close = 1

    property bezier:
        '''Use this property to build a bezier line, without calculating the
        :attr:`points`. You can only set this property, not get it.

        The argument must be a tuple of 2n elements, n being the number of points.

        Usage::

            Line(bezier=(x1, y1, x2, y2, x3, y3)

        .. versionadded:: 1.4.2

        .. note:: Bezier lines calculations are inexpensive for a low number of
            points, but complexity is quadratic, so lines with a lot of points
            can be very expensive to build, use with care!
        '''

        def __set__(self, args):
            if args == None or len(args) % 2:
                raise GraphicException(
                        'Invalid bezier value: {0!r}'.format(args))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_BEZIER
            self.flag_update()

    cdef void prebuild_bezier(self):
        cdef double x, y, l
        cdef int segments = self._bezier_precision
        cdef list T = list(self._mode_args)[:]

        self._points = []
        for x in xrange(segments):
            l = x / (1.0 * segments)
            # http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
            # as the list is in the form of (x1, y1, x2, y2...) iteration is
            # done on each item and the current item (xn or yn) in the list is
            # replaced with a calculation of "xn + x(n+1) - xn" x(n+1) is
            # placed at n+2. Each iteration makes the list one item shorter
            for i in range(1, len(T)):
                for j in xrange(len(T) - 2*i):
                    T[j] = T[j] + (T[j+2] - T[j]) * l

            # we got the coordinates of the point in T[0] and T[1]
            self._points.append(T[0])
            self._points.append(T[1])

        # add one last point to join the curve to the end
        self._points.append(T[-2])
        self._points.append(T[-1])

    property bezier_precision:
        '''Number of iteration for drawing the bezier between 2 segments,
        defaults to 180. The bezier_precision must be at least 1.

        .. versionadded:: 1.4.2
        '''

        def __get__(self):
            return self._bezier_precision

        def __set__(self, value):
            if value < 1:
                raise GraphicException('Invalid bezier_precision value, must be >= 1')
            self._bezier_precision = int(value)
            self.flag_update()


cdef class SmoothLine(Line):
    '''Experimental line using over-draw method to get better antialiasing
    results. It has few drawbacks:

    - drawing line with alpha will unlikely doesn't give the intended result if
      the line cross itself
    - no cap or joint are supported
    - it use a custom texture with premultiplied alpha
    - dash is not supported
    - line under 1px width are not supported, it will look the same

    .. warning::

        This is an unfinished work, experimental, subject to crash.

    .. versionadded:: 1.9.0
    '''

    cdef float _owidth

    def __init__(self, **kwargs):
        VertexInstruction.__init__(self, **kwargs)
        self._owidth = kwargs.get("overdraw_width") or 1.2
        self.batch.set_mode("triangles")
        self.texture = self.premultiplied_texture()

    def premultiplied_texture(self):
        texture = Texture.create(size=(4, 1), colorfmt="rgba")
        texture.add_reload_observer(self._smooth_reload_observer)
        self._smooth_reload_observer(texture)
        return texture

    cpdef _smooth_reload_observer(self, texture):
        cdef bytes GRADIENT_DATA = (
            b"\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
        texture.blit_buffer(GRADIENT_DATA, colorfmt="rgba")

    cdef void build(self):
        if self._mode == LINE_MODE_ELLIPSE:
            self.prebuild_ellipse()
        elif self._mode == LINE_MODE_CIRCLE:
            self.prebuild_circle()
        elif self._mode == LINE_MODE_RECTANGLE:
            self.prebuild_rectangle()
        elif self._mode == LINE_MODE_ROUNDED_RECTANGLE:
            self.prebuild_rounded_rectangle()
        elif self._mode == LINE_MODE_BEZIER:
            self.prebuild_bezier()

        self.build_smooth()

    cdef void apply(self):
        VertexInstruction.apply(self)
        return

    cdef void build_smooth(self):
        cdef:
            list p = self.points
            float width = max(0, (self._width - 1.))
            float owidth = width + self._owidth
            vertex_t *vertices = NULL
            unsigned short *indices = NULL
            unsigned short *tindices = NULL
            double ax, ay, bx = 0., by = 0., rx = 0., ry = 0., last_angle = 0., angle, av_angle
            float cos1, sin1, cos2, sin2, ocos1, ocos2, osin1, osin2
            long index, vindex, vcount, icount, iv, ii, max_vindex, count
            unsigned short i0, i1, i2, i3, i4, i5, i6, i7

        iv = vindex = 0
        count = len(p) / 2
        if count < 2:
            self.batch.clear_data()
            return

        vcount = count * 4
        icount = (count - 1) * 18
        if self._close:
            icount += 18

        vertices = <vertex_t *>malloc(vcount * sizeof(vertex_t))
        if vertices == NULL:
            raise MemoryError("vertices")

        indices = <unsigned short *>malloc(icount * sizeof(unsigned short))
        if indices == NULL:
            free(vertices)
            raise MemoryError("indices")

        if self._close:
            ax = p[-2]
            ay = p[-1]
            bx = p[0]
            by = p[1]
            rx = bx - ax
            ry = by - ay
            last_angle = atan2(ry, rx)

        max_index = len(p)
        for index in range(0, max_index, 2):
            ax = p[index]
            ay = p[index + 1]
            if index < max_index - 2:
                bx = p[index + 2]
                by = p[index + 3]
                rx = bx - ax
                ry = by - ay
                angle = atan2(ry, rx)
            else:
                angle = last_angle

            if index == 0 and not self._close:
                av_angle = angle
                ad_angle = pi
            else:
                av_angle = atan2(
                        sin(angle) + sin(last_angle),
                        cos(angle) + cos(last_angle))

                ad_angle = abs(pi - abs(angle - last_angle))

            a1 = av_angle - PI2
            a2 = av_angle + PI2
            '''
            cos1 = cos(a1) * width
            sin1 = sin(a1) * width
            cos2 = cos(a2) * width
            sin2 = sin(a2) * width
            ocos1 = cos(a1) * owidth
            osin1 = sin(a1) * owidth
            ocos2 = cos(a2) * owidth
            osin2 = sin(a2) * owidth
            print 'angle diff', ad_angle
            '''
            #l = width
            #ol = owidth

            if index == 0 or index >= max_index - 2:
                l = width
                ol = owidth
            else:
                la1 = last_angle - PI2
                la2 = angle - PI2
                ra1 = last_angle + PI2
                ra2 = angle + PI2
                ox = p[index - 2]
                oy = p[index - 1]
                if line_intersection(
                    ox + cos(la1) * width,
                    oy + sin(la1) * width,
                    ax + cos(la1) * width,
                    ay + sin(la1) * width,
                    ax + cos(la2) * width,
                    ay + sin(la2) * width,
                    bx + cos(la2) * width,
                    by + sin(la2) * width,
                    &rx, &ry) == 0:
                    #print 'ERROR LINE INTERSECTION 1'
                    pass

                l = sqrt((ax - rx) ** 2 + (ay - ry) ** 2)

                if line_intersection(
                    ox + cos(ra1) * owidth,
                    oy + sin(ra1) * owidth,
                    ax + cos(ra1) * owidth,
                    ay + sin(ra1) * owidth,
                    ax + cos(ra2) * owidth,
                    ay + sin(ra2) * owidth,
                    bx + cos(ra2) * owidth,
                    by + sin(ra2) * owidth,
                    &rx, &ry) == 0:
                    #print 'ERROR LINE INTERSECTION 2'
                    pass

                ol = sqrt((ax - rx) ** 2 + (ay - ry) ** 2)

            last_angle = angle

            #l = sqrt(width ** 2 * (1. / sin(av_angle)) ** 2)
            #l = width / tan(av_angle / 2.)
            #l = width * sqrt(1 + 1 / (av_angle / 2.))
            #l = 2 * (width * width * sin(av_angle))
            #l = 2 * (cos(av_angle / 2.) * width)
            #l = width / abs(cos(PI2 - 1.5 * ad_angle))
            cos1 = cos(a1) * l
            sin1 = sin(a1) * l
            cos2 = cos(a2) * l
            sin2 = sin(a2) * l

            #ol = sqrt(owidth ** 2 * (1. / sin(av_angle)) ** 2)
            #ol = owidth / tan(av_angle / 2.)
            #ol = owidth * sqrt(1 + 1 / (av_angle / 2.))
            #ol = 2 * (owidth * owidth * sin(av_angle))
            #ol = 2 * (cos(av_angle / 2.) * owidth)
            #ol = owidth / abs(cos(PI2 - 1.5 * ad_angle))
            ocos1 = cos(a1) * ol
            osin1 = sin(a1) * ol
            ocos2 = cos(a2) * ol
            osin2 = sin(a2) * ol

            x1 = ax + cos1
            y1 = ay + sin1
            x2 = ax + cos2
            y2 = ay + sin2

            ox1 = ax + ocos1
            oy1 = ay + osin1
            ox2 = ax + ocos2
            oy2 = ay + osin2

            vertices[iv].x = x1
            vertices[iv].y = y1
            vertices[iv].s0 = 0.5
            vertices[iv].t0 = 0.25
            iv += 1
            vertices[iv].x = x2
            vertices[iv].y = y2
            vertices[iv].s0 = 0.5
            vertices[iv].t0 = 0.75
            iv += 1
            vertices[iv].x = ox1
            vertices[iv].y = oy1
            vertices[iv].s0 = 1
            vertices[iv].t0 = 0
            iv += 1
            vertices[iv].x = ox2
            vertices[iv].y = oy2
            vertices[iv].s0 = 1
            vertices[iv].t0 = 1
            iv += 1

        tindices = indices
        for vindex in range(0, vcount - 4, 4):
            tindices[0] = vindex
            tindices[1] = vindex + 2
            tindices[2] = vindex + 6
            tindices[3] = vindex
            tindices[4] = vindex + 6
            tindices[5] = vindex + 4
            tindices[6] = vindex + 1
            tindices[7] = vindex
            tindices[8] = vindex + 4
            tindices[9] = vindex + 1
            tindices[10] = vindex + 4
            tindices[11] = vindex + 5
            tindices[12] = vindex + 3
            tindices[13] = vindex + 1
            tindices[14] = vindex + 5
            tindices[15] = vindex + 3
            tindices[16] = vindex + 5
            tindices[17] = vindex + 7
            tindices = tindices + 18

        if self._close:
            vindex = vcount - 4
            i0 = vindex
            i1 = vindex + 1
            i2 = vindex + 2
            i3 = vindex + 3
            i4 = 0
            i5 = 1
            i6 = 2
            i7 = 3
            tindices[0] = i0
            tindices[1] = i2
            tindices[2] = i6
            tindices[3] = i0
            tindices[4] = i6
            tindices[5] = i4
            tindices[6] = i1
            tindices[7] = i0
            tindices[8] = i4
            tindices[9] = i1
            tindices[10] = i4
            tindices[11] = i5
            tindices[12] = i3
            tindices[13] = i1
            tindices[14] = i5
            tindices[15] = i3
            tindices[16] = i5
            tindices[17] = i7
            tindices = tindices + 18

        #print 'tindices', <long>tindices, <long>indices, (<long>tindices - <long>indices) / sizeof(unsigned short)


        self.batch.set_data(vertices, <int>vcount, indices, <int>icount)

        #free(vertices)
        #free(indices)


    property overdraw_width:
        '''Determine the overdraw width of the line, defaults to 1.2
        '''
        def __get__(self):
            return self._owidth

        def __set__(self, value):
            if value <= 0:
                raise GraphicException('Invalid width value, must be > 0')
            self._owidth = value
            self.flag_update()

