# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" in the root directory of this distribution package
# for details on that license.

__maintainer__ = 'Florian Boucault <florian@fluendo.com>'

from elisa.base_components.input_provider import PushInputProvider
from elisa.core.input_event import *

import socket, errno
import bluetooth as _bt

try:
    import fcntl
except ImportError:
    fcntl = None

from twisted.internet.protocol import Protocol, ServerFactory
from twisted.internet import reactor, tcp

class NormalizedBluetoothSocket(_bt.BluetoothSocket):
    """Wrapper class for the bluez sockets to make them behave more
    like the objects they pretend to be. Plus, it works around some
    bugs."""

    def recv(self, *args, **kw):
        print "Anything"
        try:
            return _bt.BluetoothSocket.recv(self, *args, **kw)
        except _bt.BluetoothError, bterr:
            # bug in python-bluez, don't ask...
            import code
            code.interact(local=locals())
            if bterr.args == ("(104, 'Connection reset by peer')",):
                raise socket.error(errno.ECONNRESET,
                                   'Connection reset by peer')
            raise socket.error(*(bterr.args))

    def accept(self, *args, **kw):
        try:
            return _bt.BluetoothSocket.accept(self, *args, **kw)
        except _bt.BluetoothError, bterr:
            # bug in python-bluez, don't ask...
            if bterr.args == ("(11, 'Resource temporarily unavailable')",):
                raise socket.error(errno.EWOULDBLOCK,
                                   'Resource temporarily unavailable')
            raise socket.error(*(bterr.args))


class ElisaBTPort(tcp.Port):
    """An Elisa-specific Bluetooth Port class."""
    addressFamily = socket.AF_BLUETOOTH
    socketType = socket.SOCK_STREAM
    uuid = "94f39d29-7d6d-437d-973b-fba39e49d4ee"

    def createInternetSocket(self):
        #s = _bt.BluetoothSocket(_bt.RFCOMM)
        s = NormalizedBluetoothSocket(_bt.RFCOMM)
        s.setblocking(0)
        if fcntl and hasattr(fcntl, 'FD_CLOEXEC'):
            old = fcntl.fcntl(s.fileno(), fcntl.F_GETFD)
            fcntl.fcntl(s.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        return s

    def startReading(self):
        _bt.advertise_service(self.socket, "Elisa", service_id=self.uuid,
                              service_classes=[self.uuid,
                                               _bt.SERIAL_PORT_CLASS],
                              profiles=[_bt.SERIAL_PORT_PROFILE],
                              # protocols = [ OBEX_UUID ]
                              )
        #print ("Waiting for connection on RFCOMM channel %d" %
        #       self._realPortNumber)
        self.numberAccepts = 1
        tcp.Port.startReading(self)


class ElisaBTFactory(ServerFactory):
    def __init__(self, prot_instance):
        self._protocol_instance = prot_instance

    def buildProtocol(self, addr):
        p = self._protocol_instance
        # FIXME: possible circular references, add proper cleanup
        p.factory = self
        return p

class BluetoothInput(PushInputProvider, Protocol):
    action_map = {'14': EventAction.GO_LEFT,
                  '15': EventAction.GO_RIGHT,
                  '16': EventAction.GO_UP,
                  '17': EventAction.GO_DOWN,
                  '167': EventAction.OK,
                  '196': EventAction.MENU}

    type_map = {'1': EventType.KEY_DOWN,
                '2': EventType.KEY_UP}
#                '3': EventType.KEY_DOWN}

    def __init__(self):
        PushInputProvider.__init__(self)
        self._factory = ElisaBTFactory(self)
        self._port = None

    def _debug(self, *args, **kw):
        print '%r,%r' % (args, kw)

    def bind(self):
        self.debug("Listening on a ElisaBTPort...")
        self._port = reactor.listenWith(ElisaBTPort, 0, self._factory)

    def unbind(self):
        # FIXME: clean circular references properly...?
        if self._port:
            self._port.stopListening()

    def dataReceived(self, data):
        self.debug('Received: %r' % data)
        ev_data = data.split(':')[0:2]
        e = self.create_input_event(ev_data)
        if e and self.input_manager:
            self.input_manager.process_event(e, self.path)

    def connectionLost(self, reason):
        self.debug('Connection lost: %r' % reason)

    def create_input_event(self, data):
        if len(data) < 2 or data[1] not in self.type_map:
            return None

        if data[0] in self.action_map and \
            self.type_map[data[1]] == EventType.KEY_DOWN:

            event = InputEvent(EventSource.KEYBOARD,
                               EventType.OTHER,
                               self.action_map[data[0]])
            event.origin = self.origin
        else:
            event = None

        return event

if __name__ == '__main__':
    class FakeManager:
        up_ok = False
        down_ok = False

        def process_event(self, event, input_provider_path):
            print event

    r = BluetoothInput()
    m = FakeManager()
    r.input_manager = m
    r.bind()
    reactor.run()
    r.unbind()
