#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: ts=4 
###
#
# Copyright (c) 2009, 2010 J. Félix Ontañón
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Authors : J. Félix Ontañón <fontanon@emergya.es>
# 
###

import sys
import os

import gtk
import gobject
import dbus, dbus.exceptions

import tarfile
import tempfile
import shutil

from wiican.defs import *
from wiican.ui import Notificator, PngAnimation
from wiican.ui import MappingManagerDialog, MappingEditorDialog
from wiican.ui.validationerrordlg import ValidationErrorDialog
from wiican.mapping import *
from wiican.service import WIICAN_PATH, WIICAN_URI
from wiican.service import WC_DISABLED, WC_BLUEZ_PRESENT, WC_UINPUT_PRESENT, \
    WC_WIIMOTE_DISCOVERING

# Load icons
theme = gtk.icon_theme_get_default()

wiican_on_icon = theme.load_icon('wiican-on', 24, 0)
wiican_off_icon = theme.load_icon('wiican-off', 24, 0)
wiican_disc1_icon = theme.load_icon('wiican-discover1', 24, 0)
wiican_disc2_icon = theme.load_icon('wiican-discover2', 24, 0)
wiican_disc3_icon = theme.load_icon('wiican-discover3', 24, 0)

mapping_manager = MappingManager()

class WiimoteStatusIcon(gtk.StatusIcon):
    def __init__(self):
        super(WiimoteStatusIcon, self).__init__()
        self.set_visible(True)

        # Load UI
        builder = gtk.Builder()
        if not builder.add_from_file(WIIMOTEMANAGER_UI):
            raise 'Cant load %s' % WIIMOTEMANAGER_UI
        builder.connect_signals(self)

        self.aboutdlg = builder.get_object('WiiAboutDialog')
        self.main_menu = builder.get_object('main_menu')

        self.aboutdlg.connect('response', lambda d, r: d.hide())
        self.connect('popup-menu', self.__icon_popupmenu_cb, None)
        self.connect('activate', self.__activate_cb)

        self.__animation = PngAnimation([wiican_on_icon, wiican_disc1_icon, 
            wiican_disc2_icon, wiican_disc3_icon])
            
        # Connect to wiican service
        bus = dbus.SessionBus()  
        self.__wiican_iface = dbus.Interface(bus.get_object (WIICAN_URI, 
            WIICAN_PATH), WIICAN_URI)
        self.__cur_status = self.__wiican_iface.GetStatus()
        self.__wiican_iface.connect_to_signal('StatusChanged', self.__status_cb, 
            dbus_interface='org.gnome.Wiican')

        self.__load_mappings_menu()
        self.__notificator = Notificator('wiican')
        self.__status_cb(self.__cur_status)

    def __status_cb(self, new_status):
        if not new_status & WC_BLUEZ_PRESENT:
            self.__set_no_bluetooth_st()
        elif not new_status & WC_UINPUT_PRESENT:
            self.__set_no_uinput_st()
        elif new_status & WC_WIIMOTE_DISCOVERING: 
            self.__discovering_st()
        elif new_status & (WC_UINPUT_PRESENT | WC_BLUEZ_PRESENT):
            self.__idle_st()

        self.__cur_status = new_status

    def __set_no_bluetooth_st(self):
        self.set_from_icon_name('wiican-off')
        self.set_tooltip(_('Plug a bluetooth adapter'))
        self.__disconnect_item.set_sensitive(False)

    def __set_no_uinput_st(self):
        self.set_from_icon_name('wiican-off')
        self.set_tooltip(_('Please load uinput module first'))
        self.__disconnect_item.set_sensitive(False)

    def __idle_st(self):
        self.__disconnect_item.set_sensitive(False)
        self.set_from_icon_name('wiican-on')
        self.set_tooltip(_('Hold left button for use wiimote\n' \
                'Right button for menu'))

    def __discovering_st(self):
        def animate():
    	    if not self.__cur_status & WC_WIIMOTE_DISCOVERING:
                return False
            else:
                self.set_from_pixbuf(self.__animation.next())
                return True

        self.__disconnect_item.set_sensitive(True)
        self.set_tooltip(_('Discovering Wiimote'))
        gobject.timeout_add(500, animate)

    def __load_mappings_menu(self):
        self.__mappings_menu = gtk.Menu()

        disconnect_item = gtk.ImageMenuItem(gtk.STOCK_DISCONNECT)
        disconnect_item.connect('activate', self.__discover_cb, -1)
        disconnect_item.show()
        self.__mappings_menu.append(disconnect_item)
        self.__disconnect_item = disconnect_item
        if not self.__cur_status & WC_WIIMOTE_DISCOVERING:
            self.__disconnect_item.set_sensitive(False)

        sep_item = gtk.SeparatorMenuItem()
        sep_item.show()
        self.__mappings_menu.append(sep_item)

        mapping_manager.scan_mappings()

        no_visible_mappings = True
        for mapping_id, mapping in mapping_manager.items():
            if not mapping_manager.is_visible(mapping_id):
                continue

            icon = gtk.gdk.pixbuf_new_from_file_at_size(mapping.get_icon(), 16, 16)
            menuitem = gtk.ImageMenuItem(mapping.get_name())
            menuitem.set_tooltip_text(mapping.get_comment())
            menuitem.set_image(gtk.image_new_from_pixbuf(icon))
            menuitem.connect('activate', self.__discover_cb, mapping)
            menuitem.show()
            self.__mappings_menu.append(menuitem)
            no_visible_mappings = False

        if no_visible_mappings:
            add_mappings_item = gtk.MenuItem(_('Add visible mappings'))
            add_mappings_item.connect('activate', self.preferences_cb)
            add_mappings_item.show()
            self.__mappings_menu.append(add_mappings_item)

    def __icon_popupmenu_cb(self, status_icon, button, activate_time, data):
        self.main_menu.popup(None, None, gtk.status_icon_position_menu, button, 
			        activate_time, status_icon)

    def __activate_cb(self, status_icon):
        if self.__cur_status & WC_UINPUT_PRESENT and \
                self.__cur_status & WC_BLUEZ_PRESENT:
            self.__mappings_menu.popup(None, None, 
                    gtk.status_icon_position_menu, 1, 
                    gtk.get_current_event_time(), status_icon)

    def preferences_cb(self, widget):
        mapping_dlg = MappingManagerDialog()
        mapping_dlg.run()
        self.__load_mappings_menu()

    def about_cb(self, widget):
        self.aboutdlg.run()

    def quit_cb(self, widget):
        mapping_manager.saveconf()
        sys.exit(0)

    def __discover_cb(self, discover_item, mapping=None):
        if self.__cur_status & WC_WIIMOTE_DISCOVERING:
            self.__wiican_iface.DisconnectWiimote()

        if mapping and mapping != -1:
            filename = os.path.join(mapping.get_path(), Mapping.mapping_filename)

            try:
                self.__wiican_iface.ConnectWiimote(filename, True)
            except dbus.exceptions.DBusException, error:
                if error.message == ('Mapping validation error'):
                    valerr_dlg = ValidationErrorDialog(mapping.get_icon(), open_editor=True)
                    location = self.get_geometry()[1]
                    valerr_dlg.move(location[0], location[1])
                    
                    if valerr_dlg.run() == gtk.RESPONSE_YES:
                        mapping_editor_dlg = MappingEditorDialog(mapping)
                        mapping_editor_dlg.set_title(_('Editing ') + \
                            mapping.get_name())

                        if mapping_editor_dlg.run() == gtk.RESPONSE_OK:
                            new_mapping = mapping_editor_dlg.get_mapping()
                            mapping_manager.write_mapping(new_mapping)

                        mapping_editor_dlg.destroy()
                        
                    valerr_dlg.destroy()
                    self.__load_mappings_menu()
                    return

            self.__notificator.display_notification(title=_('Press 1+2'), 
                text=_('To put you Wiimote in discoverable mode now'),
                icon='wiican')

