#!/usr/bin/env python
# ----------------------------------------------------------------------------
#
# cocos_player.py
# By Ahik Man 
# 
# based on pyglet media_player.py example by Alex Holkner
#  you can find it and more info at: http://pyglet.org/
# 
# For cocos2d information go to: http://cocos2d.org/
# Media files support by avbin lib: http://code.google.com/p/avbin/
#
# bugs and limitaion:
#    Window resize is not supported (the gui is not resizing well)
#    Media file playback depends on avibin (mp4 seek is limited)
#    The sprite._set_texture at each draw, might not be the optimize way to get the video image
#
# THIS IS AN EXPERIMENTAL SOFTWARE. USE IT BY YOUR OWN RISK.
#

'''Experimental audio and video player with cucos2d GUI controls.
'''

import sys
import os

from pyglet import image, media, font
from pyglet.gl import *
from pyglet.event import EventDispatcher
from pyglet.window import key

from cocos.euclid import Point2
from cocos.director import director
from cocos.menu import Menu, MenuItem, MultipleMenuItem, ToggleMenuItem
from cocos.scene import Scene
from cocos.layer import Layer
from cocos.sprite import Sprite

from pyPlayer import PlayerWindow

def print_help():
    print "\nKeyboard usage:\n"
    print " SPACE  :  Pause/Play"
    print " + or = :  Scale up"
    print " - or _ :  Scale down"
    print "   m    : Toggle menu"    
    print "   q    :  Exit"
    print ""

def draw_rect(x, y, width, height):
    glBegin(GL_LINE_LOOP)
    glVertex2f(x, y)
    glVertex2f(x + width, y)
    glVertex2f(x + width, y + height)
    glVertex2f(x, y + height)
    glEnd()

class MainMenu(Menu):
    def __init__( self, container ):
        super( MainMenu, self ).__init__("Test Menu Items")
        self.container = container

        # then add the items
        item1= ToggleMenuItem('Play/Pause: ', self.on_toggle_callback, True )
                        
        scales = ['4','2','1', '0.5', '0.25']
        item2= MultipleMenuItem('Scale to: ',
                        self.on_multiple_callback,
                        scales,
                        2 )

        item3 = MenuItem('Back', self.on_back)

        item4 = MenuItem('Quit', self.on_quit )

        self.create_menu( [item1,item2,item3, item4] )

    def show_help(self):
        print 'hi'
        
    def on_quit( self ):
        self.container.quit()

    def on_multiple_callback(self, idx ):
        if 0 == idx:
            val = 4.0
        elif 1 == idx:
            val = 2.0
        elif 2 == idx:
            val = 1.0
        elif 3 == idx:
            val = 0.5
        elif 4 == idx:
            val = 0.25
        self.container.scaleto(val)

    def on_toggle_callback(self, b ):
        self.container.on_play_pause()

    def on_back(self ):
        self.container.menu_toggle()

class Control(Layer, EventDispatcher):
    is_event_handler = True
    width = height = 10

    def __init__(self):
        super(Control, self).__init__()

    def return_xy(self):
        return self.parent.return_xy() + Point2( self.x, self.y)

    def hit_test(self, x, y):
        rel_x, rel_y = self.return_xy()
        return (rel_x < x < rel_x + self.width and  
                rel_y < y < rel_y + self.height)

class Button(Control):
    charged = False

    def draw(self):
        if self.charged:
            glColor3f(1, 0, 0)
        draw_rect(self.x, self.y, self.width, self.height)
        glColor3f(1, 1, 1)
        self.draw_label()

    def on_mouse_press(self, x, y, button, modifiers):
        self.charged = self.hit_test(x,y)
    
    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        self.charged = self.hit_test(x, y)

    def on_mouse_release(self, x, y, button, modifiers):
        if self.hit_test(x, y):
            self.dispatch_event('on_press')
        self.charged = False

Button.register_event_type('on_press')
    
class TextButton(Button):
    def __init__(self, *args, **kwargs):
        super(TextButton, self).__init__(*args, **kwargs)
        self._text = font.Text(font.load('Arial', 12), 'TEST',
#  anchor_x='center', anchor_y='center') # New pyglt style. Don't know why it's not work..
                               valign='center', halign='center')

    def draw_label(self):
        self._text.x = self.x + self.width / 2
        self._text.y = self.y + self.height / 2
        self._text.draw()

    def set_text(self, text):
        self._text.text = text

    text = property(lambda self: self._text.text, set_text)

