# -*- coding: utf-8 -*-
# 
# The LIRC Plugin for elisa
# 
# Copyright (C) 2008 by Benjamin Kampmann <big.master.ben@web.de>
# 
# This program 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, either version 3 of the License, or
# (at your option) any later version.
# 
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
# 
# Author: Benjamin Kampmann <big.master.ben@web.de>

if __name__ == '__main__':
    # import and install the glib2reactor is the first thing today in case we
    # execute directly
    from twisted.internet import glib2reactor
    glib2reactor.install()

from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import Protocol, ClientFactory
from twisted.internet.unix import Client

from elisa.core.utils import locale_helper
from elisa.core.components.input_provider import PushInputProvider
from elisa.core.input_event import InputEvent, EventValue, \
        EventSource, EventType

from twisted.internet import reactor, defer

import os
import time
import pkg_resources

class NoMappingsFound(Exception):
    """
    Raised when the mappings file is not found or if it is empty.
    """

class CallbackProtocol(LineReceiver):
    delimiter = "\n"

    def __init__(self, callback):
        self.callback = callback

    def lineReceived(self, line):
        args = line.split()
        self.callback(*args)

class CallbackConnector(ClientFactory):

    def __init__(self, callback, lostback = None):
        self.callback = callback
        self.lostback = lostback

    def buildProtocol(self, addr):
        return CallbackProtocol(self.callback)

    def connectionLost(self, *args):
        if self.lostback:
            self.lostback()

    connectionFailed = connectionLost

class LircInput(PushInputProvider):
    default_config = {'input_map' : 'streamzap.map',
                      'repeat_delay': '0.6',
                      'device' : '/dev/lircd'}

    config_doc = {'input_map' : 'Path to the file containing the lirc mapping',
                  'repeat_delay': 'Time in seconds to wait before taking into' \
                                  'account a first repeated input event.',
                  'device' : 'the lirc deamon device'}

    def __init__(self):
        super(LircInput, self).__init__()
        self.mappings = {}
        self._client = None
        self._first_key_time = None
        self._first_key_value = None

    def initialize(self):
        dfr = super(LircInput, self).initialize()

        def initialized(result):
            self.repeat_delay = float(self.config.get('repeat_delay', '0.6'))
            input_map_file = self.config.get('input_map')
            filename = os.path.basename(input_map_file)
            data = ""
            if not os.path.isabs(input_map_file):
                # let's try to use lirc config shipped with the plugin
                path = "map_files/%s" % filename
                if pkg_resources.resource_exists(self.__module__, path):
                    lirc_config = pkg_resources.resource_filename(self.__module__,
                                                                  path)
                    lirc_config = lirc_config.decode(locale_helper.system_encoding())
                    self.info("Using %s lirc map file", lirc_config)
                    data = open(lirc_config).read()
                else:
                    msg = "Given InputMap '%s' not found" % input_map_file
                    return defer.fail(NoMappingsFound(msg))
            else:
                # the user configured an absolute lirc config file location
                self.info("Using %s lirc map file", input_map_file)
                try:
                    data = open(input_map_file).read()
                except IOError:
                    msg = "Given InputMap '%s' not found" % input_map_file
                    return defer.fail(NoMappingsFound(msg))

            if data:
                self._parse_to_mapping(data)

            if not len(self.mappings):
                msg = "No Mappings found in %s" % input_map_file
                return defer.fail(NoMappingsFound(msg))

            self._setup_client()
            return self

        dfr.addCallback(initialized)
        return dfr

    def _setup_client(self):
        connector = CallbackConnector(self._got_event)
        self._client = Client(self.config.get('device'), connector,
                              reactor=reactor)


    def _got_event(self, hex_key, repeat, key_value, remote):

        now = time.time()
        if key_value == self._first_key_value:
            # skip first repeat for a certain amount of time
            if (now - self._first_key_time) < self.repeat_delay:
                return
        else:
            self._first_key_time = now
            self._first_key_value = key_value

        if '*' in self.mappings:
            # ignore remote name provided by LIRC and take first remote registered in the
            # mappings
            mappings = self.mappings[self.mappings.keys()[0]]
        elif remote not in self.mappings:
            self.debug("Unknown remote %s", remote)
            return
        else:
            mappings = self.mappings[remote]

        if hex_key in mappings:
            value = mappings[hex_key]
        elif key_value in mappings:
            value = mappings[key_value]
        else:
            self.debug("Can't find '%s' nor '%s' in mappings for '%s'",
                       hex_key, key_value, remote)
            return

        evt = InputEvent(EventSource.REMOTE, EventType.OTHER, value)
        self.input_manager.process_event(evt)

    def _parse_to_mapping(self, data):
        """
        parse the lirc input mappings
        """
        for line in data.split('\n'):
            line = line.strip()
            splitted = line.split()
            if line.startswith('#') or len(splitted) != 3:
                self.info("Skipping %s", line)
                continue

            remote, key, elisa_key = splitted

            if not hasattr(EventValue, elisa_key):
                self.warning("Don't know key '%s'.", elisa_key)
                continue

            event_key = getattr(EventValue, elisa_key)

            if not remote in self.mappings:
                self.mappings[remote] = {}

            self.debug("Adding %s as mapped to %s for the remote %s" %
                    (event_key, key, remote))

            self.mappings[remote][key] = event_key

if __name__ == '__main__':

    res = None

    def setup_done(resource):
        global res
        res = resource
        print """

        You are now in the interactive tests. You have the DaapDebug object
        at *daap_debug* and you can play around with its get_* methods. It is
        holding the connection to your server. The login already worked :) .

        """

    def setup():
        config = LircInput.default_config
        dfr = LircInput.create(config)
        dfr.addCallback(setup_done)

    reactor.callWhenRunning(setup)
    reactor.simulate()
    reactor.startRunning()
    import IPython
    IPython.Shell.IPShellGTK([], locals()).mainloop()

    

