#! /usr/bin/env python
# *-* coding: UTF-8 *-*

# kytten/override.py
# Copyrighted (C) 2009 by Conrad "Lynx" Wong
# Copyrighted (C) 2013 by "Parashurama"
from __future__ import unicode_literals, print_function

import pyglet
import pyglet.gl as gl

from .tools import string_to_unicode
from .base import xrange

KYTTEN_LAYOUT_GROUPS = {}
KYTTEN_LAYOUT_GROUP_REFCOUNTS = {}

_Line = pyglet.text.layout._Line

class KyttenEventDispatcher(pyglet.event.EventDispatcher):


    def remove_handlers(self, *args, **kwargs):
        '''Remove event handlers from the event stack.

        See `push_handlers` for the accepted argument types.  All handlers
        are removed from the first stack frame that contains any of the given
        handlers.  No error is raised if any handler does not appear in that
        frame, or if no stack frame contains any of the given handlers.

        If the stack frame is empty after removing the handlers, it is
        removed from the stack.  Note that this interferes with the expected
        symmetry of `push_handlers` and `pop_handlers`.
        '''
        handlers = list(self._get_handlers(args, kwargs))

        # Find the first stack frame containing any of the handlers
        def find_frame():
            for frame in self._event_stack:
                for name, handler in handlers:
                    try:
                        if frame[name] == handler:
                            return frame
                    except KeyError:
                        pass
        frame = find_frame()

        # No frame matched; no error.
        if not frame:
            return

        # Remove each handler from the frame.
        for name, handler in handlers:
            try:
                if frame[name] == handler:
                    del frame[name]
            except KeyError:
                pass

        # Remove the frame if it's empty.
        if not frame:
            self._event_stack.remove(frame)

    def remove_handler(self, name, handler):
        '''Remove a single event handler.

        The given event handler is removed from the first handler stack frame
        it appears in.  The handler must be the exact same callable as passed
        to `set_handler`, `set_handlers` or `push_handlers`; and the name
        must match the event type it is bound to.

        No error is raised if the event handler is not set.

        :Parameters:
            `name` : str
                Name of the event type to remove.
            `handler` : callable
                Event handler to remove.
        '''
        for frame in self._event_stack:
            try: # set comparison to EQUAL (is testing was failling for some instance bond functions)
                if frame[name] == handler:
                    del frame[name]
                    break
            except KeyError:
                pass

class TextLayoutGroup_KYTTEN_OVERRIDE(pyglet.graphics.Group):
    def set_state(self):
        gl.glPushAttrib(gl.GL_ENABLE_BIT | gl.GL_CURRENT_BIT)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFuncSeparate(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA, gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA)
        # To Allow Normal Rendering when Buffering with FrameBufferObject
        # Without this option : problem with alpha blending when rendering buffered GUI textures
        #Also in context.glContext

    def unset_state(self):
        gl.glPopAttrib()


class ScrollableTextLayoutGroup_KYTTEN_OVERRIDE(pyglet.text.layout.ScrollableTextLayoutGroup):

    def set_state(self):
        gl.glPushAttrib(gl.GL_ENABLE_BIT | gl.GL_TRANSFORM_BIT | gl.GL_CURRENT_BIT)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFuncSeparate(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA, gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA)
        # To Allow Normal Rendering when Buffering with FrameBufferObject
        # Without this option : problem with alpha blending when rendering buffered GUI textures
        #Also in context.glContext

        # Disable clipping planes to check culling.
        gl.glEnable(gl.GL_CLIP_PLANE0)
        gl.glEnable(gl.GL_CLIP_PLANE1)
        gl.glEnable(gl.GL_CLIP_PLANE2)
        gl.glEnable(gl.GL_CLIP_PLANE3)
        # Left
        gl.glClipPlane(gl.GL_CLIP_PLANE0, (gl.GLdouble * 4)(
                    1, 0, 0, -(self._clip_x - 1)))
        # Top
        gl.glClipPlane(gl.GL_CLIP_PLANE1, (gl.GLdouble * 4)(
                    0, -1, 0, self._clip_y))
        # Right
        gl.glClipPlane(gl.GL_CLIP_PLANE2, (gl.GLdouble * 4)(
                    -1, 0, 0, self._clip_x + self._clip_width + 1))
        # Bottom
        gl.glClipPlane(gl.GL_CLIP_PLANE3, (gl.GLdouble * 4)(
                    0, 1, 0, -(self._clip_y - self._clip_height)))
        gl.glTranslatef(self.translate_x, self.translate_y, 0)


def GetKyttenLayoutGroups(group):
    if not group in  KYTTEN_LAYOUT_GROUPS:
        top_group = TextLayoutGroup_KYTTEN_OVERRIDE(group)
        background_group = pyglet.graphics.OrderedGroup(0, top_group)
        foreground_group = pyglet.text.layout.TextLayoutForegroundGroup(1, top_group)
        foreground_decoration_group = pyglet.text.layout.TextLayoutForegroundDecorationGroup(2, top_group)

        KYTTEN_LAYOUT_GROUPS[group] = (top_group,
                                       background_group,
                                       foreground_group,
                                       foreground_decoration_group)
        KYTTEN_LAYOUT_GROUP_REFCOUNTS[group] = 0
    KYTTEN_LAYOUT_GROUP_REFCOUNTS[group] += 1
    return KYTTEN_LAYOUT_GROUPS[group]

