#!/usr/bin/env python2
import struct
import glob
import select
import subprocess
import pyinotify
import argparse
import time

KEYLENGTH = 20

def call(*args):
    return lambda : subprocess.Popen(args)


def controlvolume(cmd):
    def action():
        sinks = subprocess.Popen(['pactl', 'list', 'short', 'sinks'], stdout=subprocess.PIPE).stdout.read()
        bestsink = '0'
        state = 'default'
        for sink in sinks.splitlines():
            attribs = sink.split()
            if attribs[-1] == 'RUNNING':
                bestsink = attribs[0]
                state = 'RUNNING'
            elif attribs[-1] == 'IDLE' and state == 'DEFAULT':
                bestsink = attribs[0]
                state = 'IDLE'
        command = cmd % bestsink
        subprocess.Popen(command.split())
    return action

CMDS = {(165, 165): call('xlock', 'force'),
        (165, 163, 164, 164): call('pkill', 'i3lock'),
        (164,): call('musiccontrol', 'PlayPause'),
        (200,): call('musiccontrol', 'PlayPause'),
        (163,): call('musiccontrol', 'Next'),
        (113,): controlvolume('pactl set-sink-mute %s toggle'),
        (151,): controlvolume('pactl set-sink-mute %s toggle'),
        (114,): controlvolume('pactl set-sink-volume %s -5%%'),
        (115,): controlvolume('pactl set-sink-volume %s +5%%'),
        }


def search_pattern(keys):
    matches = []
    for pattern in sorted(CMDS.keys(), key=lambda x: len(x), reverse=True):
        patternlen = len(pattern)
        for idx in xrange(patternlen):
            start = -(patternlen - idx)
            end = -idx or None
            if pattern[:end] == tuple(keys[start:]):
                matches.append((abs(start), pattern))
                break
    matches.sort(key=lambda x: x[0], reverse=True)
    if matches:
        # return first match that is complete
        lmatch, match = matches[0]
        if lmatch == len(match):
            return match
    else:
        return None

def daemon():
    devices = dict()
    for devicepath in glob.glob("/dev/input/event*"):
        devices[devicepath] = open(devicepath)

    class Processor(pyinotify.ProcessEvent):
        def process_IN_CREATE(self, event):
            print 'IN', event
            if event.name.startswith('event'):
                try:
                    devices[event.pathname] = open(event.pathname)
                except IOError as e:
                    if e.errno == 13:
                        time.sleep(3)
                        self.process_IN_CREATE(event)

        def process_IN_DELETE(self, event):
            print 'DEL', event
            devices.pop(event.pathname, None)

    watcher = pyinotify.WatchManager()
    watcher.add_watch('/dev/input/', pyinotify.IN_DELETE | pyinotify.IN_CREATE)
    notifier = pyinotify.Notifier(watcher, Processor())

    format = "llHHi"

    KEY_TYPE = 1
    keys = [0] * KEYLENGTH

    s = struct.calcsize(format)

    while True:
        rlist, wlist, xlist = select.select(devices.values(), [], [], 5)
        if notifier.check_events(1):
            notifier.read_events()
            notifier.process_events()
        for device in rlist:
            try:
                data = device.read(s)
            except Exception, e:
                print e
                continue
            time1, time2, evtype, evcode, evvalue = struct.unpack(format, data)
            if evtype == KEY_TYPE:
                if evvalue == 1:
                    keys.append(evcode)
                    keys = keys[-KEYLENGTH:]
                    pattern = search_pattern(keys)
                    if pattern:
                        print "Found Media button", pattern
                        CMDS[pattern]()


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-k', '--key', type=int, default=None)
    options = parser.parse_args()
    if not options.key:
        daemon()
    else:
        cmd = CMDS.get(options.key)
        if cmd:
            cmd()