class MappingLauncherDialog(MappingEditorDialog):
    def __init__(self, package_filename):
        self.mapping_path = self.load_mapping_in_tmp(package_filename)
        MappingEditorDialog.__init__(self, Mapping(self.mapping_path), 
            system_mapping = False)

        self.package_filename = package_filename
        self.save_btn = self.builder.get_object('save_btn')
        self.cancel_btn = self.builder.get_object('cancel_btn')

        self.save_btn.connect('clicked', self.save_mapping)
        self.cancel_btn.connect('clicked', self.cancel_cb)
        
    def load_mapping_in_tmp(self, package_path):
        package_file = tarfile.open(package_path)

        if not Mapping.info_filename in package_file.getnames():
            raise MappingError, _('Not %s file found on wiican package' % \
                Mapping.info_filename)

        if not Mapping.mapping_filename in package_file.getnames():
            raise MappingError, _('Not %s file found  on wiican package' % \
                Mapping.mapping_filename)

        mapping_path = tempfile.mkdtemp()
        package_file.extractall(mapping_path)
        package_file.close()

        return mapping_path
        
    def save_mapping(self, widget):
        mapping = self.get_mapping()
        save_path = tempfile.mkdtemp()
        mapping.write(save_path)

        package_file = tarfile.TarFile(self.package_filename, 'w')
        for f in os.listdir(save_path):
            package_file.add(os.path.join(save_path, f), arcname=f)

        package_file.close()
        shutil.rmtree(save_path)
        shutil.rmtree(self.mapping_path)

    def cancel_cb(self, widget):
        shutil.rmtree(self.mapping_path)
        
if __name__ == '__main__':
    def launch_statusicon():
        from dbus.mainloop.glib import DBusGMainLoop

        DBusGMainLoop(set_as_default=True)
        wiican = WiimoteStatusIcon()
        gobject.MainLoop().run()

    def display_error(message):
        error_loading_dlg = gtk.MessageDialog(
            flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            type = gtk.MESSAGE_ERROR,
            buttons = gtk.BUTTONS_CLOSE,
            message_format = message)
        error_loading_dlg.set_title(_('Wiican Mapping Package load failed!'))
        error_loading_dlg.run()
        error_loading_dlg.destroy()

    import exceptions
    from optparse import OptionParser

    parser = OptionParser(_('usage: %prog [options] arg'))
    parser.add_option('-f', '--file', dest='filename',
                      help=_('loads mapping package from FILENAME'))
    (options, args) = parser.parse_args()

    if options.filename != None:
        filename = options.filename
        if filename.startswith("'") or filename.startswith('"') :
            filename = filename[1:-1]

        if os.path.exists(filename):
            try:
                mapping_launcher_dlg = MappingLauncherDialog(filename)
            except exceptions.Exception, e:
                display_error(e.message)
                sys.exit(-1)

            mapping_launcher_dlg.run()
            sys.exit(0)

        else:
            display_error(_('No wiican package mapping found: %s' % filename))
            sys.exit(-2)

    else:
        launch_statusicon()
        sys.exit(0)
