/*
   Copyright 2010 Hanov Solutions Inc. All Rights Reserved

   steve.hanov@gmail.com
 */

function ConvertHsvRgb( h, s, v )
{
    if ( h < 0 ) { h += 360; }
    var hi = Math.floor( h / 60 ) % 6;
    var f = h / 60 - Math.floor( h / 60 );
    var p = v * (1 - s);
    var q = v * (1 - f * s);
    var t = v * (1 - ( 1 - f ) * s );
    var r, g, b;
    switch( hi ) {
    case 0: 
        r = v;
        g = t;
        b = p;
        break;
    case 1: 
        r = q;
        g = v;
        b = p;
        break;
    case 2: 
        r = p;
        g = v;
        b = t;
        break;
    case 3: 
        r = p;
        g = q;
        b = v;
        break;
    case 4: 
        r = t;
        g = p;
        b = v;
        break;
    case 5: 
        r = v;
        g = p;
        b = q;
        break;
    }

    return { r:r, g:g, b:b, a:1.0 };
}


function ConvertRgbHsv( r, g, b )
{
    var h, s, v;
    var max = Math.max( r, g, b );
    var min = Math.min( r, g, b );
    if ( max === min ) {
        h = 0;
    } else if ( max === r ) {
        h = ( 60 * (g - b) / ( max - min ) + 360 ) % 360;
    } else if ( max === g ) {
        h = 60 * ( b - r ) / ( max - min ) + 120;
    } else if ( max === b ) {
        h = 60 * ( r - g ) / ( max - min ) + 240;
    }

    if ( max === 0 ) {
        s = 0;
    } else {
        s = 1 - min / max;
    }

    v = max;

    return { h:h, s:s, v:v, a:1.0 };
}

