#!/usr/bin/env python
#
# GLSLang Tools
#
# Copyright 2009 Alexandre Quessy
# <alexandre@quessy.net> 
# http://www.toonloop.com
#
# GLSLang Tools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GLSLang Tools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the gnu general public license
# along with GLSLang Tools.  If not, see <http://www.gnu.org/licenses/>.
#
"""
Tool to try GLSL shaders with SDL, OpenGL texture and Python.

Example :  PYTHONPATH=$PWD ./scripts/shadertester -d 1 -f simplechroma
"""

__version__ = "0.1"

import sys
import os
import shlex
import pprint
from twisted.internet import reactor
from twisted.internet import protocol
import pygame
import pygame.image
import pygame.camera
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.error import GLError
from OpenGL.GLU import *
#try:
from rats import render
from rats.glsl import ShaderProgram
from rats.glsl import ShaderError
from toon import optgroup
#except ImportError, e:
#    print(e.message)
#    print("path: %s" % (sys.path))
#    raise
class GlslRunOptions(optgroup.OptionsGroup):
    def __init__(self):
        self.bgcolor = [1.0, 0.0, 1.0]

class TelnetController(protocol.Protocol):
    """
    Telnet server to change GLSL uniform variables.
    """
    def connectionMade(self):
        print("Telnet connection made.")
        self.game = self.factory.game
        self.transport.write("Type h for help\n")
        #self.transport.write("Welcome to the GLSLang telnet server.\n")
        #self.transport.write("Type some text and press enter.\n")
        #self.transport.write("Type \"quit\" to quit.\n")

    def dataReceived(self, data):
        #TODO: do some validation here to secure things up.
        for line in data.splitlines():
            if line.strip() != "":
                self._on_line_received(line.strip())

    def _on_line_received(self, line):
        if line == "quit":
            self.transport.loseConnection()
        elif line == "l":
            txt = "Current effect name : %s\n" % (self.game.effect.name)
            if hasattr(self.game.effect, "options"):
                txt += "Options:\n%s\n" % (pprint.pformat(self.game.effect.options.__dict__))
            self.transport.write(txt)
            #print(txt)
        elif line.find("h") == 0:
            txt = "Help:\n"
            txt += "l : list options/n"
            txt += "h : print help"
            txt += "x : set option value"
        else:
            print("Got %s" % (line))
            tokens = shlex.split(line)
            if tokens[0] in ["x", "set-opt"]:
                if len(tokens) < 3:
                    self.transport.write("x takes at least 2 args: opt, value\n")
                    return
                #group_name = tokens[1]
                opt = tokens[1]
                value = tokens[2]
                #if group_name == self.game.effect.name:
                    #print("Group name matches effect name : %s" % (group_name))
                if hasattr(self.game.effect, "options"):
                    try:
                        self.game.effect.options.set_value(opt, value)
                    except optgroup.OptionsError, e:
                        print(e.message)
                        self.transport.write("Error: %s\n" % (e.message))
                    else:
                        self.transport.write("ok\n")
            if tokens[0] == "b": # set bgcolor
                if len(tokens) < 2:
                    self.transport.write("b takes at least 1 args: bgcolor value\n")
                    return
                else:
                    opt = "bgcolor"
                    value = tokens[1]
                    try:
                        self.game.options.set_value(opt, value)
                    except optgroup.OptionsError, e:
                        print(e.message)
                        self.transport.write("Error: %s\n" % (e.message))
                    else:
                        self.transport.write("ok\n")
                        
    def conectionLost(self):
        print("Telnet connection lost.")
        self.process_proto.tranport.loseConnection()

