#!/usr/bin/env python
import os, sys, socket, select, time, cairo, gtk, glib

from optparse import OptionParser
parser = OptionParser()
parser.add_option("--server", dest="server", default="leds",
                  help="k74 server host name or ip address")
parser.add_option("--port-watch", type="int", dest="pw", default="7299",
                  help="base port number for k74 watch")
parser.add_option("--port-set", type="int", dest="ps", default="7200",
                  help="base port number for k74 set requests")
parser.add_option("--port-clear", type="int", dest="pc", default="7300",
                  help="base port number for k74 clear requests")
parser.add_option("--label-font-size", type="int", dest="lfs", default="36",
                  help="label font size")
parser.add_option("--read-only",
                  action="store_true", dest="ro", default=False,
                  help="perform no state changes here")
parser.add_option("--title",
                  dest="title", default="k74",
                  help="window title name")
parser.add_option("--horizontal",
                  action="store_true", dest="horizontal", default=False,
                  help="layout horizontal")
(opt, args) = parser.parse_args()

class Client:
    def __init__(self, port, reconnect=False):
        self.socket = None
        self.port = port
        self.reconnect = reconnect
        self.status = 0
        self.connect()

    def connect(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.buffer = ''
        # try connecting a few times
        for x in range(20):
            try:
                self.socket.connect((opt.server, self.port))
                return
            except socket.error, (errno, strerror):
                print strerror
                time.sleep(1)
            except socket.gaierror, (errno, strerror):
                print strerror
                time.sleep(2)
        # try a final time and don't handle an exception
        self.socket.connect((opt.server, self.port))

    def close(self):
        self.socket.close()
        self.socket = None
        if self.reconnect:
            self.connect()

    def recv(self):
        while 1:
            is_readable = [self.socket]
            is_writable = []
            is_error = [self.socket]
            r, w, e = select.select(is_readable, is_writable, is_error, 0.1)
            if e:
                self.close()
                return False
            if not r: return False
            try:
                byte = self.socket.recv(1)
                if len(byte) == 1:
                    self.recv_byte(byte)
                    return True
                else:
                    self.recv_eof()
                    return False
            except:
                self.recv_eof()
                return False

    def recv_byte(self, byte):
        if byte == '\n':
            self.status = int(self.buffer)
            self.buffer = ''
        else:
            self.buffer = self.buffer + byte

    def recv_eof(self):
        self.close()

def led_set(led):
    if opt.ro: return
    request = Client(opt.ps + led)
    request.close()

def led_clear(led):
    if opt.ro: return
    request = Client(opt.pc + led)
    request.close()

def draw(cr, w, h, k74, x):
    pi = 3.1415926
    xc, yc = w/2, h/2

    # black fill the drawing area
    cr.set_source_rgba(0, 0, 0, 1)
    cr.rectangle(0, 0, w, h)
    cr.fill()

    # draw filled circle of required colour
    cr.set_source_rgba(0.5, 0.5, 0.5, 1)
    cr.set_line_width(1.0)

    if k74:
        cr.set_source_rgba(0.75, 0.15, 0.15, 1)
    else:
        cr.set_source_rgba(0.05, 0.35, 0.05, 1)

    cr.arc(xc, yc, h*0.4, 0, 2*pi)
    cr.fill()

    # draw label text in centre
    label = str(x+1)
    cr.set_source_rgba(0, 0, 0, 0.25)
    cr.select_font_face("Georgia", cairo.FONT_SLANT_NORMAL,
                        cairo.FONT_WEIGHT_BOLD)
    cr.set_font_size(opt.lfs)
    xb, yb, tw, th = cr.text_extents(label)[:4]
    cr.move_to(xc - tw / 2 - xb, yc - th / 2 - yb)
    cr.show_text(label)

def expose(da, event, client, x):
    cr = da.window.cairo_create()
    _, _, width, height = da.allocation
    da.k74 = client.status & (1 << x)
    draw(cr, width, height, da.k74, x)
    return False

def thump(source, cb_condition, client, da):
    old = client.status
    if client.recv():
        if client.status != old:
            for x in range(8):
                if da[x].k74 != client.status & (1 << x):
                    expose(da[x], None, client, x)
    return True

def mb(da, event, client, x):
    if event.button == 1: led_set(x+1)
    if event.button == 3: led_clear(x+1)

def kb(win, event):
    keys = { gtk.keysyms._1: lambda: led_clear(1),
             gtk.keysyms._2: lambda: led_clear(2),
             gtk.keysyms._3: lambda: led_clear(3),
             gtk.keysyms._4: lambda: led_clear(4),
             gtk.keysyms._5: lambda: led_clear(5),
             gtk.keysyms._6: lambda: led_clear(6),
             gtk.keysyms._7: lambda: led_clear(7),
             gtk.keysyms._8: lambda: led_clear(8),

             gtk.keysyms.exclam:      lambda: led_set(1),
             gtk.keysyms.at:          lambda: led_set(2),
             gtk.keysyms.numbersign:  lambda: led_set(3),
             gtk.keysyms.dollar:      lambda: led_set(4),
             gtk.keysyms.percent:     lambda: led_set(5),
             gtk.keysyms.asciicircum: lambda: led_set(6),
             gtk.keysyms.ampersand:   lambda: led_set(7),
             gtk.keysyms.asterisk:    lambda: led_set(8),

             gtk.keysyms.q: lambda: sys.exit(0),
             }
    if event.keyval in keys:
        keys[event.keyval]()

def main():
    client = Client(opt.pw, reconnect=True)
    client.recv()

    win = gtk.Window()
    win.connect('destroy', gtk.main_quit)
    win.connect('key_press_event', kb)
    win.set_title(opt.title)
    if opt.horizontal:
        win.set_default_size(800, 100)
        win.set_geometry_hints(min_aspect=8, max_aspect=8)
    else:
        win.set_default_size(100, 800)
        win.set_geometry_hints(min_aspect=-8, max_aspect=-8)

    da = {}
    if opt.horizontal:
        ab = gtk.HBox()
    else:
        ab = gtk.VBox()

    for x in range(8):
        da[x] = gtk.DrawingArea()
        da[x].set_events(gtk.gdk.BUTTON_PRESS_MASK)
        da[x].connect('expose_event', expose, client, x)
        da[x].connect('button_press_event', mb, client, x)
        da[x].k74 = None
        ab.add(da[x])
    win.add(ab)
    win.show_all()
    glib.io_add_watch(client.socket, glib.IO_IN, thump, client, da)
    gtk.main()

if __name__ == '__main__':
    main()