var CssColours = {
    "aliceblue": "#f0f8ff",
    "antiquewhite": "#faebd7",
    "aqua": "#00ffff",
    "aquamarine":  "#7fffd4",
    "azure":  "#f0ffff",
    "beige":  "#f5f5dc",
    "bisque":  "#ffe4c4",
    "black":  "#000000",
    "blanchedalmond":  "#ffebcd",
    "blue":  "#0000ff",
    "blueviolet":  "#8a2be2",
    "brown":  "#a52a2a",
    "burlywood":  "#deb887",
    "cadetblue":  "#5f9ea0",
    "chartreuse":  "#7fff00",
    "chocolate":  "#d2691e",
    "coral":  "#ff7f50",
    "cornflowerblue":  "#6495ed",
    "cornsilk":  "#fff8dc",
    "crimson":  "#dc143c",
    "cyan":  "#00ffff",
    "darkblue":  "#00008b",
    "darkcyan":  "#008b8b",
    "darkgoldenrod":  "#b8860b",
    "darkgray":  "#a9a9a9",
    "darkgreen":  "#006400",
    "darkkhaki":  "#bdb76b",
    "darkmagenta":  "#8b008b",
    "darkolivegreen":  "#556b2f",
    "darkorange":  "#ff8c00",
    "darkorchid":  "#9932cc",
    "darkred":  "#8b0000",
    "darksalmon":  "#e9967a",
    "darkseagreen":  "#8fbc8f",
    "darkslateblue":  "#483d8b",
    "darkslategray":  "#2f4f4f",
    "darkturquoise":  "#00ced1",
    "darkviolet":  "#9400d3",
    "deeppink":  "#ff1493",
    "deepskyblue":  "#00bfff",
    "dimgray":  "#696969",
    "dodgerblue":  "#1e90ff",
    "firebrick":  "#b22222",
    "floralwhite":  "#fffaf0",
    "forestgreen":  "#228b22",
    "fuchsia":  "#ff00ff",
    "gainsboro":  "#dcdcdc",
    "ghostwhite":  "#f8f8ff",
    "gold":  "#ffd700",
    "goldenrod":  "#daa520",
    "gray":  "#808080",
    "green":  "#008000",
    "greenyellow":  "#adff2f",
    "honeydew":  "#f0fff0",
    "hotpink":  "#ff69b4",
    "indianred":   "#cd5c5c",
    "indigo":   "#4b0082",
    "ivory":  "#fffff0",
    "khaki":  "#f0e68c",
    "lavender":  "#e6e6fa",
    "lavenderblush":  "#fff0f5",
    "lawngreen":  "#7cfc00",
    "lemonchiffon":  "#fffacd",
    "lightblue":  "#add8e6",
    "lightcoral":  "#f08080",
    "lightcyan":  "#e0ffff",
    "lightgoldenrodyellow":  "#fafad2",
    "lightgrey":  "#d3d3d3",
    "lightgreen":  "#90ee90",
    "lightpink":  "#ffb6c1",
    "lightsalmon":  "#ffa07a",
    "lightseagreen":  "#20b2aa",
    "lightskyblue":  "#87cefa",
    "lightslategray":  "#778899",
    "lightsteelblue":  "#b0c4de",
    "lightyellow":  "#ffffe0",
    "lime":  "#00ff00",
    "limegreen":  "#32cd32",
    "linen":  "#faf0e6",
    "magenta":  "#ff00ff",
    "maroon":  "#800000",
    "mediumaquamarine":  "#66cdaa",
    "mediumblue":  "#0000cd",
    "mediumorchid":  "#ba55d3",
    "mediumpurple":  "#9370d8",
    "mediumseagreen":  "#3cb371",
    "mediumslateblue":  "#7b68ee",
    "mediumspringgreen":  "#00fa9a",
    "mediumturquoise":  "#48d1cc",
    "mediumvioletred":  "#c71585",
    "midnightblue":  "#191970",
    "mintcream":  "#f5fffa",
    "mistyrose":  "#ffe4e1",
    "moccasin":  "#ffe4b5",
    "navajowhite":  "#ffdead",
    "navy":  "#000080",
    "oldlace":  "#fdf5e6",
    "olive":  "#808000",
    "olivedrab":  "#6b8e23",
    "orange":  "#ffa500",
    "orangered":  "#ff4500",
    "orchid":  "#da70d6",
    "palegoldenrod":  "#eee8aa",
    "palegreen":  "#98fb98",
    "paleturquoise":  "#afeeee",
    "palevioletred":  "#d87093",
    "papayawhip":  "#ffefd5",
    "peachpuff":  "#ffdab9",
    "peru":  "#cd853f",
    "pink":  "#ffc0cb",
    "plum":  "#dda0dd",
    "powderblue":  "#b0e0e6",
    "purple":  "#800080",
    "red":  "#ff0000",
    "rosybrown":  "#bc8f8f",
    "royalblue":  "#4169e1",
    "saddlebrown":  "#8b4513",
    "salmon":  "#fa8072",
    "sandybrown":  "#f4a460",
    "seagreen":  "#2e8b57",
    "seashell":  "#fff5ee",
    "sienna":  "#a0522d",
    "silver":  "#c0c0c0",
    "skyblue":  "#87ceeb",
    "slateblue":  "#6a5acd",
    "slategray":  "#708090",
    "snow":  "#fffafa",
    "springgreen":  "#00ff7f",
    "steelblue":  "#4682b4",
    "tan":  "#d2b48c",
    "teal":  "#008080",
    "thistle":  "#d8bfd8",
    "tomato":  "#ff6347",
    "turquoise":  "#40e0d0",
    "violet":  "#ee82ee",
    "wheat":  "#f5deb3",
    "white":  "#ffffff",
    "whitesmoke":  "#f5f5f5",
    "yellow":  "#ffff00",
    "yellowgreen":  "#9acd32"
};

