# Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
#                    Jonathan Schleifer <js-gajim AT webkeks.org>
#                    Stephan Erb <steve-e AT h3c.de>
#
# This file is part of Gajim.
#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.

import string
import random
import itertools

from gajim.common import helpers
from gajim.common import events
from gajim.common import app
from gajim.common import contacts
from gajim.common import ged
from gajim.common.helpers import AdditionalDataDict
from gajim.common.const import KindConstant
from gajim.gtk.util import get_show_in_roster
from gajim.gtk.util import get_show_in_systray


class ChatControlSession:
    def __init__(self, conn, jid, thread_id, type_='chat'):
        self.conn = conn
        self.jid = jid
        self.type_ = type_
        self.resource = jid.getResource()
        self.control = None

        if thread_id:
            self.received_thread_id = True
            self.thread_id = thread_id
        else:
            self.received_thread_id = False
            if type_ == 'normal':
                self.thread_id = None
            else:
                self.thread_id = self.generate_thread_id()

        self.loggable = True

        self.last_send = 0
        self.last_receive = 0

        app.ged.register_event_handler('decrypted-message-received',
                                       ged.PREGUI,
                                       self._nec_decrypted_message_received)

    def generate_thread_id(self):
        return ''.join(
            [f(string.ascii_letters) for f in itertools.repeat(
                random.choice, 32)]
        )

    def is_loggable(self):
        return helpers.should_log(self.conn.name, self.jid.getStripped())

    def get_to(self):
        bare_jid = self.jid.getBare()
        if not self.resource:
            return bare_jid
        return bare_jid + '/' + self.resource

    def _nec_decrypted_message_received(self, obj):
        """
        Dispatch a received <message> stanza
        """
        if obj.session != self:
            return

        if obj.properties.is_muc_pm:
            contact = app.contacts.get_gc_contact(
                self.conn.name, obj.jid, obj.resource)
        else:
            contact = app.contacts.get_contact(
                self.conn.name, obj.jid, obj.resource)
        if self.resource != obj.resource:
            self.resource = obj.resource
            if self.control:
                if isinstance(contact, contacts.GC_Contact):
                    self.control.gc_contact = contact
                    self.control.contact = contact.as_contact()
                else:
                    self.control.contact = contact
                if self.control.resource:
                    self.control.change_resource(self.resource)

        if not obj.msgtxt:
            return

        log_type = KindConstant.CHAT_MSG_RECV
        if obj.properties.is_sent_carbon:
            log_type = KindConstant.CHAT_MSG_SENT

        if self.is_loggable() and obj.msgtxt:
            jid = obj.fjid
            if not obj.properties.is_muc_pm:
                jid = obj.jid

            obj.msg_log_id = app.logger.insert_into_logs(
                self.conn.name,
                jid,
                obj.properties.timestamp,
                log_type,
                message=obj.msgtxt,
                subject=obj.properties.subject,
                additional_data=obj.additional_data,
                stanza_id=obj.unique_id,
                message_id=obj.properties.id)

        if obj.properties.is_muc_pm and not obj.gc_control:
            # This is a carbon of a PM from a MUC we are not currently
            # joined. We log it silently without notification.
            return True

        if not obj.msgtxt: # empty message text
            return True

        if not self.control:
            ctrl = app.interface.msg_win_mgr.search_control(obj.jid,
                obj.conn.name, obj.resource)
            if ctrl:
                self.control = ctrl
                self.control.set_session(self)
                if isinstance(contact, contacts.GC_Contact):
                    self.control.gc_contact = contact
                    self.control.contact = contact.as_contact()
                else:
                    self.control.contact = contact

        if not obj.properties.is_muc_pm:
            self.roster_message2(obj)

    def roster_message2(self, obj):
        """
        Display the message or show notification in the roster
        """
        contact = None
        jid = obj.jid
        resource = obj.resource

        fjid = jid

        # Try to catch the contact with correct resource
        if resource:
            fjid = jid + '/' + resource
            contact = app.contacts.get_contact(obj.conn.name, jid, resource)

        highest_contact = app.contacts.get_contact_with_highest_priority(
            obj.conn.name, jid)
        if not contact:
            # If there is another resource, it may be a message from an
            # invisible resource
            lcontact = app.contacts.get_contacts(obj.conn.name, jid)
            if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
            lcontact[0].show != 'offline')) and jid.find('@') > 0:
                contact = app.contacts.copy_contact(highest_contact)
                contact.resource = resource
                contact.priority = 0
                contact.show = 'offline'
                contact.status = ''
                app.contacts.add_contact(obj.conn.name, contact)

            else:
                # Default to highest prio
                fjid = jid
                contact = highest_contact

        if not contact:
            # contact is not in roster
            contact = app.interface.roster.add_to_not_in_the_roster(
                obj.conn.name, jid, obj.properties.nickname)

        if not self.control:
            ctrl = app.interface.msg_win_mgr.search_control(obj.jid,
                obj.conn.name, obj.resource)
            if ctrl:
                self.control = ctrl
                self.control.set_session(self)
            else:
                fjid = jid

        obj.popup = helpers.allow_popup_window(self.conn.name)

        event_t = events.ChatEvent
        event_type = 'message_received'

        if self.control:
            # We have a ChatControl open
            obj.show_in_roster = False
            obj.show_in_systray = False
            do_event = False
        elif obj.properties.is_sent_carbon:
            # Its a Carbon Copied Message we sent
            obj.show_in_roster = False
            obj.show_in_systray = False
            unread_events = app.events.get_events(
                self.conn.name, fjid, types=['chat'])
            read_ids = []
            for msg in unread_events:
                read_ids.append(msg.msg_log_id)
            app.logger.set_read_messages(read_ids)
            app.events.remove_events(self.conn.name, fjid, types=['chat'])
            do_event = False
        else:
            # Everything else
            obj.show_in_roster = get_show_in_roster(event_type, self)
            obj.show_in_systray = get_show_in_systray(event_type,
                                                      obj.conn.name,
                                                      contact.jid)
            do_event = True
        if do_event:
            kind = obj.properties.type.value
            event = event_t(
                obj.msgtxt,
                obj.properties.subject,
                kind,
                obj.properties.timestamp,
                obj.resource,
                obj.msg_log_id,
                correct_id=obj.correct_id,
                message_id=obj.properties.id,
                session=self,
                displaymarking=obj.displaymarking,
                sent_forwarded=obj.properties.is_sent_carbon,
                show_in_roster=obj.show_in_roster,
                show_in_systray=obj.show_in_systray,
                additional_data=obj.additional_data)

            app.events.add_event(self.conn.name, fjid, event)

    def roster_message(self, jid, msg, tim, msg_type='',
    subject=None, resource='', msg_log_id=None, user_nick='',
    displaymarking=None, additional_data=None):
        """
        Display the message or show notification in the roster
        """
        contact = None
        fjid = jid

        if additional_data is None:
            additional_data = AdditionalDataDict()

        # Try to catch the contact with correct resource
        if resource:
            fjid = jid + '/' + resource
            contact = app.contacts.get_contact(self.conn.name, jid, resource)

        highest_contact = app.contacts.get_contact_with_highest_priority(
                self.conn.name, jid)
        if not contact:
            # If there is another resource, it may be a message from an invisible
            # resource
            lcontact = app.contacts.get_contacts(self.conn.name, jid)
            if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
            lcontact[0].show != 'offline')) and jid.find('@') > 0:
                contact = app.contacts.copy_contact(highest_contact)
                contact.resource = resource
                if resource:
                    fjid = jid + '/' + resource
                contact.priority = 0
                contact.show = 'offline'
                contact.status = ''
                app.contacts.add_contact(self.conn.name, contact)

            else:
                # Default to highest prio
                fjid = jid
                contact = highest_contact

        if not contact:
            # contact is not in roster
            contact = app.interface.roster.add_to_not_in_the_roster(
                    self.conn.name, jid, user_nick)

        if not self.control:
            ctrl = app.interface.msg_win_mgr.get_control(fjid, self.conn.name)
            if ctrl:
                self.control = ctrl
                self.control.set_session(self)
            else:
                fjid = jid

        # Do we have a queue?
        no_queue = len(app.events.get_events(self.conn.name, fjid)) == 0

        popup = helpers.allow_popup_window(self.conn.name)

        # We print if window is opened and it's not a single message
        if self.control:
            typ = ''

            if msg_type == 'error':
                typ = 'error'

            self.control.add_message(msg,
                                     typ,
                                     tim=tim,
                                     subject=subject,
                                     displaymarking=displaymarking,
                                     additional_data=additional_data)

            if msg_log_id:
                app.logger.set_read_messages([msg_log_id])

            return

        # We save it in a queue
        event_t = events.ChatEvent
        event_type = 'message_received'

        show_in_roster = get_show_in_roster(event_type, self)
        show_in_systray = get_show_in_systray(event_type,
                                              self.conn.name,
                                              contact.jid)

        event = event_t(msg, subject, msg_type, tim, resource,
            msg_log_id, session=self,
            displaymarking=displaymarking, sent_forwarded=False,
            show_in_roster=show_in_roster, show_in_systray=show_in_systray,
            additional_data=additional_data)

        app.events.add_event(self.conn.name, fjid, event)

        if popup:
            if not self.control:
                self.control = app.interface.new_chat(contact,
                    self.conn.name, session=self)

                if app.events.get_events(self.conn.name, fjid):
                    self.control.read_queue()
        else:
            if no_queue: # We didn't have a queue: we change icons
                app.interface.roster.draw_contact(jid, self.conn.name)

            app.interface.roster.show_title() # we show the * or [n]
        # Select the big brother contact in roster, it's visible because it has
        # events.
        family = app.contacts.get_metacontacts_family(self.conn.name, jid)
        if family:
            _nearby_family, bb_jid, bb_account = \
                    app.contacts.get_nearby_family_and_big_brother(family,
                    self.conn.name)
        else:
            bb_jid, bb_account = jid, self.conn.name
        app.interface.roster.select_contact(bb_jid, bb_account)
