# 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 import notify
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.single_message import SingleMessageWindow


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 app.config.should_log(self.conn.name,
                                     self.jid.getStripped())

    def get_to(self):
        to = str(self.jid)
        return app.get_jid_without_resource(to) + '/' + self.resource

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

        if obj.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 obj.mtype == 'chat':
            if not obj.msgtxt:
                return

            log_type = KindConstant.CHAT_MSG_RECV
            if obj.forwarded and obj.sent:
                log_type = KindConstant.CHAT_MSG_SENT
        else:
            log_type = KindConstant.SINGLE_MSG_RECV
            if obj.forwarded and obj.sent:
                log_type = KindConstant.SINGLE_MSG_SENT

        treat_as = app.config.get('treat_incoming_messages')
        if treat_as:
            obj.mtype = treat_as
        pm = False
        if obj.muc_pm or (obj.gc_control and obj.resource):
            # It's a Private message
            pm = True
            obj.mtype = 'pm'

        if self.is_loggable() and obj.msgtxt:
            if obj.xhtml and app.config.get('log_xhtml_messages'):
                msg_to_log = obj.xhtml
            else:
                msg_to_log = obj.msgtxt

            jid = obj.fjid
            if not pm:
                jid = obj.jid

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

        self.conn.get_module('MAM').save_archive_id(
            None, obj.stanza_id, obj.timestamp)

        if obj.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 app.config.get_per('accounts', self.conn.name,
        'ignore_unknown_contacts') and not app.contacts.get_contacts(
        self.conn.name, obj.jid) and not pm:
            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 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.user_nick)

        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 obj.mtype == 'normal':
            event_t = events.NormalEvent
            event_type = 'single_message_received'

        if self.control and obj.mtype != 'normal':
            # We have a ChatControl open
            obj.show_in_roster = False
            obj.show_in_systray = False
            do_event = False
        elif obj.forwarded and obj.sent:
            # 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 = notify.get_show_in_roster(event_type,
                self.conn.name, contact.jid, self)
            obj.show_in_systray = notify.get_show_in_systray(event_type,
                self.conn.name, contact.jid)
            if obj.mtype == 'normal' and obj.popup:
                do_event = False
            else:
                do_event = True
        if do_event:
            event = event_t(obj.msgtxt, obj.subject, obj.mtype, obj.timestamp,
                obj.encrypted, obj.resource, obj.msg_log_id,
                correct_id=(obj.id_, obj.correct_id), xhtml=obj.xhtml,
                session=self, form_node=obj.form_node,
                displaymarking=obj.displaymarking,
                sent_forwarded=obj.forwarded and obj.sent,
                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, encrypted=False, msg_type='',
    subject=None, resource='', msg_log_id=None, user_nick='', xhtml=None,
    form_node=None, 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)

        if msg_type == 'normal' and popup: # it's single message to be autopopuped
            SingleMessageWindow(self.conn.name, contact.jid,
                    action='receive', from_whom=jid, subject=subject, message=msg,
                    resource=resource, session=self, form_node=form_node)
            return

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

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

            self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted,
                    subject=subject, xhtml=xhtml, 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'

        if msg_type == 'normal':
            event_t = events.NormalEvent
            event_type = 'single_message_received'

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

        event = event_t(msg, subject, msg_type, tim, encrypted, resource,
            msg_log_id, xhtml=xhtml, session=self, form_node=form_node,
            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)