#!/usr/bin/env python

from optparse import OptionParser, IndentedHelpFormatter
from blinkstick import blinkstick
import textwrap
import sys
import psutil
import websocket
import json

class IndentedHelpFormatterWithNL(IndentedHelpFormatter):
    def format_description(self, description):
        if not description: return ""

        desc_width = self.width - self.current_indent
        indent = " "*self.current_indent
        # the above is still the same
        bits = description.split('\n')
        formatted_bits = [
            textwrap.fill(bit,
                desc_width,
                initial_indent=indent,
                subsequent_indent=indent)
            for bit in bits]
        result = "\n".join(formatted_bits) + "\n"
        return result

    def format_option(self, option):
        # The help for each option consists of two parts:
        #   * the opt strings and metavars
        #   eg. ("-x", or "-fFILENAME, --file=FILENAME")
        #   * the user-supplied help string
        #   eg. ("turn on expert mode", "read data from FILENAME")
        #
        # If possible, we write both of these on the same line:
        #   -x    turn on expert mode
        #
        # But if the opt string list is too long, we put the help
        # string on a second line, indented to the same column it would
        # start in if it fit on the first line.
        #   -fFILENAME, --file=FILENAME
        #       read data from FILENAME
        result = []
        opts = self.option_strings[option]
        opt_width = self.help_position - self.current_indent - 2

        if len(opts) > opt_width:
            opts = "%*s%s\n" % (self.current_indent, "", opts)
            indent_first = self.help_position
        else: # start help on same line as opts
            opts = "%*s%-*s  " % (self.current_indent, "", opt_width, opts)
            indent_first = 0

        result.append(opts)

        if option.help:
            help_text = self.expand_default(option)
            # Everything is the same up through here
            help_lines = []
            for para in help_text.split("\n"):
                help_lines.extend(textwrap.wrap(para, self.help_width))
            # Everything is the same after here
            result.append("%*s%s\n" % (
                indent_first, "", help_lines[0]))
            result.extend(["%*s%s\n" % (self.help_position, "", line)
            for line in help_lines[1:]])
        elif opts[-1] != "\n":
            result.append("\n")
        return "".join(result)

def print_info(stick):
    print "Found device:"
    print "    Manufacturer:  " + stick.get_manufacturer()
    print "    Description:   " + stick.get_description()
    print "    Serial:        " + stick.get_serial()
    print "    Current Color: " + stick.get_color_string()
    print "    Info Block 1:  " + stick.get_info_block1()
    print "    Info Block 2:  " + stick.get_info_block2()

def show_cpu_usage(stick):
    print "Displaying CPU usage (Green = 0%, Amber = 50%, Red = 100%)"
    print "Press Ctrl+C to exit"

    while True:
        cpu = psutil.cpu_percent(interval=1)

        intensity = int(255 * cpu / 100)

        stick.set_color(intensity, 255 - intensity, 0)

def on_message(ws, message):
    global client_id
    global sticks
    global options

    #Uncomment this for debugging purposes
    if options.verbose:
        print message

    m = json.loads(message)

    if m[0]['channel'] == '/meta/connect':
        ws.send(json.dumps(
            {'channel': '/meta/connect',
             'clientId': client_id,
             'connectionType': 'websocket'}))
        return

    elif m[0]['channel'] == '/meta/handshake':
        client_id = m[0]['clientId']

        print "Acquired clientId: " + client_id

        ws.send(json.dumps(
            {'channel': '/meta/subscribe',
             'clientId': client_id,
             'subscription': '/devices/' + options.access_code}))
        return

    elif m[0]['channel'] == '/devices/' + options.access_code:
        if 'color' in m[0]["data"]:
            print "Received color: " + m[0]["data"]["color"]

            sticks[0].set_color(hex=m[0]["data"]["color"])
        elif 'status' in m[0]["data"] and m[0]["data"]['status'] == "off":
            print "Turn off"
            sticks[0].turn_off()

    elif m[0]['channel'] == '/meta/subscribe':
        if m[0]['successful']:
            print "Subscribed to device. Waiting for color message..."
        else:
            print "Subscription to the device failed. Please check the AccessCode value."

    #Reconnect again and wait for further messages
    ws.send(json.dumps(
        {'channel': '/meta/connect',
         'clientId': client_id,
         'connectionType': 'websocket'}))


