import dbus.glib
import gobject
import sys

from telepathy.client import (
        Connection, Channel)
from telepathy.interfaces import (
        CONN_INTERFACE, CHANNEL_TYPE_TUBES)
from telepathy.constants import (
        CONNECTION_HANDLE_TYPE_CONTACT,
        CONNECTION_HANDLE_TYPE_ROOM, CONNECTION_STATUS_CONNECTED,
        CONNECTION_STATUS_DISCONNECTED, CONNECTION_STATUS_CONNECTING,
        TUBE_TYPE_DBUS, TUBE_TYPE_STREAM, TUBE_STATE_LOCAL_PENDING,
        TUBE_STATE_REMOTE_PENDING, TUBE_STATE_OPEN,
        SOCKET_ADDRESS_TYPE_IPV4, SOCKET_ACCESS_CONTROL_LOCALHOST)

from account import connection_from_file

tube_type = {TUBE_TYPE_DBUS: "D-Bus",\
             TUBE_TYPE_STREAM: "stream"}

tube_state = {TUBE_STATE_LOCAL_PENDING : 'local pending',\
              TUBE_STATE_REMOTE_PENDING : 'remote pending',\
              TUBE_STATE_OPEN : 'open'}

SERVICE = 'rfb'

loop = None

class StreamTubeClient:
    def __init__(self, account_file):
        self.conn = connection_from_file(account_file)

        self.conn[CONN_INTERFACE].connect_to_signal('StatusChanged',
            self.status_changed_cb)
        self.conn[CONN_INTERFACE].connect_to_signal("NewChannel",
                self.new_channel_cb)

    def run(self):
        self.conn[CONN_INTERFACE].Connect()

        loop = gobject.MainLoop()
        try:
            loop.run()
        finally:
            try:
                self.conn[CONN_INTERFACE].Disconnect()
            except:
                pass

    def status_changed_cb(self, state, reason):
        if state == CONNECTION_STATUS_CONNECTING:
            print 'connecting'
        elif state == CONNECTION_STATUS_CONNECTED:
            print 'connected'
            self.connected_cb()
        elif state == CONNECTION_STATUS_DISCONNECTED:
            print 'disconnected'
            loop.quit()

    def connected_cb(self):
        self.self_handle = self.conn[CONN_INTERFACE].GetSelfHandle()

    def new_channel_cb(self, object_path, channel_type, handle_type, handle,
        suppress_handler):
      if channel_type == CHANNEL_TYPE_TUBES:
            self.channel_tubes = Channel(self.conn.dbus_proxy.bus_name,
                    object_path)

            self.channel_tubes[CHANNEL_TYPE_TUBES].connect_to_signal(
                    "TubeStateChanged", self.tube_state_changed_cb)
            self.channel_tubes[CHANNEL_TYPE_TUBES].connect_to_signal(
                    "NewTube", self.new_tube_cb)
            self.channel_tubes[CHANNEL_TYPE_TUBES].connect_to_signal(
                    "TubeClosed", self.tube_closed_cb)
            self.channel_tubes[CHANNEL_TYPE_TUBES].connect_to_signal(
                   "StreamTubeNewConnection",
                   self.stream_tube_new_connection_cb)

            for tube in self.channel_tubes[CHANNEL_TYPE_TUBES].ListTubes():
                id, initiator, type, service, params, state = (tube[0],
                        tube[1], tube[2], tube[3], tube[4], tube[5])
                self.new_tube_cb(id, initiator, type, service, params, state)

    def new_tube_cb(self, id, initiator, type, service, params, state):
        initiator_id = self.conn[CONN_INTERFACE].InspectHandles(
                CONNECTION_HANDLE_TYPE_CONTACT, [initiator])[0]

        print "new %s tube (%d) offered by %s, service: %s, state: %s" % (
                tube_type[type], id, initiator_id, service, tube_state[state])

        if state == TUBE_STATE_OPEN:
            self.tube_opened(id)

    def tube_opened(self, id):
        pass

    def tube_state_changed_cb(self, id, state):
        if state == TUBE_STATE_OPEN:
            self.tube_opened(id)

    def tube_closed_cb(self, id):
        print "tube closed", id

    def stream_tube_new_connection_cb(self, id, handle):
        print "new socket connection on tube %u from %s" % (id,
               self.conn[CONN_INTERFACE].InspectHandles(
                   CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0])

class StreamTubeVncInitiatorClient(StreamTubeClient):
    def __init__(self, account_file, contact_id):
        StreamTubeClient.__init__(self, account_file)
        self.contact_id = contact_id

    def connected_cb(self):
        StreamTubeClient.connected_cb(self)

        print "connected, offering VNC tube"

        # get a handle for our contact
        handle = self.conn[CONN_INTERFACE].RequestHandles(
                CONNECTION_HANDLE_TYPE_CONTACT, [self.contact_id])[0]

        # request a tube channel with them
        chan_path = self.conn[CONN_INTERFACE].RequestChannel(
            CHANNEL_TYPE_TUBES, CONNECTION_HANDLE_TYPE_CONTACT,
            handle, True)
        self.channel_tubes = Channel(self.conn.dbus_proxy.bus_name, chan_path)

        # offer the tube to them
        id = self.channel_tubes[CHANNEL_TYPE_TUBES].OfferStreamTube(SERVICE,
                {}, SOCKET_ADDRESS_TYPE_IPV4, ("127.0.0.1", dbus.UInt16(5900)),
                SOCKET_ACCESS_CONTROL_LOCALHOST, "")

class StreamTubeVncJoinerClient(StreamTubeClient):
    def __init__(self, account_file):
        StreamTubeClient.__init__(self, account_file)

        self.tube_accepted = False

    def new_tube_cb(self, id, initiator, type, service, params, state):
        StreamTubeClient.new_tube_cb(self, id, initiator, type, service, params, state)

        if state == TUBE_STATE_LOCAL_PENDING and service == SERVICE and\
                not self.tube_accepted:
            print "accepting tube", id
            self.tube_accepted = True
            self.channel_tubes[CHANNEL_TYPE_TUBES].AcceptStreamTube(id,
                    SOCKET_ADDRESS_TYPE_IPV4,
                    SOCKET_ACCESS_CONTROL_LOCALHOST, "")

    def tube_opened(self, id):
        StreamTubeClient.tube_opened(self, id)

        address_type, address = self.channel_tubes[CHANNEL_TYPE_TUBES].GetStreamTubeSocketAddress(
                id)
        # oops, found a bug :)
        #assert address_type == SOCKET_ADDRESS_TYPE_IPV4

        host, port = address
        print "tube opened at %s:%u, launching vinagre" % (host, port)

        argv = ["/usr/bin/vinagre", "%s:%u" % (str(host), port)]
        gobject.spawn_async(argv)

    def connected_cb(self):
        StreamTubeClient.connected_cb(self)

        print "waiting for a tube offer"

if __name__ == '__main__':
    args = sys.argv[1:]

    if len(args) == 1:
        client = StreamTubeVncJoinerClient(args[0])
    elif len(args) == 2:
        client = StreamTubeVncInitiatorClient(args[0], args[1])
    else:
        sys.exit(1)

    client.run()
