# -*- coding: utf-8 -*-

#   This file is part of emesene.
#
#    Emesene 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 2 of the License, or
#    (at your option) any later version.
#
#    emesene 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 emesene; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import gtk
import pango
import os
import gobject
import paths
import abstract.stock
import dialog
# TODO: write p2p for ink, then you can reenable this.
#import InkDraw
import ConversationUI

class ConversationWindow(gtk.Window):
    '''This class is the conversation window, it is just the frame
    where all the conversations are hold, it can be tabbed or one
    conversation per window, that depend on the config.'''

    def __init__(self, controller, conversation):
        '''Constructor'''
        gtk.Window.__init__(self)

        self.set_border_width(0)

        self.controller = controller
        self.config = controller.config
        self.closed = False
        self.notOpen = True
        self.minimized = False

        if not self.config.user['windows']:
            self.geometry = self.config.user['convWindowGeometry']
            self.parse_geometry(self.geometry)
        else:
            self.width = int(self.config.user['convWindowWidth'])
            self.height = int(self.config.user['convWindowHeight'])
            self.set_default_size(self.width, self.height)

        if self.config.user['convMaximized']:
            self.maximize()

        self.connect('size-allocate', self.on_size_alloc)

        # accelerators
        accelGroup = gtk.AccelGroup()
        self.add_accel_group(accelGroup)
        self.accelGroup = accelGroup
        accelGroup.connect_group(gtk.keysyms.W, \
                                  gtk.gdk.CONTROL_MASK, gtk.ACCEL_LOCKED, \
                                  self.on_key_close_tab)

        for i in range(1, 10):
            accelGroup.connect_group(gtk.keysyms._0 + i, \
                                  gtk.gdk.MOD1_MASK, gtk.ACCEL_LOCKED, \
                                  self.on_key_change_tab)

        self.conversation = conversation
        self.conversation.setIsCurrent(True)

        self.update_title()
        self.set_role('chat')

        self.textTagCount = 0

        self.user = self.conversation.getUser()
        self.conversations = []

        self.menu = ConversationWindowMenu(self, controller)

        self.vbox = gtk.VBox()
        self.tabs = gtk.Notebook()
        self.tabs.set_show_border(False)
        self.tabs.set_property('tab-vborder', 0)

        self.tabs.set_show_tabs(False) #ok, ok, never show tabs on window init
        self.tabs.set_scrollable(True)
        self.tabs.show_all()
        self.openTab(conversation)

        self.vbox.pack_start(self.menu, False, False)
        self.vbox.pack_start(self.tabs, True, True)

        if self.config.user['showMenubar']:
            self.menu.show_all()
        else:
            accelGroup.connect_group(ord('L'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.clearOutputText)
            accelGroup.connect_group(ord('Q'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.menu.onCloseActivate)
            accelGroup.connect_group(ord('I'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.menu.onInviteActivate)
            accelGroup.connect_group(ord('S'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.menu.onSendFileActivate)

        self.vbox.show()

        self.connect('delete-event', self.close)
        self.connect('focus-in-event', self.on_focus_in_event)
        self.connect('window-state-event', self.on_window_state_event)
        self.switchId = self.tabs.connect('switch-page', self.on_switch_page)
        self.showMenubarId = self.config.connect('change::showMenubar', \
            self.on_menubar_change)

        self.config.connect('change::avatarsInTaskbar', self.on_avatars_change)
        # the 'display-picture-changed' signal is emitted when the new dp is not cached
        self.controller.msn.connect('display-picture-changed', self.onDisplayPictureChanged)
        # using the 'user-attr-changed' signal to catch change to cached dps
        self.controller.msn.connect('user-attr-changed', self.onUserAttrChanged)

        self.connect('key-press-event', self.on_key_press)

        self.add(self.vbox)

        self.conversation.ui.hideUserList()
                
    def on_key_press(self, widget, event):
        if (event.state & gtk.gdk.CONTROL_MASK) and event.keyval in [ gtk.keysyms.Tab, gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Page_Down, gtk.keysyms.Page_Up]:
	    if event.state & gtk.gdk.SHIFT_MASK or event.keyval == gtk.keysyms.Page_Up:
                self.cycleTabs(-1)
            else:
                self.cycleTabs(1)
            return True

    def on_focus_in_event(self, widget, event):
        self.unsetUrgency()
        self.conversation.ui.input.grabFocus()

    def on_window_state_event(self, widget, event):
        self.config.user['convMaximized'] = (event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED)
        self.minimized = (event.new_window_state == gtk.gdk.WINDOW_STATE_ICONIFIED)

    def on_size_alloc(self, widget, allocation):
        self.width = allocation.width
        self.height = allocation.height

    def on_key_close_tab(self, accelGroup, window, keyval, modifier):
        '''Catches events like Ctrl+W and closes current tab'''
        self.closeTab(self.conversation)

    def on_key_change_tab(self, accelGroup, window, keyval, modifier):
        '''Catches alt+number and shows tab number-1  '''
        pages = self.tabs.get_n_pages()
        new = keyval - gtk.keysyms._0 - 1   # WTF?
        if new < pages:
            self.showTab(new)

    def on_menubar_change(self, _config, value, oldValue):
        '''handler for showMenubar config change'''
        if value == '1':
            self.menu.show_all()
        else:
            accelGroup = self.accelGroup
            accelGroup.disconnect_key(ord('L'), gtk.gdk.CONTROL_MASK)
            accelGroup.disconnect_key(ord('Q'), gtk.gdk.CONTROL_MASK)
            accelGroup.disconnect_key(ord('I'), gtk.gdk.CONTROL_MASK)
            accelGroup.disconnect_key(ord('S'), gtk.gdk.CONTROL_MASK)
            accelGroup.connect_group(ord('L'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.clearOutputText)
            accelGroup.connect_group(ord('Q'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.menu.onCloseActivate)
            accelGroup.connect_group(ord('I'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.menu.onInviteActivate)
            accelGroup.connect_group(ord('S'), gtk.gdk.CONTROL_MASK, \
                gtk.ACCEL_LOCKED, self.menu.onSendFileActivate)
            self.menu.hide()

    def on_avatars_change(self, _config, value, oldValue):
        '''handler for avatarsInTaskbar config change'''
        self.set_icon(self.conversation.getWindowIcon())

    def onDisplayPictureChanged(self, _msnp, _switchboard, _msnobj, email):
        if self.conversation.switchboard.firstUser == email:
            self.set_icon(self.conversation.getWindowIcon())

    def onUserAttrChanged(self, _msnp, contact):
        if self.conversation.switchboard.firstUser == contact.email:
            self.set_icon(self.conversation.getWindowIcon())

    def show_invite_dialog(self, conversation=None):
        '''show the invite dialog for conversation, if conversation is
        None, then the current conversation is used'''
        if not self.controller or not self.controller.contacts \
            or not self.controller.msn:
            return

        conversation = conversation or self.conversation

        online_contacts = self.controller.contacts.get_online_list()
        dictionary = {}

        for i in online_contacts:
            dictionary[i.account] = i

        members = {}

        for mail in conversation.getMembers() + [conversation.getUser()]:
            contact = self.controller.getContact(mail)
            if contact:
                members[mail] = contact

        ConversationUI.InviteWindow(self.controller, None, dictionary,
            members, self.controller.theme, conversation.inviteUser).run()
            
    def show_inkdraw_dialog(self, conversation=None):
        '''shows inkdraw'''
        conversation = conversation or self.conversation
        if not self.controller or not self.controller.contacts \
            or not self.controller.msn:
            return
            
        InkDraw.InkDraw(self.controller)

    def cycleTabs(self, cycle):
        '''Move <cycle> pages to the right, if it's negative it moves
        to the left. Usually it's 1 or -1'''
        pages = self.tabs.get_n_pages()
        active = self.tabs.get_current_page()
        self.showTab((active + cycle) % pages)
        self.menu.update()

    def showTab(self, num):
        '''put the num tab as the selected one
        use this only if you want to explicitly set a tab NUMBER'''
        self.on_switch_page(None, None, num)
        self.tabs.set_current_page(num)

    def on_switch_page(self, _notebook, _page, page_num):
        '''callback for the switch-page signal'''

        self.conversation.setIsCurrent(False)
        self.conversation = self.tabs.get_nth_page(page_num).parentConversation

        self.set_icon(self.conversation.getWindowIcon())

        self.update_title()
        self.conversation.setIsCurrent(True)
        self.conversation.ui.update()

    def update_title(self):
        '''change window title to parsed title of current conversation'''
        self.set_title(\
            self.controller.unifiedParser.getParser(\
            self.conversation.getTitle()).get(escaped=False))

    def openTab(self, conversation):
        '''open a new tab, that represent a new conversation'''
        ui = conversation.ui
        tabWidget = ui.tabWidget

        eventbox = gtk.EventBox()
        eventbox.set_visible_window(False)
        eventbox.add(tabWidget)
        eventbox.connect('event', self.tabs_event, ui)

        tabNum = self.tabs.append_page(child=ui, tab_label=eventbox)

        try:
            self.tabs.set_tab_reorderable(ui, True)
        except AttributeError:
            #print 'Your "stable" system sucks because it's filled with'
            #print 'outdated libs. Please upgrade to a decent distro. '
            # :P
            pass

        self.conversations.append(conversation)

        #add the new tab label to notebook menu popup
        self.addOrUpdateMenuPopupItem(tabNum, conversation)

        # expand and fill
        self.tabs.set_tab_label_packing(ui, True, True, gtk.PACK_START)

        self.tabs.set_show_tabs(len(self.conversations) > 1)

        if not self.is_active() or len(self.conversations) == 0:
            self.tabs.set_current_page(tabNum)

    def addOrUpdateMenuPopupItem(self, tabNum, conversation):
        '''add the new tab label to notebook popup menu'''

        page = self.tabs.get_nth_page(tabNum)
        hbox = gtk.HBox()
        hbox.set_spacing(6)
        image = gtk.Image()
        image.set_from_pixbuf(self.controller.theme.getImage('online'))
        hbox.pack_start(image, False, False)
        hbox.pack_start(gtk.Label(conversation.getTitle()))
        hbox.show_all()
        self.tabs.set_menu_label(page, hbox)

    def close_tab_confirmation(self, *args):
        if args[0] == abstract.stock.YES:
            for ft in args[1].transfers:
                ft.cancel() 
            self.close_tab_for_real(args[1])
            
    def closeTab(self, conversation):
        '''Close a conversation, but if there's some transfer,
        ask first and warns about unread messages'''
        closeBool = False

        #if prevention is activated and there are unread messages in this tab
        #tab grabs focus and label appears, else, check transfers
        if conversation.ui.tabWidget.hasMessageWaiting and self.config.user['preventClosingTime']:
            self.showTab(self.tabs.page_num(conversation.ui))
            message = _("There are unread messages in this tab")
            conversation.ui.set_label_closingPrevention(message)
            closeBool = False
        else:
            message = _("Transfers are happening, are you sure you want to cancel them?")
            if [x for x in conversation.transfers
                        if x.state not in (x.RECEIVED, x.FAILED)]:
                dialog.yes_no(message, self.close_tab_confirmation, conversation)
                closeBool = False
            else:
                closeBool = True

        if closeBool:
            self.close_tab_for_real(conversation)
        return closeBool

    def close_tab_for_real(self, conversation):                        
        num = self.tabs.page_num(conversation.ui)
        self.tabs.remove_page(num)

        #erase the conversation
        conversation.setStatus('closed')
        conversation.setClosed(True)
        conversation.ui.close()
        
        if conversation in self.conversations:
            self.conversations.remove(conversation)

        self.controller.conversationManager.emit('close-conversation-ui', \
            conversation, self)
        
        conversation.ui.destroy()
        
        # what do we have now?
        if len(self.conversations) == 0:
            # nothing
            self.destroy()
        else:
            # another tab, set is as current
            self.on_switch_page(None, None, self.tabs.get_current_page())
            self.tabs.set_show_tabs(len(self.conversations) > 1)

    def tabs_event(self, _widget, event, ui):
        '''closes a tab on middleclick press+release'''
        if event.type == gtk.gdk.BUTTON_RELEASE and event.button == 2:
            self.closeTab(ui.parentConversation)
        
    def clearOutputText(self, *args):
        self.conversation.textBuffer.set_text('')

    def show(self):
        gtk.Window.show(self)
        self.notOpen = False

    def close_win_confirmation(self, *args):
        if args[0] == abstract.stock.YES:
            for ft in args[1]:
                ft.cancel() 
            self.close_win_for_real()
    
    def close(self, *args):
        '''close the window and set the attribute self.closed to true
           C10uD: i hate this function and its duplicated code, but 
           reusing the close tab code is real pain :D'''
        closeBool = True
        tabsWhitUnreadMessages = False

        #if the window have tabs, check if there are unread messages
        if self.config.user['preventClosingTime'] and not self.minimized:
            for conv in self.conversations:
                if conv.hasMessageWaiting or conv.ui.tabWidget.hasMessageWaiting:
                    tabsWhitUnreadMessages = True

        #if the window doesn't have tabs but it's not minimized with unread messages
        if tabsWhitUnreadMessages or (self.get_urgency_hint() and not self.minimized):
            message = _("There are unread messages in this window")
            self.conversation.ui.set_label_closingPrevention(message)
            #unset the "messageWaiting" so user can close the window if clicked again
            preventTime = 0
            if self.config.user['preventClosingTime']:
                preventTime = 1
            gobject.timeout_add_seconds(preventTime, self.conversation.setNoMessageWaiting)
            gobject.timeout_add_seconds(preventTime, self.unsetUrgency)
            for conv in self.conversations:
                gobject.timeout_add_seconds(preventTime, conv.setNoMessageWaiting)
                gobject.timeout_add_seconds(preventTime, conv.ui.tabWidget.setNoMessageWaiting)
            closeBool = False
        else:
            activeTransfers = []
            for conv in self.conversations:
                for tr in conv.transfers:
                    if tr.state not in (tr.RECEIVED, tr.FAILED):
                        activeTransfers.append(tr)        
            if len(activeTransfers) > 0:
                message = _("Transfers are happening, are you sure you want to cancel them?")
                dialog.yes_no(message, self.close_win_confirmation, activeTransfers)
                closeBool = False
            else:
                closeBool = True

        if closeBool:
            self.unsetUrgency()
            self.close_win_for_real()
        return True
            
    def close_win_for_real(self):
        ''' this method kills all the tabs (if present) and this very window'''
        self.x, self.y = self.get_position()
        for conversation in self.conversations:
            conversation.setStatus('closed')
            conversation.setClosed(True)
            conversation.ui.close()
            # self.conversations.remove(conversation)

            self.controller.conversationManager.emit('close-conversation-ui', \
                conversation, self)
            conversation.ui.destroy()
        
        self.conversations = []
        self.closed = True

        self.geometry = '%dx%d+%d+%d' % (self.width,self.height,self.x,self.y)
        self.controller.config.user['convWindowGeometry'] = self.geometry
        self.controller.config.user['convWindowWidth'] = self.width
        self.controller.config.user['convWindowHeight'] = self.height

        self.config.disconnect(self.showMenubarId)
        self.tabs.disconnect(self.switchId)
        
        self.hide()
        self.destroy()
        
    def isClosed(self):
        '''return True if the window has been hided'''
        return self.closed

    def addChildAtAnchor(self, child, anchor, tab_num = None):
        '''add a child to an anchor in the current output if tab_num is None, otherwise add it in the tab'''
        # TODO: this method should be in conversation(ui)
        if tab_num is None:
            textview = self.conversation.ui.textview
        else:
            textview = self.tabs.get_nth_page(tab_num).textview
        textview.add_child_at_anchor(child, anchor)

    def setUrgency(self):
        '''try to set the urgency hint to the window or blink the tray icon'''

        # not perturb, respect the BSY status
        if self.controller.msn.status == 'BSY' and \
           self.config.user['dontDisturbOnBusy']:
            return

        if not self.has_toplevel_focus():
            try:
                if self.controller.config.user['blinkTrayIcon']:
                    self.controller.trayIcon.tray.set_blinking(True)
            except:
                pass
            try:
                if self.controller.config.user['statusbarHighLight']:
                    self.set_urgency_hint(True)
            except:
                pass

    def unsetUrgency(self):
        '''try to unset the urgency hint to the window and
        turn off tray icon blink'''
        try:
            self.set_urgency_hint(False)
            self.controller.trayIcon.tray.set_blinking(False)
            self.conversation.ui.update()
            self.controller.emit('message-read')
        except:
            # errors related to getting disconnected don't really matter
            pass

    def changeFont(self):
        '''opens the font selection dialog'''
        fontDesc = pango.FontDescription(self.config.user['fontFace'])
        if self.config.user['fontItalic']:
            uStyle = pango.STYLE_ITALIC
        else:
            uStyle = pango.STYLE_NORMAL

        if self.config.user['fontBold']:
            uWeight = pango.WEIGHT_BOLD
        else:
            uWeight = pango.WEIGHT_NORMAL

        fontDesc.set_style(uStyle)
        fontDesc.set_weight(uWeight)
        fontDesc.set_size(self.config.user['fontSize'] * pango.SCALE)

        fontDialog = gtk.FontSelectionDialog(_('Choose a font'))
        fontDialog.set_font_name(fontDesc.to_string())
        response = fontDialog.run()

        if response == gtk.RESPONSE_OK:
            pangoDesc = pango.FontDescription(fontDialog.get_font_name())
            font = pangoDesc.get_family()
            style = pangoDesc.get_style()
            weight = pangoDesc.get_weight()

            italic = bold = False

            if style == pango.STYLE_ITALIC:
                italic = True
            if weight == pango.WEIGHT_BOLD:
                bold = True

            self.controller.emit('font-changed', font, bold, italic, pangoDesc.get_size() / pango.SCALE)

        fontDialog.destroy()

    def changeColor(self):
        '''opens the color selection dialog'''
        colorDialog = gtk.ColorSelectionDialog(_('Choose a color'))
        colorDialog.set_resizable(False)
        colorDialog.colorsel.set_has_palette(True)
        colorDialog.colorsel.set_current_color(\
            gtk.gdk.color_parse(self.config.user['fontColor']))
        response = colorDialog.run()

        if response == gtk.RESPONSE_OK:
            color = colorDialog.colorsel.get_current_color()

            red = color.red >> 8
            green = color.green >> 8
            blue = color.blue >> 8

            self.controller.emit('color-changed', \
                '#%02X%02X%02X' % (red, green, blue))

        colorDialog.destroy()

    def send_file_dialog(self):
        '''Displays a dialog to choose a file to send'''
        if not self.controller or not self.controller.contacts \
            or not self.controller.msn:
            return

        dialog = gtk.FileChooserDialog(_('Send file'),
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                     gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dialog.set_current_folder(paths.HOME_DIR)

        response = dialog.run()

        if response == gtk.RESPONSE_OK:
            self.conversation.sendFile(dialog.get_filename())
        dialog.destroy()


class ConversationWindowMenu(gtk.MenuBar):
    '''This class represent the menu in the conversation window, i define
    it here because no other module here cares about this one.'''

    def __init__(self, parentConversationWindow, controller):
        '''Contructor'''

        gtk.MenuBar.__init__(self)

        self.parentConversationWindow = parentConversationWindow
        self.controller = controller
        accelGroup = parentConversationWindow.accelGroup

        # --- 'Conversation' menu

        conversationMenu = gtk.Menu()
        conversationMenuItem = gtk.MenuItem(_("_Conversation"))
        conversationMenuItem.set_submenu(conversationMenu)

        self.inviteMenuItem = gtk.ImageMenuItem(_('_Invite'))
        self.inviteMenuItem.set_image(gtk.image_new_from_stock(gtk.STOCK_ADD,
                                    gtk.ICON_SIZE_MENU))
        self.inviteMenuItem.connect('activate', self.onInviteActivate)
        self.inviteMenuItem.add_accelerator('activate', accelGroup, ord('I'),
            gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)

        self.sendFileMenuItem = gtk.ImageMenuItem(_("_Send file"))
        self.sendFileMenuItem.set_image(gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP,
                                    gtk.ICON_SIZE_MENU))
        self.sendFileMenuItem.connect('activate', self.onSendFileActivate)
        self.sendFileMenuItem.add_accelerator('activate', accelGroup, ord('S'),
            gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)

        clearMenuItem = gtk.ImageMenuItem(_('C_lear Conversation'))
        clearMenuItem.set_image(gtk.image_new_from_stock(gtk.STOCK_CLEAR,
                                    gtk.ICON_SIZE_MENU))
        clearMenuItem.connect('activate',
                                parentConversationWindow.clearOutputText)
        clearMenuItem.add_accelerator('activate', accelGroup, ord('L'),
            gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)

        closeMenuItem = gtk.ImageMenuItem(_('Close all'))
        closeMenuItem.set_image(gtk.image_new_from_stock(gtk.STOCK_CLOSE,
                                gtk.ICON_SIZE_MENU))
        closeMenuItem.connect('activate', self.onCloseActivate)
        closeMenuItem.add_accelerator('activate', accelGroup, ord('Q'),
            gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)

        conversationMenu.add(self.inviteMenuItem)
        conversationMenu.add(self.sendFileMenuItem)
        conversationMenu.add(clearMenuItem)
        conversationMenu.add(gtk.SeparatorMenuItem())
        conversationMenu.add(closeMenuItem)
        self.add(conversationMenuItem)

        # --- Format menu
        formatMenu = gtk.Menu()
        formatMenuItem = gtk.MenuItem(_("For_mat"))
        formatMenuItem.set_submenu(formatMenu)

        fontMenuItem = gtk.ImageMenuItem(gtk.STOCK_SELECT_FONT)
        colorMenuItem = gtk.ImageMenuItem(gtk.STOCK_SELECT_COLOR)

        fontMenuItem.connect('activate', self.onFontActivate)
        colorMenuItem.connect('activate', self.onColorActivate)

        formatMenu.add(fontMenuItem)
        formatMenu.add(colorMenuItem)
        self.add(formatMenuItem)

    def onCloseActivate(self, *args):
        '''This method is called when Close is activated on the menu'''
        self.parentConversationWindow.close()

    def onInviteActivate(self, *args):
        '''This method is called when Invite is activated on the menu'''
        self.parentConversationWindow.show_invite_dialog()

    def onSendFileActivate(self, *args):
        '''This method is called when Invite is activated on the menu'''
        self.parentConversationWindow.send_file_dialog()

    def onLogActivate(self, check):
        '''This method is called when Log is activated on the menu'''
        self.parentConversationWindow.conversation.setDoLog(
            check.get_active())

    def onFontActivate(self, *args):
        '''This method is called when font is activated on the menu'''
        self.parentConversationWindow.changeFont()

    def onColorActivate(self, *args):
        '''This method is called when color is activated on the menu'''
        self.parentConversationWindow.changeColor()

    def update(self, *args):
        '''This method is called when a switchboard error is detected'''
        self.inviteMenuItem.set_sensitive(False)
        self.sendFileMenuItem.set_sensitive(False)