def on_error(ws, error):
    print error


def on_close(ws):
    print "### closed ###"


def on_open(ws):
    ws.send(json.dumps(
        {'channel': '/meta/handshake',
         'version': '1.0',
         'supportedConnectionTypes': ['long-polling', 'websocket']}))


def main():
    global options
    global sticks

    parser = OptionParser(
                formatter=IndentedHelpFormatterWithNL()
                )

    parser.add_option("-i", "--info",
                      action="store_true", dest="info",
                      help="display BlinkStick info")

    parser.add_option("-s", "--serial", 
                      dest="serial",
                      help="select device by serial number. If unspecified, action will be performed on all BlinkSticks.")

    parser.add_option("--set-color", 
                      dest="color",
                      help="set the color for the device. The value can either be a named color, hex value, 'random' or 'off'.\n\n"
                        "CSS color names are defined http://www.w3.org/TR/css3-color/ e.g. red, green, blue."
                        "Specify color using hexadecimal color value e.g. '#FF3366'")

    parser.add_option("--set-infoblock1", 
                      dest="infoblock1",
                      help="set the first info block for the device.")

    parser.add_option("--set-infoblock2", 
                      dest="infoblock2",
                      help="set the second info block for the device.")

    parser.add_option("--cpu-usage", 
                      dest="cpu_usage", action="store_true", 
                      help="Use BlinkStick to display CPU usage.")

    parser.add_option("--connect", 
                      dest="access_code",
                      help="Connect to blinkstick.com and control the device remotely.")

    parser.add_option("-v", "--verbose",
                      action="store_true", dest="verbose",
                      help="Display debug output")

    parser.add_option("--add-udev-rule",
                      action="store_true", dest="udev",
                      help="Add udev rule to access BlinkSticks without root permissions. Must be run as root.")

    (options, args) = parser.parse_args()

    print "BlinkStick control script"
    print "(c) Agile Innovative Ltd 2013"
    print ""

    if options.serial is None:
        sticks = blinkstick.find_all()
    else:
        sticks = [blinkstick.find_by_serial(options.serial)]

        if len(sticks) == 0:
            print "BlinkStick with serial number " + options.device + " not found..."
            return 64

    #Global action
    if options.udev:

        try:
            filename = "/etc/udev/rules.d/85-blinkstick.rules"
            file = open(filename, 'w')
            file.write('SUBSYSTEM=="usb", ATTR{idVendor}=="20a0", ATTR{idProduct}=="41e5", MODE:="0666"')
            file.close()

            print "Rule added to " + filename
        except IOError as e:
            print str(e)
            print "Make sure you run this script as root: sudo blinkstick --add-udev-rule"
            return 64

        print "Reboot your computer for changes to take effect"
        return 0

    #Actions here work only on the first BlinkStick found
    if options.cpu_usage:
        show_cpu_usage(sticks[0])
    elif options.access_code:
        access_code = options.access_code
        # Set this to True for debugging purposes
        websocket.enableTrace(options.verbose)
        ws = websocket.WebSocketApp("ws://live.blinkstick.com:9292/faye",
                                    on_message=on_message,
                                    on_error=on_error,
                                    on_close=on_close)
        ws.on_open = on_open

        ws.run_forever()

    #Actions here work on all BlinkSticks
    for stick in sticks:
        if options.infoblock1:
            stick.set_info_block1(options.infoblock1)

        if options.infoblock2:
            stick.set_info_block2(options.infoblock2)
    
        if options.info:
            print_info(stick)
        elif options.color:
            if options.color.startswith('#'):
                stick.set_color(hex=options.color)
            elif options.color == "random":
                stick.set_random_color()
            elif options.color == "off":
                stick.turn_off()
            else:
                stick.set_color(name=options.color)
        else:
            parser.print_help()

    return 0

if __name__ == "__main__":
    sys.exit(main())
