# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" in the root directory of this distribution package
# for details on that license.


__maintainer__ = 'Lionel Martin <lionel@fluendo.com>'
__maintainer2__ = 'Florian Boucault <florian@fluendo.com>'

from elisa.core import common
from elisa.base_components.context import Context
from elisa.core.utils import signal
from elisa.core.media_uri import MediaUri, unquote

import pgm
from pgm.graph.text import Text
import os
import sys
try:
    import resource
except ImportError:
    pass

from twisted.internet import reactor

# FIXME use a normal import (that doesn't seem to work right now)
plugin_registry = common.application.plugin_registry
MediaLocationMessage = plugin_registry.get_component_class('base:media_location_message')

from elisa.plugins.pigment.dropped_plugin_message import DroppedPluginMessage

class PigmentContext(Context):
    """
    Context based on Pigment. Creates a Pigment viewport and associates a
    Pigment canvas to it. It takes care of the viewport resizes and
    subsequent regenerations.

    @ivar master_drawables: dictionary of master drawables used for improved
                            theme management based on Pigment cloning
                            keys: theme icons
                            values: master drawables
    @type master_drawables: dictionary
    """

    default_config = {'screen_ratio' : 'auto',
                      'window_width': '0',
                      'touchscreen': '0',
                      'use_gtk': '0',
                      'show_mem': '0',
                      'show_cpu': '0',
                      'show_fps': '0',
                      'start_fullscreen': '1'}



    config_doc = {'screen_ratio' : 'The ratio of the screen. This is a string'
                                   ' containing a relation value, separated'
                                   ' by a colon (:). Common values are 16:9,'
                                   ' 4:3, 16:10, but it could be any other,'
                                   ' too. You can have special values : mm,'
                                   ' based on screen size in milimeters,'
                                   ' sr, based on window manager resolution,'
                                   ' or auto. auto will use mm special value.',
                  'window_width' : 'Here you can set the width in pixels the'
                                   ' window should have at startup. The height'
                                   ' is computed using screen_ratio.'
                                   ' If this value is 0, we decide'
                                   ' automatically.',
                  'touchscreen' : 'If set to 1, the mouse behaviour will be'
                                  ' adapted for touchscreen equipped hardware.',
                  'show_mem' : 'Show/Hide the memory usage.',
                  'show_cpu' : 'Show/Hide the CPU usage.',
                  'show_fps' : 'Show/Hide the rendering framerate.',
                  'start_fullscreen': 'If set to 1, Elisa will start fullscreen'
                                      ' otherwise windowed'
    }

    master_drawables = {}
    
    def use_gtk__get(self):
        return self.config.get('use_gtk',
                               self.default_config.get('use_gtk')) == '1'

    def initialize(self):
        Context.initialize(self)

        # nVidia driver specific option that inhibits its yielding behaviour
        # this allows Pigment rendering to be smooth even when the system as a
        # whole has a high load.
        # It is unnecessary for ATI proprietary drivers and all open source
        # drivers.
        # more details at:
        # http://us.download.nvidia.com/XFree86/Linux-x86/169.07/README/chapter-11.html
        os.environ["__GL_YIELD"] = "NOTHING"

        # Deactivate forcing indirect rendering since it causes more troubles
        # than it's worth with DRI drivers: Elisa always works better with
        # LIBGL_ALWAYS_INDIRECT unset.
        # https://bugs.launchpad.net/ubuntu/+source/desktop-effects/+bug/137388
        os.unsetenv("LIBGL_ALWAYS_INDIRECT")

        # signal handlers id
        self._signal_handler_ids = []

        config_screen_ratio = self.config.get('screen_ratio', 'auto')

        self.touchscreen =  self.config.get('touchscreen', '1') == '1'

        # Disabled compiz toggeling
        """
        try:    
            # FIXME: that is only working on Linux with ps installed
            self._compiz_check()
        except:
            pass
        """

        # FIXME: this is a workaround a Python import bug; at that point for
        # some reason pgm is not defined. This should be fixed with Python
        # 3000
        import pgm

        # OpenGL viewport creation
        factory = pgm.ViewportFactory('opengl')
        viewport = factory.create()

        self.viewport_handle = viewport
        viewport.title = 'Elisa Media Center'
        self._window_managet_screen_size_mm = viewport.screen_size_mm
 
        # Canvas creation
        self.canvas = pgm.Canvas()
        
        try:
            width = int(self.config.get('window_width', '0'))
            if width != 0:
               viewport.width = width
        except Exception, e:
            self.warning("The configuration value of window_width seems to be"
                         " no number value in pixels: %s" % e)

        # delay the resize of the canvas to avoid crippling the CPU
        self._resize_delay = 0.200
        self._resize_delayed = None
        self.canvas_resized = signal.Signal('canvas_resized', tuple)

        # Bind the canvas to the OpenGL viewport
        viewport.set_canvas(self.canvas)

        fullscreen = self.config.get('start_fullscreen', '0') == '1'

        if self.use_gtk:
            self.debug("using gtk")
            import gtk
            import pgm.gtk
            self.gtk_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
            id = self.gtk_window.connect('delete-event',
                                         self._gtk_window_delete_event_cb)
            self._signal_handler_ids.append(id)
            embed = pgm.gtk.PgmGtk()
            self.gtk_window.add(embed)
            embed.set_viewport(viewport)
            self.gtk_window.resize(*viewport.size)
            self.gtk_window.set_focus_child(embed)
            self.debug("set fullscreen: %r", fullscreen)
            if fullscreen:
                gtk_window.fullscreen()
            else:
                gtk_window.unfullscreen()
            self.gtk_window.show_all()
        else:
            # show the window
            self.gtk_window = None
            self.debug("set fullscreen: %r", fullscreen)
            viewport.fullscreen = fullscreen
            viewport.show()
            id = viewport.connect('configure-event', self._configure_callback)
            self._signal_handler_ids.append(id)

        # hide the cursor
        self.viewport_handle.cursor = pgm.VIEWPORT_NONE
    
        # set the aspect ratio for the first time AFTER setting the pgm
        # viewport to fullscreen to work-a-round the fullscreen in compiz
        # envorinment bug: https://code.fluendo.com/pigment/trac/ticket/234
        self.aspect_ratio_changed(None, config_screen_ratio)

        if not self.touchscreen:
            # delay the hiding of the mouse cursor
            self._cursor_delay = 1.000
            self._cursor_delayed = None
            # signal triggered when the canvas gets resized
            id = viewport.connect('motion-notify-event',
                                  self._motion_notify_callback)
            self._signal_handler_ids.append(id)
            id = viewport.connect('drag-motion-event',
                                  self._drag_motion_cb)
            self._signal_handler_ids.append(id)
            id = viewport.connect('drag-drop-event',
                                  self._drag_drop_cb)
            self._signal_handler_ids.append(id)

        show_fps = self.config.get('show_fps', '1') == '1'
        if show_fps:
            self._add_fps_display()

        show_mem = self.config.get('show_mem', '1') == '1'
        if show_mem:
            self._add_mem_display()

        show_cpu = self.config.get('show_cpu', '1') == '1'
        if show_cpu:
            self._add_cpu_display()

    def _gtk_window_delete_event_cb(self, *args):
        common.application.stop()

    def aspect_ratio_changed(self, old_aspect_ratio, new_aspect_ratio):
        if old_aspect_ratio is not None:
            self.debug("aspect ratio changed from %r to %r", old_aspect_ratio,
                       new_aspect_ratio)
        else:
            self.info("will set the aspect ratio with parameter %r", new_aspect_ratio)
            
        screen_ratio = self._get_screen_size_from_ratio(new_aspect_ratio)
        self.debug("screen ratio is %r", screen_ratio)
        
        pixel_aspect_ratio = self._get_pixel_aspect_ratio(screen_ratio)
        self.info("used pixel aspect ratio is %r", pixel_aspect_ratio)

        canvas_height = self.canvas.width / screen_ratio
        self.debug("new canvas height: %r", canvas_height)

        #I change the aspect ratio of the canvas a Little bit to put
        #Two thin lines (left and right) when the video is fullscreen.
	#And, with this magic hack, the FPS increase by 40% for HD video
	#on gusty for intel video card.
	#Fps increase also on intel/Hardy, but less than 40%.
        self.canvas.height = canvas_height * 1.005
        
        # set the window aspect ratio to the screen aspect ratio
        viewport_height = self.viewport_handle.width / (screen_ratio * pixel_aspect_ratio)
        self.debug("is fullscreen: %s", self.viewport_handle.fullscreen)
        self.debug("new viewport height: %r", viewport_height)
        self.viewport_handle.height = viewport_height

        #w2,l2 = self.viewport_handle.size
        #self.viewport_handle.screen_size_mm = (int(w), int(h))
        self.viewport_size = self.viewport_handle.size
        self.debug("viewport size: %r", self.viewport_size)

        config_screen_ratio = self.config.get('screen_ratio', 'auto')
        if config_screen_ratio != new_aspect_ratio:
            self.config['screen_ratio'] = new_aspect_ratio
            
        self.canvas_resized.emit(self.canvas.size)
            
    def clean(self):
        viewport = self.viewport_handle

        # disconnecting from all the signals it connected to
        for id in self._signal_handler_ids:
            viewport.disconnect(id)

        # unbinding the canvas from the OpenGL viewport
        viewport.set_canvas(None)

        # releasing its reference to the OpenGL viewport
        del self.viewport_handle
        
        return super(PigmentContext, self).clean()    

    def _compiz_check(self):
        # FIXME: this is a work-a-round against the flickering in compiz
        ps_aux = os.popen('ps aux')

        compiz = False

        for line in ps_aux:
            if 'compiz' in line:
                compiz = True

        if compiz:
            self.warning("Compiz detected, trying to hide all other windows.")
            try:
                import wnck
            except ImportError:
                self.warning("Could not import WNCK. Windows hiding disabled.")
            else:
                s = wnck.screen_get_default()
                s.force_update()
                lst = s.get_windows()
                cur_workspace = s.get_active_workspace()
                for window in lst:
                    if window.is_in_viewport(cur_workspace):
                        window.minimize()

        else:
            self.info('Compiz not found')

    def _get_screen_size_from_ratio(self, config_screen_ratio):
        # retrieve the screen aspect ratio
        if config_screen_ratio == 'sr':
            self.info("using window manager screen resolution as screen aspect ratio: %r",
                      self.viewport_handle.screen_resolution)
            w, h = self.viewport_handle.screen_resolution
            self.viewport_handle.screen_size_mm = (int(w), int(h) )
        elif config_screen_ratio.find(':') != -1:
            self.info("Using configured aspect screen ratio: %r", config_screen_ratio)
            w, h = config_screen_ratio.split(':')
            self.viewport_handle.screen_size_mm = (int(w), int(h))
        else:
            self.info("Using the milimeter based screen aspect ratio : %r",
                      self.viewport_handle.screen_size_mm)
            w, h = self._window_managet_screen_size_mm
            self.viewport_handle.screen_size_mm =  (int(w), int(h))
        width = float(w)
        height = float(h)
        self.debug("screen size: (%r,%r)", width, height)
        
        return width / float(height)
    
    def _get_pixel_aspect_ratio(self, screen_aspect_ratio):
        w, h = self.viewport_handle.screen_resolution
        return ( w / float(h) ) / float(screen_aspect_ratio)
                
    def _configure_callback(self, viewport, event):
        # (re)schedule the resize of the canvas
        if self._resize_delayed != None and self._resize_delayed.active():
            self._resize_delayed.reset(self._resize_delay)
        else:
            self._resize_delayed = reactor.callLater(self._resize_delay,
                                                     self._resize_canvas)

    def _resize_canvas(self):
        # only reacts if the size of the viewport changed
        if self.viewport_size != self.viewport_handle.size:
            self.debug("regeneration of the drawables")
            self.viewport_size = self.viewport_handle.size
            self.canvas_resized.emit(self.canvas.size)

        """
        # resize the canvas to follow the aspect ratio of the viewport
        viewport_ratio = float(self.viewport_handle.width)/float(self.viewport_handle.height)
        canvas_ratio = float(self.canvas.width)/float(self.canvas.height)

        # only resize the canvas if the aspect ratio of the viewport has changed
        if viewport_ratio != canvas_ratio:
            # FIXME: only one of width/height needs to change (multiplied by
            # viewport_ratio)
            self.canvas.size = (self.viewport_handle.width/100.0,
                                self.viewport_handle.height/100.0)
            self.debug("The canvas is getting resized. New size = (%s, %s)" % \
                        (self.canvas.size[0], self.canvas.size[1]))
        """

    def _drag_motion_cb(self, viewport, event):
        return True

    def _add_dropped_location(self, uri):
        action_type = MediaLocationMessage.ActionType.LOCATION_ADDED
        message = MediaLocationMessage(action_type, uri.label, uri.scheme, uri,
                media_types=['video', 'audio', 'image'], removable=False)
        common.application.bus.send_message(message)

    def _dropped_uri_metadata_cb(self, result):
        uri = result['uri']
        file_type = result['file_type']
        mime_type = result['mime_type']

        if file_type == 'directory':
            self._add_dropped_location(uri)
        elif mime_type == 'application/zip':
            # an egg (create a custom typefinder?)
            # FIXME: writeme
            bus = common.application.bus
            bus.send_message(DroppedPluginMessage(uri))

    def _drag_drop_cb(self, viewport, event):
        metadata_manager = common.application.metadata_manager
        
        for uri in event.uri:
            uri = unquote(uri)
            uri = MediaUri(uri)
            metadata = {'uri': uri, 'file_type': None, 'mime_type': None}
            dfr = metadata_manager.get_metadata(metadata)
            dfr.addCallback(self._dropped_uri_metadata_cb)

    def _motion_notify_callback(self, viewport, event):
        # show the cursor
        viewport.cursor = pgm.VIEWPORT_INHERIT

        # (re)schedule the hiding of the cursor
        if self._cursor_delayed != None and self._cursor_delayed.active():
            self._cursor_delayed.reset(self._cursor_delay)
        else:
            self._cursor_delayed = reactor.callLater(self._cursor_delay, self._hide_cursor)

    def _hide_cursor(self):
        self.viewport_handle.cursor = pgm.VIEWPORT_NONE
        
    def reduce_window(self):
        if self.gtk_window:
            self.gtk_window.iconify()
        
    def theme_changed(self, old_theme, new_theme):
        for icon, master in self.master_drawables.iteritems():
            image_path = new_theme.get_media(icon)
            master.set_from_file(image_path)

    def _add_fps_display(self):
        self._fps_label = Text()
        self._fps_label.bg_a = 0
        self._fps_label.width = self.canvas.width/10.0
        self._fps_label.height = self.canvas.height/20.0
        self._fps_label.x = self.canvas.width - self._fps_label.width
        self._fps_label.y = self.canvas.height - self._fps_label.height
        self._fps_label.visible = True
        self.canvas.add(pgm.DRAWABLE_NEAR, self._fps_label)
        self._update_fps_display()

    def _update_fps_display(self):
        if self.viewport_handle:
            new_label = "%d fps" % self.viewport_handle.frame_rate
            if new_label != self._fps_label.label:
                self._fps_label.label = new_label
            reactor.callLater(1.0, self._update_fps_display)

    def _add_mem_display(self):
        self._mem_label = Text()
        self._mem_label.bg_a = 0
        self._mem_label.width = self.canvas.width/5.0
        self._mem_label.height = self.canvas.height/20.0
        self._mem_label.x = self.canvas.width \
                            - self._mem_label.width \
                            - self._fps_label.width
        self._mem_label.y = self.canvas.height - self._mem_label.height
        self._mem_label.visible = True
        self.canvas.add(pgm.DRAWABLE_NEAR, self._mem_label)
        self._update_mem_display()

    def _update_mem_display(self):
        if self.viewport_handle:
            if sys.platform == 'linux2':
                rss = int(open('/proc/self/statm').readline().split()[1])
                rss = rss * resource.getpagesize() / (1024.0 * 1024.0)
                new_label = "rss %.1fMB" % (rss)
                if new_label != self._mem_label.label:
                    self._mem_label.label = new_label
                reactor.callLater(1.0, self._update_mem_display)

    def _add_cpu_display(self):
        self._cpu_label = Text()
        self._cpu_label.bg_a = 0
        self._cpu_label.width = self.canvas.width/5.0
        self._cpu_label.height = self.canvas.height/20.0
        self._cpu_label.x = self.canvas.width \
                            - self._cpu_label.width \
                            - self._mem_label.width \
                            - self._fps_label.width
        self._cpu_label.y = self.canvas.height - self._cpu_label.height
        self._cpu_label.visible = True
        self.canvas.add(pgm.DRAWABLE_NEAR, self._cpu_label)
        self._update_cpu_display()

    def _update_cpu_display(self):
        if self.viewport_handle:
            if sys.platform == 'linux2':
                # FIXME! Find the way to actually do this
                cpu_usage = None
                new_label = "cpu %d%%" % (100)
                if new_label != self._cpu_label.label:
                    self._cpu_label.label = new_label
                reactor.callLater(1.0, self._update_cpu_display)

