#!/usr/bin/env python

# Jacob Joseph
# 16 Feb 2010

# color space helper for LED lighting.  Primarily to slew between
# colors by different schemes

import colorsys
import math
from numpy import arange, array

def slew_timebase( steps, mode='linear', atan_inflection=10):
    """Return an array of step fractions from (0,...,1].  mode is one
of 'linear','atan'"""

    if mode == 'linear':
        return [ float(i) / steps for i in range(1,steps+1)]
    elif mode == 'atan':
        stepsize = 1.0 / steps
        
        maxtan = math.atan( 0.5 * atan_inflection)
        
        return [ math.atan( a * atan_inflection) / maxtan / 2 + 0.5
                 for a in arange( -0.5 + stepsize,
                                  0.5 + stepsize, stepsize)]
    else:
        assert False, "Unimplemented mode: %s" % mode


class ledColor(object):
    """Store a color.  Retain RGB and HSV values, so as to
non-destructively change brightness."""
    
    __rgb = None
    __hsv = None
    
    def __init__(self, rgb=None, hsv=None, hsl=None):
        """Specify rgb, hsl, or hsv as a tuple of 3 floats"""

        if rgb is not None:
            self.set_rgb( rgb)
        #elif hsl is not None:
        #    self.set_hsl(hsl)
        elif hsv is not None:
            self.set_hsv(hsv)
        else:
            self.set_rgb( (0.1,0.1,0.1))

    def __repr__(self):
        s = "<ledColor HSV: (%0.4f, %0.4f, %0.4f), RGB: (%0.4f, %0.4f, %0.4f)>" % (
            self.__hsv + self.__rgb)
        return s

    def __str__(self):
        return self.__repr__()

    def get_rgb(self):
        return self.__rgb

    def set_rgb( self, *tup):
        if len(tup) == 1: tup = tup[0]

        tup = tuple(tup)
        assert len(tup) == 3, "Tuple of 3 floats expected.  Got '%s'" % tup

        self.__rgb = tup
        self.__hsv = colorsys.rgb_to_hsv( *tup)
    rgb = property(fget=get_rgb, fset=set_rgb)

    def get_hsv(self):
        return self.__hsv

    def set_hsv( self, *tup):
        if len(tup) == 1: tup = tup[0]

        tup = tuple(tup)
        assert len(tup) == 3, "Tuple of 3 floats expected.  Got '%s'" % tup

        self.__hsv = tup
        self.__rgb = colorsys.hsv_to_rgb( float(tup[0]) / 360, tup[1], tup[2])
    hsv = property(fget=get_hsv, fset=set_hsv)

    def set_value(self, level):
        """level is 0 (darken completely) to 1 (full itensity)"""

        self.set_hsv( self.__hsv[0], self.__hsv[1], level)

    def set_saturation(self, level):
        """level is 0 (desaturate completely) to 1 (saturate
        completely)"""
        self.set_hsv( self.__hsv[0], level, self.__hsv[1])

    def set_hue(self, level):
        self.set_hsv( level, *self.__hsv[1:])

    def slew(self, rgb=None, hsv=None, max_steps=100, mode='atan',
             colorspace='hsv'):

        """Sweep to a target color, yielding ourself at as many as
max_steps intermediates by the shortest distance through a given
colorspace.  mode is 'linear' or 'atan'."""

        if rgb is not None:
            new = ledColor( rgb = rgb)
        elif hsv is not None:
            new = ledColor( hsv = hsv)
        else:
            assert False, "Color not specified"

        steps = max_steps
        
        timefactor = slew_timebase( steps, mode)

        #print "slew", self, rgb, hsv, new

        if colorspace == 'hsv':

            cur = self.__hsv
            diff = array(new.hsv) - cur

            for f in timefactor:
                self.set_hsv( f * diff + cur)
                yield self
                
        elif colorspace == 'rgb':
            cur = self.__rgb
            diff = array(new.rgb) - cur
            #print "diff", diff
            
            for f in timefactor:
                self.set_rgb( f * diff + cur)
                #print "yielding", self
                yield self
        else:
            assert False, "Unimplemented colorspace: %s" % colorspace
        
        
