#!/usr/bin/env python

from optparse import OptionParser, IndentedHelpFormatter
from blinkstick import blinkstick
import textwrap
import sys
import time
import logging
logging.basicConfig()


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 format_usage(self, usage):
        return "BlinkStick control script %s\n(c) Agile Innovative Ltd 2013-2014\n\n%s" % (blinkstick.get_blinkstick_package_version(), IndentedHelpFormatter.format_usage(self, usage))


def print_info(stick):
    print("Found device:")
    print("    Manufacturer:  {0}").format(stick.get_manufacturer())
    print("    Description:   {0}").format(stick.get_description())
    print("    Serial:        {0}").format(stick.get_serial())
    print("    Current Color: {0}").format(stick.get_color(color_format="hex"))
    print("    Mode:          {0}").format(stick.get_mode())
    print("    Info Block 1:  {0}").format(stick.get_info_block1())
    print("    Info Block 2:  {0}").format(stick.get_info_block2())


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("--inverse",
                      action="store_true", dest="inverse",
                      help="control BlinkSticks in inverse mode")

    parser.add_option("--channel",
                      default=0, dest="channel",
                      help="Select channel")

    parser.add_option("--index",
                      default=0, dest="index",
                      help="Select index")

    parser.add_option("--limit",
                      default=100, dest="limit",
                      help="Limit the brightness of the color 0..100")

    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("--duration",
                      dest="duration",
                      default=1000,
                      help="Set duration of transition in milliseconds (use with --morph and --pulse).")

    parser.add_option("--delay",
                      dest="delay",
                      default=500,
                      help="Set time in milliseconds to light LED for (use with --blink).")

    parser.add_option("--repeats",
                      dest="repeats",
                      default=1,
                      help="Number of repetitions (use with --blink and --pulse).")

    parser.add_option("--blink",
                      dest="blink",
                      action='store_true',
                      help="Blink LED (requires --set-color, and optionally --delay)")

    parser.add_option("--pulse",
                      dest="pulse",
                      action='store_true',
                      help="Pulse LED (requires --set-color, and optionally --duration).")

    parser.add_option("--morph",
                      dest="morph",
                      action='store_true',
                      help="Morph to specified color (requires --set-color, and optionally --duration).")

    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("-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.")

    parser.add_option("--set-mode",
                      default=0, dest="mode",
                      help="Set mode for BlinkStick Pro. 0 - default, 1 - inverse, 2 - ws2812")

    (options, args) = parser.parse_args()

    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 {0}").format(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

    for stick in sticks:
        if options.inverse:
            stick.set_inverse(True)

        stick.set_max_rgb_value(int(float(options.limit) / 100.0 * 255))

        stick.set_error_reporting(False)

    #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.mode:
            if options.mode == "0" or options.mode == "1" or options.mode == "2" or options.mode == "3":
                stick.set_mode(int(options.mode))
            else:
                print("Error: Invalid mode parameter value")
        elif options.info:
            print_info(stick)
        elif options.color:
            # determine color
            args = {}
            if options.color.startswith('#'):
                args['hex'] = options.color
            elif options.color == "random":
                args['name'] = 'random'
            elif options.color == "off":
                args['hex'] = "#000000"
            else:
                args['name'] = options.color

            args['index'] = int(options.index)
            args['channel'] = int(options.channel)

            # handle blink/pulse/morph
            func = stick.set_color
            if options.blink:
                func = stick.blink
                args['delay'] = options.delay
                args['repeats'] = int(options.repeats)
            elif options.pulse:
                func = stick.pulse
                args['duration'] = options.duration
                args['repeats'] = int(options.repeats)
            elif options.morph:
                func = stick.morph
                args['duration'] = options.duration

            func(**args)


        else:
            parser.print_help()

    return 0


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