(function( $ )
{
$.fn.rotate_knob = function( method )
{
    var update = function( self, value, preventEvent  )
    {
        var min = $(self).data('min');
        var max = $(self).data('max');
        var units = $(self).data('units');

        if (value < min)
            value = min;
        else if (value > max)
            value = max;

        var angle = Number(value) / units * Math.PI * 2;

        var rotate = "rotate(" + angle + "rad)";

        $(self).children(".knob-rotate").first().css(
        {
            "transform" : rotate,
            "-ms-transform" : rotate,
            "-moz-transform" : rotate,
            "-webkit-transform": rotate,
            "-o-transform": rotate,
        });

        $(self).data('angle', angle);
        $(self).data('value', value);

        if (!preventEvent && $(self).data('onValueChange'))
            $(self).data('onValueChange').call(self, value);
    };

    var methods =
    {
        init: function ( options )
        {
            var options = $.extend(
            {
                min: 0,
                max: 40,
                unitsPerCycle: 1,

                value: 0,
            }, options);

            return this.each(function()
            {
                var self = this;

                $(self).data('min', options.min);
                $(self).data('max', options.max);
                $(self).data('units', options.unitsPerCycle);
                $(self).data('units', options.unitsPerCycle);

                $(self).data('onValueChange', options.onValueChange);
                $(self).data('onBeginChange', options.onBeginChange);
                $(self).data('onEndChange', options.onEndChange);

                update(self, options.value);
                
                var onMove = function ( e )
                {
                    var x0 = $(self).offset().left + $(self).outerWidth() / 2;
                    var y0 = $(self).offset().top + $(self).outerHeight() / 2;

                    var x1 = $(self).data('beginX') - x0;
                    var y1 = $(self).data('beginY') - y0;

                    var x2 = e.pageX - x0;
                    var y2 = e.pageY - y0;

                    var sinA = (x1 * y2 - x2 * y1) / Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2));
                    var cosA = Math.sqrt(1 - sinA * sinA);

                    var angle = $(self).data('angle') + Math.asin(sinA);
                    var value = angle / Math.PI / 2 * $(self).data('units');

                    update(self, value);

                    $(self).data('beginX', e.pageX);
                    $(self).data('beginY', e.pageY);
                };

                var onUp = function ( e )
                {
                    $(document).unbind('mousemove', onMove);
                    $(document).unbind('mouseup', onUp);
                    e.preventDefault();

                    if ($(self).data('onEndChange'))
                        $(self).data('onEndChange').call(self, $(self).data('value'));
                };

                var onDown = function ( e )
                {
                    $(document).bind('mousemove', onMove);
                    $(document).bind('mouseup', onUp);

                    $(self).data('beginX', e.pageX);
                    $(self).data('beginY', e.pageY);

                    e.preventDefault();

                    if ($(self).data('onBeginChange'))
                        $(self).data('onBeginChange').call(self);
                };

                $(self).bind('mousedown', onDown);
            });
        },

        setValue: function ( value )
        {
            return this.each(function()
            {
                var self = this;
                update(self, value, true);
            });
        },

        setRange: function( min, max )
        {
            return this.each(function()
            {
                var self = this;
                var value = $(self).data('value');

                if (min > max)
                {
                    var tmp = min;
                    min = max;
                    max = tmp;
                }
                
                $(self).data('min', min);
                $(self).data('max', max);
                    
                update(self, value);
            });
        }
    };

    if ( methods[method] )
        return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    else if ( typeof method === 'object' || ! method )
        return methods.init.apply( this, arguments );
    else
        $.error( 'Method ' +  method + ' does not exist' );
};
})( jQuery );

