# telepathy-pinocchio - dummy Telepathy connection manager for instrumentation
#
# Copyright (C) 2008 Nokia Corporation
# Copyright (C) 2008 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# version 2.1 as published by the Free Software Foundation.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA

import os.path
import shutil
from xml.dom import minidom
import dbus.service

import telepathy as tp

import pinocchio as pin

class ConnectionManager(tp.server.ConnectionManager):
    """
    Pinocchio dummy (instrumentation) Connection Manager
    
    Implements DBus interface org.freedesktop.Telepathy.ConnectionManager
    """

    def __init__(self):
        tp.server.ConnectionManager.__init__(self, 'pinocchio')

        self._protos[pin.common.PROTO_DEFAULT] = pin.connection.Connection

    def GetParameters(self, proto):
        """Returns list of parameters for this protocol."""

        raise tp.errors.NotImplemented('method GetParameters() not yet '
                                       'implemented')

    def connection_connect(self, connection):
        """Treat this connection as if it connected to a real server.
        
        Arguments:
        connection -- connection to track and support

        Exceptions:
        telepathy.errors.NotAvailable 
        """
        # there's no reason to raise NotAvailable, but this function would
        # hypothetically raise it if it were on a real network

    def connection_disconnect(self, connection):
        """Treat this connection as if it disconnected from a real server.
        
        Arguments:
        connection -- connection to no longer track
        """
        if connection in self._connections:
            self._connections.remove(connection)

    def channel_new(self, connection, channel_type, handle_type, handle,
                    suppress_handler):
        """Returns a channel for the connection; if it does not already exist,
        create a new one.
        
        Arguments:
        connection -- connection on which to open a new channel
        channel_type -- DBus interface name for the new channel type
        handle_type -- Telepathy handle type for the channel
        handle -- handle for the new or existing channel (as in RequestChannel)
        suppress_handler -- True to prevent any automatic handling of channel
        """
        if connection not in self._connections:
            raise tp.errors.Disconnected('connection unknown')

        handle_obj = connection.get_handle_obj(handle_type, handle)

        if channel_type == tp.interfaces.CHANNEL_TYPE_CONTACT_LIST:
            channel_result = self._channel_new_contact_list(connection,
                                                            handle_type, handle,
                                                            suppress_handler)
        else:
            # TODO: should be, but is not yet, implemented
            # tp.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
            # tp.interfaces.CHANNEL_TYPE_ROOM_LIST:
            # tp.interfaces.CHANNEL_TYPE_TEXT:
            # tp.interfaces.CHANNEL_TYPE_TUBES:
            raise tp.errors.NotImplemented()

        return channel_result

    def _channel_new_contact_list(self, connection, handle_type, handle,
                                  suppress_handler):
        """Returns a contact list channel for the connection; if it does not
        already exist, create a new one.
        
        Arguments:
        connection -- connection on which to open a new channel
        handle_type -- Telepathy handle type for the channel
        handle -- handle for the new or existing channel (as in RequestChannel)
        suppress_handler -- True to prevent any automatic handling of channel
        """
        handle_obj = connection.get_handle_obj(handle_type, handle)

        if handle_type == tp.constants.HANDLE_TYPE_LIST:
            account_id = connection.get_account_id()
            channel_result = None

            for channel in connection._channels:
                if channel._type == tp.interfaces.CHANNEL_TYPE_CONTACT_LIST:
                    try:
                        if channel.account_id == account_id:
                            if channel._handle.get_id() == handle:
                                # channel already exists; return it to caller
                                channel_result = channel
                                break
                    except AttributeError:
                        pass

            if not channel_result:
                channel_result = pin.channel.contact_list.ContactList(
                                                                connection,
                                                                handle_obj,
                                                                account_id)
                connection.add_channel(channel_result, handle_obj,
                                       suppress_handler)
        elif handle_type == tp.constants.HANDLE_TYPE_GROUP:
            account_id = connection.get_account_id()
            channel_result = None

            for channel in connection._channels:
                if channel._type == tp.interfaces.CHANNEL_TYPE_CONTACT_LIST:
                    try:
                        if channel.account_id == account_id:
                            if channel._handle.get_id() == handle:
                                # channel already exists; return it to caller
                                channel_result = channel
                                break
                    except AttributeError:
                        pass

            if not channel_result:
                channel_result = pin.channel.contact_list.Group(connection,
                                                                handle_obj,
                                                                account_id)
                connection.add_channel(channel_result, handle_obj,
                                       suppress_handler)
        else:
            raise tp.errors.InvalidArgument()

        return channel_result

    def account_exists(self, account_name):
        """Returns True if the account exists, False otherwise.

        Arguments:
        account_name -- raw account name (eg, foo@example.org)
        """
        account_id = tp.server.conn._escape_as_identifier(account_name)

        filename = pin.common.get_contacts_file(
                                            account_id,
                                            pin.common.PREFIX_SAVED_PREFERRED)

        return os.path.isfile(filename)

    def connections_teardown(self):
        """Tear down all managed connections."""

        for connection in self._connections:
            connection.Disconnect()

    @dbus.service.method(tp.interfaces.CONNECTION_MANAGER,
                         in_signature='s', out_signature='')
    def create_account(self, account_name):
        """Initalize a new account with an empty roster.

        Arguments:
        account_name -- raw account name (eg, foo@example.org)

        Exceptions:
        org.freedesktop.Telepathy.Error.InvalidArgument
        """
        # TODO: improve this validation
        if not account_name:
            raise tp.errors.InvalidArgument('invalid account name')

        account_id = tp.server.conn._escape_as_identifier(account_name)

        # only create the account if it doesn't exist
        if not self.account_exists(account_name):
            dom_impl = minidom.getDOMImplementation()
            xml_doc = dom_impl.createDocument(None, 'roster', None)
            roster_xml = xml_doc.documentElement

            # add newline for human-readability
            newline_value = xml_doc.createTextNode('\n')
            roster_xml.appendChild(newline_value)

            pin.common.save_roster(xml_doc, account_id)

    @dbus.service.method(tp.interfaces.CONNECTION_MANAGER,
                         in_signature='s', out_signature='')
    def remove_account(self, account_name):
        """Delete an existing account. Note that this won't delete a global
        account.

        Arguments:
        account_name -- raw account name (eg, foo@example.org)

        Exceptions:
        org.freedesktop.Telepathy.Error.InvalidArgument
        """
        # TODO: improve this validation
        if not account_name:
            raise tp.errors.InvalidArgument('invalid account name')

        account_id = tp.server.conn._escape_as_identifier(account_name)

        # only remove the account if it exists
        account_dir = pin.common.get_account_dir(pin.common.PREFIX_SAVED,
                                                 account_id)
        shutil.rmtree(account_dir)
