#
#   PyGUI - OpenGL View - Cocoa
#

__all__ = ['GLConfig', 'GLView', 'GLPixmap']

import AppKit
from Foundation import NSSize
from AppKit import NSOpenGLPixelFormat, NSOpenGLView, \
    NSBitmapImageRep, NSCachedImageRep, NSImage
from Foundation import NSMakeRect
from GViews import default_size
from GGLViews import GLView as GGLView
from GGLPixmaps import GLPixmap as GGLPixmap
from GGLConfig import GLConfig as GGLConfig
from GLContexts import GLContext
from GLTextures import Texture
from GLDisplayLists import DisplayList
from ViewBases import PyGUI_NS_ViewBase
from Utils import NSMultiClass

try:
    from OpenGL.GL import glViewport, glFlush, glFinish, glReadPixels, \
        GL_RGBA, GL_UNSIGNED_BYTE, glReadPixelsub
except ImportError, e:
    raise GLError("OpenGL support is not available (%s)" % e)

#------------------------------------------------------------------------------

class GLConfig(GGLConfig):
    #  _ns_pixel_format   NSOpenGLPixelFormat
    
    def _ns_get_pixel_format(self, offscreen = False):
        attrs = [AppKit.NSOpenGLPFAColorSize, self._color_size]
        if self._double_buffer:
            attrs += [AppKit.NSOpenGLPFADoubleBuffer]
        if self._alpha:
            attrs += [AppKit.NSOpenGLPFAAlphaSize, self._alpha_size]
        if self._stereo:
            attrs += [AppKit.NSOpenGLPFAStereo]
        if self._aux_buffers:
            attrs += [AppKit.NSOpenGLPFAAuxBuffers, self._aux_buffers]
        if self._depth_buffer:
            attrs += [AppKit.NSOpenGLPFADepthSize, self._depth_size]
        if self._stencil_buffer:
            attrs += [AppKit.NSOpenGLPFAStencilSize, self._stencil_size]
        if self._accum_buffer:
            attrs += [AppKit.NSOpenGLPFAAccumSize, self._accum_size]
        if self._multisample:
            attrs += [AppKit.NSOpenGLPFASampleBuffers, 1]
            attrs += [AppKit.NSOpenGLPFASamples, self._samples_per_pixel]
        if offscreen:
            attrs += [AppKit.NSOpenGLPFAOffScreen]
        attrs.append(0)
        ns_pf = NSOpenGLPixelFormat.alloc().initWithAttributes_(attrs)
        if not ns_pf and self._double_buffer:
            attrs.remove(AppKit.NSOpenGLPFADoubleBuffer)
            ns_pf = NSOpenGLPixelFormat.alloc().initWithAttributes_(attrs)
        if not ns_pf:
            raise GLConfigError
        return ns_pf
    
    def _ns_set_pixel_format(self, ns_pf):
        def ns_attr(attr):
            return ns_pf.getValues_forAttribute_forVirtualScreen_(attr, 0)[0]
        self._ns_pixel_format = ns_pf
        self._double_buffer = ns_attr(AppKit.NSOpenGLPFADoubleBuffer)
        self._color_size = ns_attr(AppKit.NSOpenGLPFAColorSize)
        self._alpha_size = ns_attr(AppKit.NSOpenGLPFAAlphaSize)
        self._alpha = self._alpha_size > 0
        self._stereo = ns_attr(AppKit.NSOpenGLPFAStereo)
        self._aux_buffers = ns_attr(AppKit.NSOpenGLPFAAuxBuffers)
        self._depth_size = ns_attr(AppKit.NSOpenGLPFADepthSize)
        self._depth_buffer = self._depth_size > 0
        self._stencil_size = ns_attr(AppKit.NSOpenGLPFAStencilSize)
        self._stencil_buffer = self._stencil_size > 0
        self._accum_size = ns_attr(AppKit.NSOpenGLPFAAccumSize)
        self._accum_buffer = self._accum_size > 0
        self._multisample = ns_attr(AppKit.NSOpenGLPFASampleBuffers) > 0
        self._samples_per_pixel = ns_attr(AppKit.NSOpenGLPFASamples)

    def supported(self):
        try:
            ns_pf = self._ns_get_pixel_format()
            pf = GLConfig.__new__()
            pf._ns_set_pixel_format(ns_pf)
            return pf
        except GLConfigError:
            return None