def ReleaseKyttenLayoutGroups(group):
    KYTTEN_LAYOUT_GROUP_REFCOUNTS[group] -= 1
    if not KYTTEN_LAYOUT_GROUP_REFCOUNTS[group]:
        del KYTTEN_LAYOUT_GROUP_REFCOUNTS[group]
        del KYTTEN_LAYOUT_GROUPS[group]

pyglet_IncrementalTextLayout = pyglet.text.layout.IncrementalTextLayout

class KyttenIncrementalTextLayout(pyglet_IncrementalTextLayout):
    def _init_groups(self, group):
        # Scrollable layout never shares group because of translation.
        self.top_group = ScrollableTextLayoutGroup_KYTTEN_OVERRIDE(group)
        self.background_group = pyglet.graphics.OrderedGroup(0, self.top_group)
        self.foreground_group = pyglet.text.layout.TextLayoutForegroundGroup(1, self.top_group)
        self.foreground_decoration_group =  pyglet.text.layout.TextLayoutForegroundDecorationGroup(2, self.top_group)

    def _update(self):
        if not self._update_enabled:
            return

        trigger_update_event = (self.invalid_glyphs.is_invalid() or
                                self.invalid_flow.is_invalid() or
                                self.invalid_lines.is_invalid())

        # Special care if there is no text:
        if not self.glyphs:
            for line in self.lines:
                line.delete(self)
            del self.lines[:]
            self.lines.append(_Line(0))
            font = self.document.get_font(0, dpi=self._dpi)
            self.lines[0].ascent = font.ascent
            self.lines[0].descent = font.descent
            self.lines[0].paragraph_begin = self.lines[0].paragraph_end = True
            self.invalid_lines.invalidate(0, 1)

        self._update_glyphs()
        self._update_flow_glyphs()
        self._update_flow_lines()
        '''
        #not useful updated in: self.view_y = self.view_y property
        #self._update_visible_lines()
        #self._update_vertex_lists()
        #self.top_group.top = self._get_top(self.lines)
        '''
        # Reclamp view_y in case content height has changed and reset top of
        # content.
        self.view_y = self.view_y
        self.top_group.top = self._get_top(self._get_lines())

        if trigger_update_event:
            self.dispatch_event('on_layout_update')

    def select_all(self):
        self.set_selection(0, len(self.document.text))

    def get_selection_text(self):
        return self.document.text[self.selection_start: self.selection_end]

    def get_selection(self):
        return (self.selection_start, self.selection_end)

class KyttenLabel(pyglet.text.Label):
    def __init__(self, text, *args, **kwargs):
        pyglet.text.Label.__init__(self, string_to_unicode(text), *args, **kwargs)

    def _set_text(self, text):
        self.document.text = string_to_unicode(text)
    text = property(pyglet.text.Label._get_text, _set_text)

    def _init_groups(self, group):
        if not group:
            return # use the default groups
        self.top_group, self.background_group, self.foreground_group,  self.foreground_decoration_group = GetKyttenLayoutGroups(group)

    def teardown(self):
        pyglet.text.Label.delete(self)
        group = self.top_group.parent
        if group is not None:
            ReleaseKyttenLayoutGroups(group)
            self.top_group = self.background_self = self.foreground_group = self.foreground_decoration_group = None

class KyttenInputLabel(KyttenLabel):
    def _get_left(self):
        if self._multiline:
            width = self._width
        else:
            width = self.content_width
            if self.width and width > self.width:
                # align to right edge, clip left
                return self._x + self.width - width

        if self._anchor_x == 'left':
            return self._x
        elif self._anchor_x == 'center':
            return self._x - width // 2
        elif self._anchor_x == 'right':
            return self._x - width
        else:
            assert False, 'Invalid anchor_x'

    def _update(self):
        pyglet.text.Label._update(self)

        # Iterate through our vertex lists and break if we need to clip
        remove = []
        if self.width and not self._multiline:
            for vlist in self._vertex_lists:
                num_quads = len(vlist.vertices) / 8
                remove_quads = 0
                has_quads = False
                for n in xrange(0, num_quads):
                    x1, y1, x2, y2, x3, y3, x4, y4 = vlist.vertices[n*8:n*8+8]
                    tx1, ty1, tz1, tx2, ty2, tz2, \
                       tx3, ty3, tz3, tx4, ty4, tz4 = \
                       vlist.tex_coords[n*12:n*12+12]
                    if x2 >= self._x:
                        has_quads = True
                        m = n - remove_quads  # shift quads left
                        if x1 < self._x:  # clip on left side
                            percent = (float(self._x) - float(x1)) / \
                                      (float(x2) - float(x1))
                            x1 = x4 = max(self._x, x1)
                            tx1 = tx4 = (tx2 - tx1) * percent + tx1
                        vlist.vertices[m*8:m*8+8] = \
                            [x1, y1, x2, y2, x3, y3, x4, y4]
                        vlist.tex_coords[m*12:m*12+12] = \
                             [tx1, ty1, tz1, tx2, ty2, tz2,
                              tx3, ty3, tz3, tx4, ty4, tz4]
                    else:
                        # We'll delete quads entirely not visible
                        remove_quads = remove_quads + 1
                if remove_quads == num_quads:
                    remove.append(vlist)
                elif remove_quads > 0:
                    vlist.resize((num_quads - remove_quads) * 4)
        for vlist in remove:
            vlist.delete()
            self._vertex_lists.remove(vlist)

