#!/usr/bin/env python

# Copyright (C) 2011  Sam Bull

"""
Base widget, all widgets inherit from this.

"""

from pygame.locals import Rect
from OpenGL.GL import *
import FTGL

from ..surface import SurfaceBase


class OpenGLImage(SurfaceBase):
    """
    Class used to emulate the interface of Surface for OpenGL drawing.

    Functions should be used in the same manner as pygame.Surface.
    Differences are shown in documentation, otherwise assume it to
    function the same as the equivalent pygame.Surface function.

    """
    def __init__(self, surf, parent=None, **kwargs):
        self._a = None
        self._lock = False
        self.display_list = []
        self._children = []
        if parent:
            self._parent = parent # Parent surface used for _abs
        else:
            self._parent = self._default_screen

        self._rect = Rect((0,0), (0,0))
        if isinstance(surf, (tuple,list)):
            self._rect.size = surf
        elif isinstance(surf, OpenGLFont):
            self._rect.size = (surf.size)
            self.display_list.extend(surf.display_list)
        elif isinstance(surf, OpenGLImage):
            self._rect = surf._rect
            self._a = surf._a
            self.display_list = surf.display_list
            self._children = surf._children

    def blit(self, surf, pos=None):
        assert isinstance(surf, OpenGLImage)
        if surf not in self._children:
            if pos is not None: surf.pos = pos
            surf._parent = self
            self._children.append(surf)

    def draw(self):
        glLoadIdentity()
        if self.rect.w <= self._parent.rect.w and \
           self.rect.h <= self._parent.rect.h:
            # Mask the area, so nothing is drawn outside surface area.
            bottom = self._default_screen.h - self.rect_abs.bottom
            glScissor(self.rect_abs.x, bottom,
                      self.rect.w+1, self.rect.h+1)
        glTranslatef(self.pos_abs[0], self.pos_abs[1], 0)
        for dl,col in self.display_list:
            if col is not None: glColor(col[0], col[1], col[2], self.a)
            glCallList(dl)
        for child in self._children:
            child.draw()

    def fill(self, col=0, rect=None, special_flags=0):
        """If col == 0 and rect is None, clears image with no fill."""
        if rect is None:
            # Clear the surface
            self.display_list = []
            self._children = []
            rect = Rect((0,0), self.size)
        if col != 0:
            col = [c/255. for c in col]
            dl = glGenLists(1)
            glNewList(dl, GL_COMPILE)
            glRectfv(rect.topleft, rect.bottomright)
            glEndList()
            self.display_list.append((dl, col))

    def copy(self):
        return OpenGLImage(self)

    def get_size(self):
        return self.size

    def set_alpha(self, alpha):
        self._a = alpha/255.

    def set_at(self, vertex, col):
        col = [c/255. for c in col]

        if self._lock:
            glColor(*col)
        else:
            dl = glGenLists(1)
            glNewList(dl, GL_COMPILE)
            glBegin(GL_POINTS)

        glVertex(vertex)

        if not self._lock:
            glEnd()
            glEndList()
            surf.display_list.append((dl, col))

    def lock(self):
        """
        Should only be used for multiple set_at() calls.
        Should not be used in conjuction with the draw functions.

        """
        self._lock = True
        self._lock_dl = glGenLists(1)
        glNewList(self._lock_dl, GL_COMPILE)
        glBegin(GL_POINTS)

    def unlock(self):
        glEnd()
        glEndList()
        self.display_list.append((self._lock_dl, None))
        del self._lock_dl
        self._lock = False

    def replace(self, surf, **kwargs):
        self._rect.size = surf.size
        self.display_list = surf.display_list[:]
        self._children = surf._children[:]

    @property
    def a(self):
        if self._a is not None:
            return self._a
        else:
            return self._parent.a

    # --- Dummy methods. Ignore. ---

    def __call__(self):
        return self

    def set_colorkey(self, col):
        pass

class OpenGLFont():
    """
    Wraps the FTGL.TextureFont to allow it to be added
    to an OpenGLImage object in the same manner as pygame.Font.

    """
    def __init__(self, font, size):
        self._children = []
        self.font = FTGL.TextureFont(font)
        self.font.FaceSize(16)
        self.y_offset = self.font.line_height * .75

    def render(self, text, antialias, color, background=None):
        text = text.encode()
        col = [c/255. for c in color]
        dl = glGenLists(1)
        self.size = (self.font.Advance(text), self.font.line_height)
        glNewList(dl, GL_COMPILE)
        glPushMatrix()
        # Flip text right way up
        glMultMatrixf((1, 0, 0, 0,
                       0,-1, 0, 0,
                       0, 0, 1, 0,
                       0, self.y_offset, 0, 1))
        self.font.Render(text)
        glPopMatrix()
        glEndList()
        self.display_list = [(dl, col)]
        return self

class Draw():
    """
    Class to emulate the pygame.draw module.
    Functions should work in the same manner.

    """
    def rect(self, surf, col, rect, width=0):
        col = [c/255. for c in col]
        dl = glGenLists(1)
        glNewList(dl, GL_COMPILE)
        if not width:
            glRectfv(rect.topleft, rect.bottomright)
        else:
            if width > 1: width -= 1
            hw = width/2.
            glLineWidth(width)
            glBegin(GL_LINES)
            glVertex(rect.x, rect.y + hw)
            glVertex(rect.right - width, rect.y + hw)
            # (hw%1) fixes line rendering off by a pixel
            glVertex(rect.right - hw, rect.y + (hw%1))
            glVertex(rect.right - hw, rect.bottom - width + (hw%1))
            glVertex(rect.right, rect.bottom - hw)
            glVertex(rect.x + width, rect.bottom - hw)
            glVertex(rect.x + hw, rect.bottom)
            glVertex(rect.x + hw, rect.y + width)
            glEnd()
        glEndList()
        surf.display_list.append((dl, col))

    def polygon(self, surf, col, pointlist, width=0):
        """
        With width == 0, can only draw convex polygons. Be careful of
        the order of vertices if it draws differently than pygame.

        """
        col = [c/255. for c in col]
        dl = glGenLists(1)
        glNewList(dl, GL_COMPILE)
        if not width:
            glBegin(GL_POLYGON)
            for v in pointlist:
                glVertex(v)
            glEnd()
        glEndList()
        surf.display_list.append((dl, col))

    def circle(self, surf, col, pos, radius, width=0):
        col = [c/255. for c in col]
        dl = glGenLists(1)
        glNewList(dl, GL_COMPILE)
        if not width:
            glEnable(GL_POINT_SMOOTH)
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)
            glPointSize(radius*2)
            glBegin(GL_POINTS)
            glVertex(pos)
            glEnd()
            glDisable(GL_POINT_SMOOTH)
            glPointSize()
        glEndList()
        surf.display_list.append((dl, col))

    def line(self, surf, col, start_pos, end_pos, width=1):
        col = [c/255. for c in col]
        dl = glGenLists(1)
        glNewList(dl, GL_COMPILE)
        glLineWidth(width)
        glBegin(GL_LINES)
        glVertex(start_pos)
        glVertex(end_pos)
        glEnd()
        glEndList()
        surf.display_list.append((dl, col))

# Export Draw functions
draw = Draw()