import colorsys
import yaml

from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, CustomJS, Slider
from bokeh.plotting import figure, output_file, show, curdoc
from bokeh.themes import Theme

# for plot 2: create colour spectrum of resolution N and brightness I, return as list of decimal RGB value tuples
def generate_color_range(N, I):
    HSV_tuples = [ (x*1.0/N, 0.5, I) for x in range(N) ]
    RGB_tuples = map(lambda x: colorsys.hsv_to_rgb(*x), HSV_tuples)
    for_conversion = []
    for RGB_tuple in RGB_tuples:
        for_conversion.append((int(RGB_tuple[0]*255), int(RGB_tuple[1]*255), int(RGB_tuple[2]*255)))
    hex_colors = [ rgb_to_hex(RGB_tuple) for RGB_tuple in for_conversion ]
    return hex_colors, for_conversion

# convert RGB tuple to hexadecimal code
def rgb_to_hex(rgb):
    return '#%02x%02x%02x' % rgb

# convert hexadecimal to RGB tuple
def hex_to_dec(hex):
    red = ''.join(hex.strip('#')[0:2])
    green = ''.join(hex.strip('#')[2:4])
    blue = ''.join(hex.strip('#')[4:6])
    return (int(red, 16), int(green, 16), int(blue,16))

# plot 1: create a color block with RGB values adjusted with sliders

# initialise a white block for the first plot
hex_color = rgb_to_hex((255, 255, 255))

# initialise the text color as black. This will be switched to white if the block color gets dark enough
text_color = '#000000'

# create a data source to enable refreshing of fill & text color
source = ColumnDataSource(data=dict(color=[hex_color], text_color=[text_color]))

# create first plot, as a rect() glyph and centered text label, with fill and text color taken from source
p1 = figure(x_range=(-8, 8), y_range=(-4, 4),
            plot_width=600, plot_height=300,
            title='move sliders to change', tools='')

p1.rect(0, 0, width=18, height=10, fill_color='color',
        line_color = 'black', source=source)

p1.text(0, 0, text='color', text_color='text_color',
        alpha=0.6667, text_font_size='36pt', text_baseline='middle',
        text_align='center', source=source)

# the callback function to update the color of the block and associated label text
# NOTE: the JS functions for converting RGB to hex are taken from the excellent answer
# by Tim Down at http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
callback = CustomJS(args=dict(source=source), code="""
    function componentToHex(c) {
        var hex = c.toString(16);
        return hex.length == 1 ? "0" + hex : hex;
    }
    function rgbToHex(r, g, b) {
        return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
    }
    function toInt(v) {
       return v | 0;
    }
    var data = source.data;
    var color = data['color'];
    var text_color = data['text_color'];
    var R = toInt(red_slider.value);
    var G = toInt(green_slider.value);
    var B = toInt(blue_slider.value);
    color[0] = rgbToHex(R, G, B);
    text_color[0] = '#ffffff';
    if ((R > 127) || (G > 127) || (B > 127)) {
        text_color[0] = '#000000';
    }
    source.change.emit();
""")

# create slider tool objects with a callback to control the RGB levels for first plot
SLIDER_ARGS = dict(start=0, end=255, value=255, step=1, callback=callback)

red_slider = Slider(title="R", **SLIDER_ARGS)
callback.args['red_slider'] = red_slider

green_slider = Slider(title="G", **SLIDER_ARGS)
callback.args['green_slider'] = green_slider

blue_slider = Slider(title="B", **SLIDER_ARGS)
callback.args['blue_slider'] = blue_slider

# plot 2: create a color spectrum with a hover-over tool to inspect hex codes

brightness = 0.8 # change to have brighter/darker colors
crx = list(range(1,1001)) # the resolution is 1000 colors
cry = [ 5 for i in range(len(crx)) ]
crcolor, crRGBs = generate_color_range(1000,brightness) # produce spectrum

# make data source object to allow information to be displayed by hover tool
crsource = ColumnDataSource(data=dict(x=crx, y=cry, crcolor=crcolor, RGBs=crRGBs))

# create second plot
p2 = figure(x_range=(0,1000), y_range=(0,10),
            plot_width=600, plot_height=150,
            tools='hover', title='hover over color')

color_range1 = p2.rect(x='x', y='y', width=1, height=10,
                       color='crcolor', source=crsource)

# set up hover tool to show color hex code and sample swatch
p2.hover.tooltips = [
    ('color', '$color[hex, rgb, swatch]:crcolor'),
    ('RGB levels', '@RGBs')
]

# theme everything for a cleaner look
curdoc().theme = Theme(json=yaml.load("""
attrs:
    Plot:
        toolbar_location: null
    Grid:
        grid_line_color: null
    Axis:
        axis_line_color: null
        major_label_text_color: null
        major_tick_line_color: null
        minor_tick_line_color: null
"""))

layout = row(
    column(red_slider, green_slider, blue_slider),
    column(p1, p2)
)

output_file("color_sliders.html", title="color_sliders.py example")

show(layout)