/** @constructor */
function ColourWheel( size )
{
    this.div = document.createElement("div");

    this.canvas = document.createElement("canvas");
    this.width = size;
    this.height = size;
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    this.canvas.style.cursor = "crosshair";
    this.ctx = this.canvas.getContext("2d");

    this.div.appendChild( this.canvas );

    this.outer = this.height;
    this.inner = this.height * 0.8;


    if ( ColourWheel[size] ) {
        this.data = ColourWheel[size];
    } else {
        var data = this.ctx.getImageData( 0, 0, this.outer, this.outer );
        var r = this.outer / 2;
        var r2 = this.inner / 2;
        var rgb;
        var a;

        for ( var y = 0; y < this.outer; y++ ) {
            var s = Math.sqrt( r*r-(y-r)*(y-r));
            var fromX1 = Math.floor(-s+r);
            var toX1 = Math.floor(s+r);
            var p;
            var x;

            var b = r2*r2-(y-r)*(y-r);
            if ( b >= 0 ) {
                s = Math.sqrt( b );
                var fromX2 = Math.floor(-s + r);
                var toX2 =Math.floor( s + r );
                p = y*this.outer*4+fromX1*4;
                for ( x = fromX1; x <= fromX2; x++ ) {
                    a = Math.atan2( y - r, x-r );
                    rgb = ConvertHsvRgb( a / Math.PI * 180, 1, 1 );

                    data.data[ p++ ] = rgb.r*255;
                    data.data[ p++ ] = rgb.g*255;
                    data.data[ p++ ] = rgb.b*255;
                    data.data[ p++ ] = 255;
                }

                p = y*this.outer*4+toX2*4;
                for ( x = toX2; x <= toX1; x++ ) {
                    a = Math.atan2( y - r, x-r );
                    rgb = ConvertHsvRgb( a / Math.PI * 180, 1, 1 );

                    data.data[ p++ ] = rgb.r*255;
                    data.data[ p++ ] = rgb.g*255;
                    data.data[ p++ ] = rgb.b*255;
                    data.data[ p++ ] = 255;
                }
            } else {
                p = y*this.outer*4+fromX1*4;

                for ( x = fromX1; x <= toX1; x++ ) {
                    a = Math.atan2( y - r, x-r );
                    rgb = ConvertHsvRgb( a / Math.PI * 180, 1, 1 );

                    data.data[ p++ ] = rgb.r*255;
                    data.data[ p++ ] = rgb.g*255;
                    data.data[ p++ ] = rgb.b*255;
                    data.data[ p++ ] = 255;
                }
            }
        }
        this.data = data;
        ColourWheel[size] = data;
    }

    this.hsv = { h:20, s:1, v:1, a:1 };
    this.rgb = ConvertHsvRgb( this.hsv.h, this.hsv.s, this.hsv.v );

    this.update();
    this.draw();

    var self = this;
    self.buttonDown = false;
    self.mouseArea = "";
    $(this.canvas).mousedown( function(e) {
        var offset = $(self.canvas).offset();
        var x = (e.pageX - offset.left );
        var y = (e.pageY - offset.top );
        self.buttonDown = true;
        self.calcClick( x, y );
        e.stopPropagation();
        e.preventDefault();
    });

    $(this.canvas).mousemove( function(e) {
        if ( self.buttonDown ) {
            var offset = $(self.canvas).offset();
            var x = (e.pageX - offset.left );
            var y = (e.pageY - offset.top );
            self.calcClick( x, y );
        }
        e.stopPropagation();
        e.preventDefault();
    });

    $(window).mouseup( function(e) {
        self.buttonDown = false;
        self.mouseArea = "";
        e.stopPropagation();
        e.preventDefault();
    });
}
ColourWheel.prototype = {

    setFromColour: function( colourString )
    {
        if ( colourString in CssColours ) {
            colourString = CssColours[colourString];
        }

        var hex6 = /\#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i;
        var r, g, b;

        var m = hex6.exec( colourString );
        if ( m !== null ) {
            r = parseInt( m[1], 16 ) / 255;
            g = parseInt( m[2], 16 ) / 255;
            b = parseInt( m[3], 16 ) / 255;
        } else {
            r = 0.0;
            g = 1.0;
            b = 1.0;
        }

        this.hsv = ConvertRgbHsv( r, g, b );
        this.rgb.r = r;
        this.rgb.g = g;
        this.rgb.b = b;
        this.draw();
        this.update();
    },

    update: function()
    {
        function toHex(n) {
            var s = Math.round(n * 255).toString(16);
            if ( s.length === 1 ) {
                s = "0" + s;
            }
            return s;
        }

        var hash = "#" + toHex(this.rgb.r) + toHex( this.rgb.g ) + toHex(
                this.rgb.b);

        if ( this.onUpdate ) {
            this.onUpdate( hash );
        }

    },

    draw: function()
    {
        this.ctx.save();
        this.ctx.lineWidth = 1;
        this.ctx.fillStyle = "rgb(128, 128, 128)";
        this.ctx.fillRect( 0, 0, this.width, this.height);
        this.ctx.putImageData( this.data, 0, 0 );

        var a = this.hsv.h / 180 * Math.PI;

        this.ctx.beginPath();
        var p1 = {
            x: Math.cos( a ) * this.inner / 2 + this.outer / 2,
            y: Math.sin( a ) * this.inner / 2 + this.outer / 2
        };
        var p2 = {
            x: Math.cos( a+2 * Math.PI / 3 ) * this.inner / 2 + this.outer / 2,
            y: Math.sin( a+2 * Math.PI / 3 ) * this.inner / 2 + this.outer / 2
        };
        var p3 = {
            x: Math.cos( a+4 * Math.PI / 3 ) * this.inner / 2 + this.outer / 2,
            y: Math.sin( a+4 * Math.PI / 3 ) * this.inner / 2 + this.outer / 2
        };

        var outLine = {
            x: Math.cos( a ) * this.outer / 2 + this.outer / 2,
            y: Math.sin( a ) * this.outer / 2 + this.outer / 2
        };

        var mid = {
            x: p2.x + (p3.x - p2.x)/2,
            y: p2.y + (p3.y - p2.y)/2
        };
        this.ctx.moveTo( p1.x, p1.y );
        this.ctx.lineTo( p2.x, p2.y );
        this.ctx.lineTo( p3.x, p3.y );
        this.ctx.lineTo( p1.x, p1.y );

        var fill = this.ctx.createLinearGradient( p2.x, p2.y, p3.x, p3.y );
        fill.addColorStop( 0, "#ffffff" );
        fill.addColorStop( 1, "#000000" );
        this.ctx.fillStyle = fill;
        this.ctx.fill();

        fill = this.ctx.createLinearGradient( p1.x, p1.y, mid.x, mid.y);

        var fullRgb = ConvertHsvRgb( this.hsv.h, 1.0, 1.0 );
        fullRgb.a = 1.0;

        fill.addColorStop( 0, this.rgbToString(fullRgb) );
        fullRgb.a = 0.0;
        fill.addColorStop( 1, this.rgbToString(fullRgb) );
        this.ctx.fillStyle = fill;
        this.ctx.fill();

        // Draw the line that points the colour
        this.strokeStyle = "#000000";
        this.ctx.beginPath();
        this.ctx.moveTo( p1.x, p1.y );
        this.ctx.lineTo( outLine.x, outLine.y );
        this.ctx.stroke();

        // draw the circle around the exact point on the triangle.
        var x, y,v;
        v = 1.0 - this.hsv.v;
        x = this.hsv.s * p1.x + v * p3.x + (1-this.hsv.s-v) *
            p2.x;
        y = this.hsv.s * p1.y + v * p3.y + (1-this.hsv.s-v) *
            p2.y;

        this.ctx.beginPath();
        this.ctx.arc( x, y, 4, 0, 2 * Math.PI, false );
        this.ctx.stroke();

        this.ctx.restore();

        this.p1 = p1;
        this.p2 = p2;
        this.p3 = p3;
        this.mid = mid;

        this.rgb = ConvertHsvRgb( this.hsv.h, this.hsv.s, this.hsv.v );
        this.rgb.a = this.hsv.a;
    },

    rgbToString: function( rgb )
    {
        return "rgba(" + Math.round(rgb.r*255) + "," +
            Math.round( rgb.g*255) + "," +
            Math.round( rgb.b*255) + "," +
            rgb.a + ")";
    },

    calcClick: function( x, y )
    {
        var distance = Math.sqrt( (x - this.outer/2)*(x-this.outer/2) +
                (y-this.outer/2)*(y-this.outer/2));

        if ( this.mouseArea === "ring" || 
                this.mouseArea !== "triangle" &&
                distance >= this.inner / 2 && distance <= this.outer / 2 ) {
            var a = Math.atan2( this.outer/2 - y, this.outer/2 - x );
            this.hsv.h = a / Math.PI * 180 + 180;
            if ( this.hsv.s === 0.0 ) {
                // Usability: Testing revealed user was unable to change the
                // colour, because she expected it to change when she clicked
                // on the outer ring.
                this.hsv.s = 1.0;
                this.hsv.v = 1.0;
            }
            this.mouseArea = "ring";
        } else {
            var lambda1, lambda2, lambda3;
            var det;
            var p1 = this.p1;
            var p2 = this.p2;
            var p3 = this.p3;

            det = (p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y);

            lambda1 = ((p2.y-p3.y)*(x-p3.x)-(p2.x-p3.x)*(y-p3.y)) / det;
            lambda2 = (-(p1.y-p3.y)*(x-p3.x)+(p1.x-p3.x)*(y-p3.y)) / det;
            lambda3 = 1.0 - Math.max(0, lambda1) - Math.max(0, lambda2);

            this.hsv.s = Math.min( Math.max( lambda1, 0.0), 1.0 );
            this.hsv.v = 1.0 - Math.min( Math.max( lambda3, 0.0), 1.0 );

            this.mouseArea = "triangle";
        }

        this.draw();
        this.update();
    }

};