class Slider(Control):
    THUMB_WIDTH = 6
    THUMB_HEIGHT = 10
    GROOVE_HEIGHT = 2
    min = 0
    max = 400
    
    x = y = 0
    width = 400
    height = 20

    def draw(self):
        center_y = self.y + self.height / 2
        draw_rect(self.x, center_y - self.GROOVE_HEIGHT / 2, 
                  self.width, self.GROOVE_HEIGHT)
        loc = self.x + self.value * self.width / (self.max - self.min)
        draw_rect(loc - self.THUMB_WIDTH / 2, center_y - self.THUMB_HEIGHT / 2, 
                  self.THUMB_WIDTH, self.THUMB_HEIGHT)

    def coordinate_to_value(self, x):
        rel_x, rel_y = self.return_xy()
        return float(x - rel_x) / self.width * (self.max - self.min) + self.min

    def on_mouse_press(self, x, y, button, modifiers):
        if self.hit_test(x,y):
            value = self.coordinate_to_value(x)
            self.dispatch_event('on_begin_scroll')
            self.dispatch_event('on_change', value)

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        if self.hit_test(x,y):
            value = min(max(self.coordinate_to_value(x), self.min), self.max)
            self.dispatch_event('on_change', value)
    
    def on_mouse_release(self, x, y, button, modifiers):
        if self.hit_test(x,y):
            self.dispatch_event('on_end_scroll')

Slider.register_event_type('on_begin_scroll')
Slider.register_event_type('on_end_scroll')
Slider.register_event_type('on_change')
 
class Gui( Control ):
    GUI_WIDTH = 400
    GUI_HEIGHT = 40
    GUI_PADDING = 4
    GUI_BUTTON_BOTTOM = 8
    GUI_BUTTON_HEIGHT = 16
    GUI_HIT_DRAG_TEST = 6

    def __init__(self, container, player):
        super(Gui, self).__init__()
        self.container = container
        self.player = player
        self.x = 40
        self.y = 50

        self.slider = Slider() # (self)
        self.slider.x = 0
        self.slider.y = self.GUI_PADDING * 2 + self.GUI_BUTTON_HEIGHT
        self.slider.on_begin_scroll = lambda: container.begin_scroll()
        self.slider.on_end_scroll = lambda: container.end_scroll()
        self.slider.on_change = lambda value: container.seek(value)
        self.add(self.slider)
        self.add_buttons()
    
    def add_text_button(self, text, x, width):
        b = TextButton()
        b.text = text
        b.x = x
        b.y = self.GUI_BUTTON_BOTTOM
        b.width = width
        b.height = self.GUI_BUTTON_HEIGHT
        return b
                
    def add_buttons(self):    
        ix = 2 * self.GUI_PADDING 
        
        self.play_pause_button = self.add_text_button("Pause", ix, 50)
        self.play_pause_button.on_press = self.on_play_pause_button_pressed
        ix += self.play_pause_button.width + self.GUI_PADDING
        self.add( self.play_pause_button )
        
        b2 = self.add_text_button("Stop", ix, 50)
        b2.on_press = lambda: self.container.pause()
        ix += b2.width + self.GUI_PADDING
        self.add( b2 )

        b3 = self.add_text_button("-", ix, 30)
        b3.on_press = lambda: self.container.scale(0.5)
        ix += b3.width + self.GUI_PADDING
        self.add( b3 )

        b4 = self.add_text_button("+", ix, 30)
        b4.on_press = lambda: self.container.scale(2.0)
        ix += b4.width + self.GUI_PADDING
        self.add( b4 )

        b5 = self.add_text_button("Menu", ix, 50)
        b5.on_press = lambda: self.container.menu_toggle()
        ix += b5.width + self.GUI_PADDING
        self.add( b5 )

        b6 = self.add_text_button("Quit", ix, 50)
        b6.on_press = lambda: self.container.quit()
        ix += b6.width + self.GUI_PADDING
        self.add( b6 )

    def draw( self ):
        self.slider.value = self.player.time
        draw_rect(self.x, self.y, self.GUI_WIDTH, self.GUI_HEIGHT)
        draw_rect(self.x, self.y, self.GUI_HIT_DRAG_TEST, self.GUI_HIT_DRAG_TEST)
        
    def return_xy(self): # Called by the gui childs
        return Point2( self.x, self.y)
    
    def gui_update(self, duration):
        self.slider.min = 0.
        self.slider.max = duration
        self.gui_update_state()

    def gui_update_state(self):
        if self.player.playing:
            self.play_pause_button.text = 'Pause'
        else:
            self.play_pause_button.text = 'Play'
            
    def on_play_pause_button_pressed(self):
        self.container.on_play_pause()
        self.gui_update_state()
    
    def hit_to_drag(self, x,y):
        (dx, dy) = (x - self.x, y - self.y)        
        return (dx < self.GUI_HIT_DRAG_TEST and dx > -self.GUI_HIT_DRAG_TEST \
            and dy < self.GUI_HIT_DRAG_TEST and dy > -self.GUI_HIT_DRAG_TEST )
    
    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        if self.hit_to_drag(x,y):
            self.position = (x,y)