#------------------------------------------------------------------------------

class GLView(GGLView):
    #  _ns_view      NSOpenGLView
    #  _ns_context   NSOpenGLContext
    #  _ns_flush     function for flushing/swapping buffers

    def __init__(self, config = None, share_group = None, **kwds):
        pf = GLConfig._from_args(config, kwds)
        ns_pf = pf._ns_get_pixel_format()
        width, height = default_size
        ns_rect = NSMakeRect(0, 0, width, height)
        ns_view = _PyGUI_NSOpenGLView.alloc().initWithFrame_pixelFormat_(
            ns_rect, ns_pf)
        ns_view.pygui_component = self
        GGLView.__init__(self, _ns_view = ns_view, **kwds)
        GLContext.__init__(self, share_group = share_group, _ns_pixel_format = ns_pf)
        ns_context = self._ns_context
        ns_view.setOpenGLContext_(ns_context)
        #ns_context.setView_(ns_view) # Docs say this is needed, but
        # prints warning and seems to work without.
        if pf.double_buffer:
            self._ns_flush = ns_context.flushBuffer
        else:
            self._ns_flush = glFlush
        self.with_context(self._init_context)
    
    def destroy(self):
        #print "GLView.destroy:", self ###
        ns_view = self._ns_view
        GGLView.destroy(self)
        #print "GLView.destroy: breaking back link from", ns_view ###
        ns_view.pygui_component = None
    
    def _init_context(self):
        self.init_context()
        self._update_viewport()
    
    def _update_viewport(self):
        width, height = self.size
        glViewport(0, 0, int(width), int(height))
        self.viewport_changed()
        
    def invalidate(self):
        self._ns_view.setNeedsDisplay_(True)

    def track_mouse(self):
        return self._ns_track_mouse(self._ns_view)
    
#------------------------------------------------------------------------------

class GLPixmap(GGLPixmap):

    def __init__(self, width, height, config = None, share_group = None, **kwds):
        pf = GLConfig._from_args(config, kwds)
        ns_pf = pf._ns_get_pixel_format()
        ns_size = NSSize(width, height)
        ns_cache = NSCachedImageRep.alloc().initWithSize_depth_separate_alpha_(
            ns_size, 0, True, True)
        ns_image = NSImage.alloc().initWithSize_(ns_size)			
        GLContext.__init__(self, share_group = share_group, _ns_pixel_format = ns_pf)
        self._ns_context.setView_(ns_cache.window().contentView())
        self._init_with_ns_image(ns_image, flipped = False)
        self._ns_cache = ns_cache
        self.with_context(self._init_context)
    
    def _init_context(self):
        width, height = self._ns_image.size()
        glViewport(0, 0, int(width), int(height))
        self.init_context()
    
    def _ns_flush(self):
        glFlush()
        width, height = self.size
        pixels = glReadPixels(0, 0, int(width), int(height), GL_RGBA, GL_UNSIGNED_BYTE)
        bytes_per_row = int(width) * 4
        ns_new_bitmap = NSBitmapImageRep.alloc().\
            initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(
            (pixels, "", "", "", ""), int(width), int(height), 8, 4, True, False, AppKit.NSDeviceRGBColorSpace, bytes_per_row, 0)
        ns_image = NSImage.alloc().initWithSize_(NSSize(width, height))
        ns_image.addRepresentation_(ns_new_bitmap)
        ns_image.lockFocus()
        ns_image.unlockFocus()
        self._ns_image = ns_image

#------------------------------------------------------------------------------

class _PyGUI_NSOpenGLView(NSOpenGLView, PyGUI_NS_ViewBase):
    __metaclass__ = NSMultiClass
    #
    #  pygui_component   GLView

    def isFlipped(self):
        return True

    def reshape(self):
        self.pygui_component._update_viewport()

    def drawRect_(self, rect):
        pygui_component = self.pygui_component
        pygui_component.with_context(pygui_component.render, flush = True)