class GlSlangGame(render.Game):
    size = (640, 480)
    def __init__(self, effect, camera_number=0):
        render.Game.__init__(self)
        self.options = GlslRunOptions()
        self.effect = effect
        self.textures = [0] # list of texture ID 

        # create a display surface. standard pygame stuff
        self.screen = pygame.display.set_mode(self.size, OPENGL | DOUBLEBUF | HWSURFACE)
        pygame.display.set_caption("GLSLang Runner")
        self.resize(self.size)
        self.gl_init()
        # gets a list of available cameras.
        cameras_list = pygame.camera.list_cameras()
        if not cameras_list:
            raise ValueError("Sorry, no cameras detected.")
        else:
            print("Found cameras: %s" % (cameras_list))
        if os.uname()[0] == 'Darwin':
            self.isMac = True
            # creates the camera of the specified size and in RGB colorspace
            self.camera = pygame.camera.Camera(camera_number, self.size, 'RGBA')
        else:
            self.isMac = False
            # creates the camera of the specified size and in RGB colorspace
            self.camera = pygame.camera.Camera('/dev/video%d' % (camera_number), self.size, "RGBA")
        # starts the camera
        self.camera.start()
        #self.clock = pygame.time.Clock()

        # create a surface to capture to.  for performance purposes, you want the
        # bit depth to be the same as that of the display surface.
        self.snapshot = pygame.surface.Surface(self.size, 0, self.screen)
        self._start_telnet_server()

    def _start_telnet_server(self):
        factory = protocol.Factory()
        factory.protocol = TelnetController
        factory.game = self
        port = 15555
        reactor.listenTCP(port, factory)
        print("Started telnet server on port %s" % (port))

    def gl_init(self):
        """
        Init the window
        """
        glEnable(GL_TEXTURE_RECTANGLE_ARB) 
        print("Enabled GL_TEXTURE_RECTANGLE_ARB")
        glShadeModel(GL_SMOOTH)
        self.textures[0] = glGenTextures(1)
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glColor3f(1., 1., 1.)
        print("will setup effect")
        try:
            self.effect.setup()
        except GLError, e:
            print(str(e)) # should fully format it
            print(e.description)
    
    def resize(self, (width, height)):
        """
        Called when we resize the window.
        (fullscreen on/off)
        """
        if height == 0:
            height = 1
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(-4.0, 4.0, -3.0, 3.0, -1.0, 1.0)
        glMatrixMode(GL_MODELVIEW)
    
    def _draw_images(self):
        """
        Called on every frame rendering
        """
        w = 640.0
        h = 480.0
        aspectRatio = w / float(h)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        # background color
        glDisable(GL_TEXTURE_RECTANGLE_ARB) 
        glColor3f(*self.options.bgcolor)
        glPushMatrix()
        glBegin(GL_QUADS)
        glVertex2f(-4.0, -3.0) # Bottom Left
        glVertex2f(4.0, -3.0) # Bottom Right
        glVertex2f(4.0, 3.0) # Top Right
        glVertex2f(-4.0, 3.0) # Top Left
        glEnd()
        glPopMatrix()
        # Image with effect in big
        glEnable(GL_TEXTURE_RECTANGLE_ARB) 
        self.effect.pre_draw()
        glColor3f(1., 1., 1.)
        glPushMatrix()
        glBegin(GL_QUADS)
        glTexCoord2f(0.0, 0.0)
        glVertex2f(-4.0, -3.0) # Bottom Left
        glTexCoord2f(w, 0.0)
        glVertex2f(4.0, -3.0) # Bottom Right
        glTexCoord2f(w, h)
        glVertex2f(4.0, 3.0) # Top Right
        glTexCoord2f(0.0, h)
        glVertex2f(-4.0, 3.0) # Top Left
        glEnd()
        glPopMatrix()
        self.effect.post_draw()
        # The picture in picture : 
        glPushMatrix()
        glTranslate(2, 1.5, 0)
        glBegin(GL_QUADS)
        glTexCoord2f(0.0, 0.0)
        glVertex2f(-aspectRatio, -1.) # Bottom Left
        glTexCoord2f(w, 0.0)
        glVertex2f(aspectRatio, -1.) # Bottom Right
        glTexCoord2f(w, h)
        glVertex2f(aspectRatio, 1.) # Top Right
        glTexCoord2f(0.0, h)
        glVertex2f(-aspectRatio, 1.) # Top Left
        glEnd()
        glPopMatrix()
    
    def draw(self):
        """
        Grabs a frame from the camera (to a texture) and renders the screen.

        if you don't want to tie the framerate to the camera, you can check and
        see if the camera has an image ready.  note that while this works
        on most cameras, some will never return true.
        """
        if 0 and self.camera.query_image():
            self.snapshot = self.camera.get_image(self.snapshot)
        self.snapshot = self.camera.get_image(self.snapshot)
        textureData = pygame.image.tostring(self.snapshot, "RGBX", 1)
        if bool(glActiveTexture):   # make sure glActiveTexture is defined
            glActiveTexture(GL_TEXTURE0) # IMPORTANT ! sets the texture unit to 0. 
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self.textures[0])
        glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, self.snapshot.get_width(), self.snapshot.get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData)
        glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        self._draw_images()
        pygame.display.flip()
    
    def process_events(self, events):
        for e in events:
            if e.type == QUIT:
                self.running = False
            if e.type == KEYDOWN:
                if e.key == K_ESCAPE:
                    self.running = False
                elif e.key == K_RETURN:
                    self.effect.enabled != self.effect.enabled # FIXME: does this crash?
            elif e.type == KEYDOWN:
                if e.key == K_f:
                    pygame.display.toggle_fullscreen()

    def set_option(self, group_name, option_name, value):
        print("set_option %s %s %s" % (group_name, option_name, value))
        #if hasattr(self.effect.options, 

if __name__ == '__main__':
    import optparse

    parser = optparse.OptionParser(usage="%prog", version='shadertester ' + str(__version__))
    parser.add_option("-f", "--effect", type="string", default="simplechroma", 
        help="Name of the shader effect to test.")
    parser.add_option("-d", "--device", dest="device", type="int", \
        help="Specifies V4L2 device to grab image from. Expects an integer such as 0, 1 or 2.", default=0)
    parser.add_option("-l", "--list-effects", \
        action="store_true", \
        help="Prints the list of effects and exits.") 
    (options, args) = parser.parse_args()
    camera_number = options.device
    if options.list_effects:
        print("Effects :")
        for f in ["simplechroma", "brcosa"]:
            print(" - %s" % (f))
        sys.exit(0)
    try:
        if options.effect == "simplechroma":
            from toon.effects import simplechroma
            effect = simplechroma.create_effect()
        elif options.effect == "brcosa":
            from toon.effects import brcosa
            effect = brcosa.create_effect()
        elif options.effect == "leavecolor":
            from toon.effects import leavecolor
            effect = leavecolor.create_effect()
        elif options.effect == "lumakey":
            from toon.effects import lumakey
            effect = lumakey.create_effect()
        else:
            print("Effect %s is not valid." % (options.effect))
            sys.exit(1)
    except ImportError, e:
        print(e.message)
        print("path: %s" % (sys.path))
        raise
    effect.enabled = True # choose it
    pygame.init()
    pygame.camera.init()
    game = GlSlangGame(effect, camera_number)
    pygame_timer = render.Renderer(game, False) # not verbose !  options.verbose
    pygame_timer.desired_fps = 30
    try:
        reactor.run()
    except KeyboardInterrupt:
        pass # will exit on ctrl-c
    print("Exiting glslang-run")
    pygame.quit()