Gui.register_event_type('on_mouse_drag')
    
class Video_layer(Layer):
    is_event_handler=True
    def __init__(self, container, player):
        super(Video_layer, self).__init__()
        self.container = container
        self.player = player
        self.player.push_handlers(self)
        self.player.eos_action = self.player.EOS_PAUSE
        self.width, self.height = self.get_video_size()
        
        # Create empty image for the Sprite
        from pyglet.image import AbstractImage, Texture
        absImage = AbstractImage(int(self.width), int(self.height))
        # Video size might be float. Texture need int sizes
        texture = Texture(int(self.width), int(self.height), absImage, 4444)
        self.sprite = Sprite(texture)
        self.add(self.sprite)       
        x,y = director.get_window_size()
        self.sprite.position =x/2, y/2
                
    def on_key_press(self, symbol, mod):
        if symbol == key.M:
            self.container.menu_toggle()
        if symbol == key.PLUS or symbol == key.EQUAL:
            self.container.scale(2.0)
        if symbol == key.MINUS or symbol == key.UNDERSCORE:
            self.container.scale(0.5)
        if symbol == key.SPACE:
            self.container.on_play_pause()
        if symbol == key.Q:
            self.container.quit()

    def get_video_size(self):
        if not self.player.source or not self.player.source.video_format:
            print "Opuss. No source or no video..."
            return 160, 120
        video_format = self.player.source.video_format
        width = video_format.width
        height = video_format.height
        if video_format.sample_aspect > 1:
            width *= video_format.sample_aspect
        elif video_format.sample_aspect < 1:
            height /= video_format.sample_aspect
        return width, height
               
    def draw(self):
        if self.player.source and self.player.source.video_format:
            self.sprite._set_texture(self.player.texture)
            
class Player_container():
    
    was_playing = False
    cur_scale = 1.0
    menu_is_on = False
        
    def __init__(self, filename):
        # Init the media player
        player = media.Player()
        source = media.load(filename)
        player.queue(source)
        self.player = player
        self.source = source
        if (source.video_format):
            print "size is ", source.video_format.width, source.video_format.height
        
            # Arange the cocos scene
            director.init( resizable=False)
            self.scene = Scene(  )
            self.video_layer = Video_layer(self, player)
            self.scene.add(self.video_layer, z=-1)
            self.menu = MainMenu(self)
            self.gui = Gui(self, player)
            self.scene.add( self.gui)
            self.player.play() # Let the scene build first (Trying avoid annoying sounds)
            self.gui.gui_update(self.source.duration)
            director.run( self.scene)
        else:
            print "\n*** No video... ***\n"
            self.quit()          
                
    def on_play_pause(self):
        if self.player.playing:
            self.player.pause()
        else:
            self.player.play()
        self.gui.gui_update_state()
    
    def begin_scroll(self):
        self.was_playing = self.player.playing
        self.player.pause()

    def end_scroll(self):
        if self.was_playing:
            self.player.play()
        
    def pause(self):
        self.player.pause()
        self.gui.gui_update(self.source.duration)

    def play(self):
        self.player.play()
        self.gui.gui_update(self.source.duration)

    def seek(self, time):
        self.player.seek(time)
        
    def scale(self, val):
        self.cur_scale *= val
        self.video_layer.scale = self.cur_scale

    def scaleto(self, val):
        self.cur_scale = val
        self.video_layer.scale = self.cur_scale

    def menu_toggle(self):
        if self.menu_is_on:
            director.scene.remove(self.menu)
            self.menu_is_on = False
        else:
            director.scene.add(self.menu)
            self.menu_is_on = True

    def quit(self):
        self.player.pause() # Avoid annoying noise
        pyglet.app.exit()
    
if __name__ == "__main__":

    if len(sys.argv) < 2:
        print 'Usage: cocosPlayer.py <filename>'
        sys.exit(1)
    print_help()
    pc = Player_container(sys.argv[1])

