/*!
 * roundSlider v1.0 | (c) 2015, Soundar
 * MIT license | http://roundsliderui.com/licence.html
 */

(function ($, window, undefined) {
    "use strict";
    /*jslint nomen: true */

    var pluginName = "roundSlider";

    // The plugin initialization
    $.fn[pluginName] = function (options) {
        return CreateRoundSlider.call(this, options, arguments);
    };

    RoundSlider.prototype = {

        pluginName: pluginName,
        version: "1.0",

        // after the control initialization the updated default values
        // are merged into the options
        options: {},

        // default properties of the plugin. while add a new property,
        // that type should be included in the "_props:" for validation
        defaults: {
            min: 0,
            max: 100,
            step: 1,
            value: null,
            radius: 85,
            width: 18,
            handleSize: "+0",
            startAngle: 0,
            endAngle: "+360",
            animation: true,
            showTooltip: true,
            editableTooltip: true,
            readOnly: false,
            disabled: false,
            keyboardAction: true,
            mouseScrollAction: false,
            sliderType: "default",
            circleShape: "full",
            handleShape: "round",

            // events
            beforeCreate: null,
            create: null,
            start: null,
            drag: null,
            change: null,
            stop: null,
            tooltipFormat: null
        },
        _props: function () {
            return {
                numberType: ["min", "max", "step", "radius", "width", "startAngle"],
                booleanType: ["animation", "showTooltip", "editableTooltip", "readOnly", "disabled",
                    "keyboardAction", "mouseScrollAction"],
                stringType: ["sliderType", "circleShape", "handleShape"]
            };
        },
        control: null,
        _init: function () {
            this._initialize();
            this._update();
            this._render();
        },
        _initialize: function () {
            this._isBrowserSupport = this._isBrowserSupported();
            if (!this._isBrowserSupport) return;
            this._originalObj = this.control.clone();
            this._isReadOnly = false;
            this._checkDataType();
            this._refreshCircleShape();
        },
        _render: function () {
            this.container = createElement("div.rs-container");
            this.innerContainer = createElement("div.rs-inner-container");
            this.block = createElement("div.rs-block rs-outer rs-border");
            this.container.append(this.innerContainer.append(this.block));
            this.control.addClass("rs-control").empty().append(this.container);

            this._setRadius();
            if (this._isBrowserSupport) {
                this._createLayers();
                this._setProperties();
                this._setValue();
                this._bindControlEvents("_bind");
                this._checkIE();
            }
            else {
                var msg = createElement("div.rs-msg");
                msg.html(typeof this._throwError === "function" ? this._throwError() : this._throwError);
                this.control.empty().addClass("rs-error").append(msg);
            }
        },
        _update: function () {
            this._validateSliderType();
            this._updateStartEnd();
            this._validateStartEnd();
            this._handle1 = this._handle2 = this._handleDefaults();
            this._analyzeModelValue();
            this._validateModelValue();
        },
        _createLayers: function () {
            var padd = this.options.width, start = this._start, path;
            path = createElement("div.rs-path rs-transition");

            if (this._rangeSlider || this._showRange) {
                this.block1 = path.clone().addClass("rs-range-color").rsRotate(start);
                this.block2 = path.clone().addClass("rs-range-color").css("opacity", "0").rsRotate(start);
                this.block3 = path.clone().addClass("rs-path-color").rsRotate(start);
                this.block4 = path.addClass("rs-path-color").css({ "opacity": "1", "z-index": "1" }).rsRotate(start - 180);

                this.block.append(this.block1, this.block2, this.block3, this.block4).addClass("rs-split");
            }
            else this.block.append(path.addClass("rs-path-color"));

            this.lastBlock = createElement("span.rs-block").css({ "padding": padd });
            this.innerBlock = createElement("div.rs-inner rs-bg-color rs-border");
            this.lastBlock.append(this.innerBlock);
            this.block.append(this.lastBlock);
            this._appendHandle();
            this._appendOverlay();
            this._appendHiddenField();
        },
        _setProperties: function () {
            this._prechange = this._predrag = this.options.value;
            this._setHandleShape();
            this._addAnimation();
            this._appendTooltip();
            if (!this.options.showTooltip) this._removeTooltip();
            if (this.options.disabled) this.disable();
            else if (this.options.readOnly) this._readOnly(true);
            if (this.options.mouseScrollAction) this._bindScrollEvents("_bind");
        },
        _setValue: function () {
            if (this._rangeSlider) {
                this._setHandleValue(1);
                this._setHandleValue(2);
            }
            else {
                if (this._showRange) this._setHandleValue(1);
                var index = (this.options.sliderType == "default") ? (this._active || 1) : parseFloat(this.bar.children().attr("index"));
                this._setHandleValue(index);
            }
        },
        _appendTooltip: function () {
            if (this.container.children(".rs-tooltip").length !== 0) return;
            this.tooltip = createElement("span.rs-tooltip rs-tooltip-text");
            this.container.append(this.tooltip);
            this._tooltipEditable();
            this._updateTooltip();
        },
        _removeTooltip: function () {
            if (this.container.children(".rs-tooltip").length == 0) return;
            this.tooltip && this.tooltip.remove();
        },
        _tooltipEditable: function () {
            if (!this.tooltip || !this.options.showTooltip) return;
            var hook;
            if (this.options.editableTooltip) {
                this.tooltip.addClass("edit");
                hook = "_bind";
            }
            else {
                this.tooltip.removeClass("edit");
                hook = "_unbind";
            }
            this[hook](this.tooltip, "click", this._editTooltip);
        },
        _editTooltip: function (e) {
            if (!this.tooltip.hasClass("edit") || this._isReadOnly) return;
            this.input = createElement("input.rs-input rs-tooltip-text").css({
                height: this.tooltip.outerHeight(),
                width: this.tooltip.outerWidth()
            });
            this.tooltip.html(this.input).removeClass("edit").addClass("hover");

            this.input.val(this._getTooltipValue(true)).focus();

            this._bind(this.input, "blur", this._focusOut);
            this._bind(this.input, "change", this._focusOut);
        },
        _focusOut: function (e) {
            if (e.type == "change") {
                this.options.value = this.input.val().replace("-", ",");
                this._analyzeModelValue();
                this._validateModelValue();
                this._setValue();
                this.input.val(this._getTooltipValue(true));
            }
            else this.tooltip.addClass("edit").removeClass("hover");
            this._raiseEvent("change");
        },
        _setHandleShape: function () {
            var type = this.options.handleShape;
            this._handles().removeClass("rs-handle-dot rs-handle-square");
            if (type == "dot") this._handles().addClass("rs-handle-dot");
            else if (type == "square") this._handles().addClass("rs-handle-square");
            else this.options.handleShape = this.defaults.handleShape;
        },
        _setHandleValue: function (index) {
            this._active = index;
            var handle = this["_handle" + index];
            if (this.options.sliderType != "min-range") this.bar = this._activeHandleBar();
            this._changeSliderValue(handle.value, handle.angle);
        },
        _setAnimation: function () {
            if (this.options.animation) this._addAnimation();
            else this._removeAnimation();
        },
        _addAnimation: function () {
            if (this.options.animation) this.control.addClass("rs-animation");
        },
        _removeAnimation: function () {
            this.control.removeClass("rs-animation");
        },
        _setRadius: function () {
            var r = this.options.radius, d = r * 2;
            var circleShape = this.options.circleShape, height = d, width = d;
            this.container.removeClass().addClass("rs-container");

            if (circleShape.indexOf("half") === 0) {
                switch (circleShape) {
                    case "half-top":
                    case "half-bottom":
                        height = r; width = d; break;
                    case "half-left":
                    case "half-right":
                        height = d; width = r; break;
                }
                this.container.addClass(circleShape.replace("half-", "") + " half");
            }
            else if (circleShape.indexOf("quarter") === 0) {
                height = width = r;
                var s = circleShape.split("-");
                this.container.addClass(s[0] + " " + s[1] + " " + s[2]);
            }
            else this.container.addClass("full " + circleShape);

            var style = { "height": height, "width": width };
            this.control.css(style);
            this.container.css(style);
        },
        _border: function () {
            return parseFloat(this.block.css("border-top-width")) * 2;
        },
        _appendHandle: function () {
            if (this._rangeSlider || !this._showRange) this._createHandle(1);
            if (this._rangeSlider || this._showRange) this._createHandle(2);
            this._startLine = this._addSeperator(this._start, "rs-start");
            this._endLine = this._addSeperator(this._start + this._end, "rs-end");
        },
        _addSeperator: function (pos, cls) {
            var line = createElement("span.rs-seperator").css({ "width": this.options.width, "margin-left": this._border() / 2 });
            var lineWrap = createElement("span.rs-bar rs-transition " + cls).append(line).rsRotate(pos);
            this.container.append(lineWrap);
            return lineWrap;
        },
        _updateSeperator: function () {
            this._startLine.rsRotate(this._start);
            this._endLine.rsRotate(this._start + this._end);
        },
        _createHandle: function (index) {
            var handle = createElement("div.rs-handle rs-move");
            handle.attr({ "index": index, "tabIndex": "0" });

            var id = this.control[0].id, id = id ? id + "_" : "";
            var label = id + "handle" + (this.options.sliderType == "range" ? "_" + (index == 1 ? "start" : "end") : "");
            handle.attr({ "role": "slider", "aria-label": label });     // WAI-ARIA support

            var bar = createElement("div.rs-bar rs-transition").css("z-index", "4").append(handle).rsRotate(this._start);
            bar.addClass(this.options.sliderType == "range" && index == 2 ? "rs-second" : "rs-first");
            this.container.append(bar);
            this._refreshHandle();

            this.bar = bar;
            this._active = index;
            if (index != 1 && index != 2) this["_handle" + index] = this._handleDefaults();
            this._bind(handle, "focus", this._handleFocus);
            this._bind(handle, "blur", this._handleBlur);
            return handle;
        },
        _refreshHandle: function () {
            var hSize = this.options.handleSize, h, w, isSquare = true;
            if (typeof hSize === "string" && isNumber(hSize)) {
                if (hSize.charAt(0) === "+" || hSize.charAt(0) === "-") {
                    try { hSize = eval(this.options.width + hSize.charAt(0) + Math.abs(parseFloat(hSize))); }
                    catch (e) { console.warn(e); }
                }
                else if (hSize.indexOf(",")) {
                    var s = hSize.split(",");
                    if (isNumber(s[0]) && isNumber(s[1])) w = parseFloat(s[0]), h = parseFloat(s[1]), isSquare = false;
                }
            }
            if (isSquare) h = w = isNumber(hSize) ? parseFloat(hSize) : this.options.width;
            var diff = (this.options.width + this._border() - w) / 2;
            this._handles().css({ height: h, width: w, "margin": -h / 2 + "px 0 0 " + diff + "px" });
        },
        _handleDefaults: function () {
            return { angle: this._valueToAngle(this.options.min), value: this.options.min };
        },
        _handles: function () {
            return this.container.children("div.rs-bar").find(".rs-handle");
        },
        _activeHandleBar: function () {
            return $(this.container.children("div.rs-bar")[this._active - 1]);
        },
        _handleArgs: function () {
            var _handle = this["_handle" + this._active];
            return {
                element: this._activeHandleBar().children(),
                index: this._active,
                value: _handle ? _handle.value : null,
                angle: _handle ? _handle.angle : null
            };
        },
        _raiseEvent: function (event) {
            this._updateTooltip();
            if (event == "change") this._updateHidden();
            if (this["_pre" + event] !== this.options.value) {
                this["_pre" + event] = this.options.value;
                return this._raise(event, { value: this.options.value, "handle": this._handleArgs() });
            }
        },

        // Events handlers
        _elementDown: function (e) {
            if (this._isReadOnly) return;
            var $target = $(e.target);

            if ($target.hasClass("rs-handle")) {
                this._handleDown(e);
            }
            else {
                var point = this._getXY(e), center = this._getCenterPoint();
                var distance = getdistance(point, center);
                var outerDistance = this.block.outerWidth() / 2;
                var innerDistance = outerDistance - (this.options.width + this._border());

                if (distance >= innerDistance && distance <= outerDistance) {
                    e.preventDefault();
                    var handle = this.control.find(".rs-handle.rs-focus");
                    //if (handle.length == 0)
                    this.control.attr("tabindex", "0").focus().removeAttr("tabindex");
                    var d = this._getAngleValue(point, center), angle, value;
                    angle = d.angle, value = d.value;
                    if (this._rangeSlider) {
                        handle = this.control.find(".rs-handle.rs-focus");
                        if (handle.length == 1) this._active = parseFloat(handle.attr("index"));
                        else this._active = (this._handle2.value - value) < (value - this._handle1.value) ? 2 : 1;
                        this.bar = this._activeHandleBar();
                    }

                    this._changeSliderValue(value, angle);
                    this._raiseEvent("change");
                }
            }
        },
        _handleDown: function (e) {
            e.preventDefault();
            var $target = $(e.target);
            $target.focus();
            this._removeAnimation();
            this._bindMouseEvents("_bind");
            this.bar = $target.parent();
            this._active = parseFloat($target.attr("index"));
            this._handles().removeClass("rs-move");
            this._raise("start", { "handle": this._handleArgs() });
        },
        _handleMove: function (e) {
            e.preventDefault();
            var point = this._getXY(e), center = this._getCenterPoint();
            var d = this._getAngleValue(point, center), angle, value;
            angle = d.angle, value = d.value;

            this._changeSliderValue(value, angle);
            this._raiseEvent("drag");
        },
        _handleUp: function (e) {
            this._handles().addClass("rs-move");
            this._bindMouseEvents("_unbind");
            this._addAnimation();
            this._raiseEvent("change");
            this._raise("stop", { "handle": this._handleArgs() });
        },
        _handleFocus: function (e) {
            if (this._isReadOnly) return;
            var $target = $(e.target);
            this._handles().removeClass("rs-focus");
            $target.addClass("rs-focus");
            this.bar = $target.parent();
            this._active = parseFloat($target.attr("index"));
            if (this.options.keyboardAction) {
                this._bindKeyboardEvents("_unbind");
                this._bindKeyboardEvents("_bind");
            }

            // updates the class for change z-index
            this.control.find("div.rs-bar").css("z-index", "4");
            this.bar.css("z-index", "5");
        },
        _handleBlur: function (e) {
            this._handles().removeClass("rs-focus");
            if (this.options.keyboardAction) this._bindKeyboardEvents("_unbind");
        },
        _handleKeyDown: function (e) {
            if (this._isReadOnly) return;
            var key = e.keyCode;
            if (key == 27) this._handles().blur();
            if (!(key >= 35 && key <= 40)) return;
            if (key >= 37 && key <= 40) this._removeAnimation();
            var h = this["_handle" + this._active], val, ang;

            e.preventDefault();
            if (key == 38 || key == 37) val = this._round(this._limitValue(h.value + this.options.step));  // Up || Left Key
            else if (key == 39 || key == 40) val = this._round(this._limitValue(h.value - this._getMinusStep(h.value))); // Right || Down Key
            else if (key == 36) val = this._getKeyValue("Home"); // Home Key
            else if (key == 35) val = this._getKeyValue("End"); // End Key

            ang = this._valueToAngle(val);
            this._changeSliderValue(val, ang);
            this._raiseEvent("change");
        },
        _handleKeyUp: function (e) {
            this._addAnimation();
        },
        _getMinusStep: function (val) {
            if (val == this.options.max) {
                var step = (this.options.max - this.options.min) % this.options.step;
                return step == 0 ? this.options.step : step;
            }
            return this.options.step;
        },
        _getKeyValue: function (key) {
            if (this._rangeSlider) {
                if (key == "Home") return (this._active == 1) ? this.options.min : this._handle1.value;
                else return (this._active == 1) ? this._handle2.value : this.options.max;
            }
            return (key == "Home") ? this.options.min : this.options.max;
        },
        _elementScroll: function (event) {
            if (this._isReadOnly) return;
            event.preventDefault();
            var e = event.originalEvent || event, h, val, ang, delta;
            delta = e.wheelDelta ? e.wheelDelta / 60 : (e.detail ? -e.detail / 2 : 0);
            if (delta == 0) return;

            this._updateActiveHandle(event);
            h = this["_handle" + this._active];
            val = h.value + (delta > 0 ? this.options.step : -this._getMinusStep(h.value));
            val = this._limitValue(val);
            ang = this._valueToAngle(val);

            this._removeAnimation();
            this._changeSliderValue(val, ang);
            this._raiseEvent("change");
            this._addAnimation();
        },
        _updateActiveHandle: function (e) {
            var $target = $(e.target);
            if ($target.hasClass("rs-handle") && $target.parent().parent()[0] == this.control[0]) {
                this.bar = $target.parent();
                this._active = parseFloat($target.attr("index"));
            }
            if (!this.bar.find(".rs-handle").hasClass("rs-focus")) this.bar.find(".rs-handle").focus();
        },

        // Events binding
        _bindControlEvents: function (hook) {
            this[hook](this.control, "mousedown", this._elementDown);
            this[hook](this.control, "touchstart", this._elementDown);
        },
        _bindScrollEvents: function (hook) {
            this[hook](this.control, "mousewheel", this._elementScroll);
            this[hook](this.control, "DOMMouseScroll", this._elementScroll);
        },
        _bindMouseEvents: function (hook) {
            this[hook]($(document), "mousemove", this._handleMove);
            this[hook]($(document), "mouseup", this._handleUp);
            this[hook]($(document), "mouseleave", this._handleUp);

            // *** for Touch support *** //
            this[hook]($(document), "touchmove", this._handleMove);
            this[hook]($(document), "touchend", this._handleUp);
            this[hook]($(document), "touchcancel", this._handleUp);
        },
        _bindKeyboardEvents: function (hook) {
            this[hook]($(document), "keydown", this._handleKeyDown);
            this[hook]($(document), "keyup", this._handleKeyUp);
        },

        // internal methods
        _changeSliderValue: function (value, angle) {
            var oAngle = this._oriAngle(angle), lAngle = this._limitAngle(angle);
            if (!this._rangeSlider && !this._showRange) {

                this["_handle" + this._active] = { angle: angle, value: value };
                this.options.value = value;
                this.bar.rsRotate(lAngle);
                this._updateARIA(value);
            }
            else if ((this._active == 1 && oAngle <= this._oriAngle(this._handle2.angle)) ||
                    (this._active == 2 && oAngle >= this._oriAngle(this._handle1.angle))) {

                this["_handle" + this._active] = { angle: angle, value: value };
                this.options.value = this._rangeSlider ? this._handle1.value + "," + this._handle2.value : value;
                this.bar.rsRotate(lAngle);
                this._updateARIA(value);

                var dAngle = this._oriAngle(this._handle2.angle) - this._oriAngle(this._handle1.angle), o2 = "1", o3 = "0";
                if (dAngle <= 180) o2 = "0", o3 = "1";
                this.block2.css("opacity", o2);
                this.block3.css("opacity", o3);

                (this._active == 1 ? this.block4 : this.block2).rsRotate(lAngle - 180);
                (this._active == 1 ? this.block1 : this.block3).rsRotate(lAngle);
            }
        },
        // WAI-ARIA support
        _updateARIA: function (value) {
            var min = this.options.min, max = this.options.max;
            this.bar.children().attr({ "aria-valuenow": value });
            if (this.options.sliderType == "range") {
                var handles = this._handles();
                handles.eq(0).attr({ "aria-valuemin": min });
                handles.eq(1).attr({ "aria-valuemax": max });

                if (this._active == 1) handles.eq(1).attr({ "aria-valuemin": value });
                else handles.eq(0).attr({ "aria-valuemax": value });
            }
            else this.bar.children().attr({ "aria-valuemin": min, "aria-valuemax": max });
        },
        _getXY: function (e) {
            if (e.type.indexOf("mouse") == -1) e = (e.originalEvent || e).changedTouches[0];
            return { x: e.pageX, y: e.pageY };
        },
        _getCenterPoint: function () {
            var offset = this.block.offset(), center;
            center = {
                x: offset.left + (this.block.outerWidth() / 2),
                y: offset.top + (this.block.outerHeight() / 2)
            };
            return center;
        },
        _getAngleValue: function (point, center) {
            var deg = Math.atan2(point.y - center.y, center.x - point.x);
            var angle = (-deg / (Math.PI / 180));
            if (angle < this._start) angle += 360;
            angle = this._checkAngle(angle);
            return this._processStepByAngle(angle);;
        },
        _checkAngle: function (angle) {
            var o_angle = this._oriAngle(angle);
            if (o_angle > this._end) {
                var preAngle = this._oriAngle(this["_handle" + this._active].angle);
                angle = this._start + (preAngle <= this._end - preAngle ? 0 : this._end);
            }
            return angle;
        },
        _processStepByAngle: function (angle) {
            var value = this._angleToValue(angle);
            return this._processStepByValue(value);
        },
        _processStepByValue: function (value) {
            var step = this.options.step, remain, currVal, nextVal, preVal, val, ang;
            remain = (value - this.options.min) % step;
            currVal = value - remain;
            nextVal = this._limitValue(currVal + step);
            preVal = this._limitValue(currVal - step);

            if (value >= currVal) val = (value - currVal < nextVal - value) ? currVal : nextVal;
            else val = (currVal - value > value - preVal) ? currVal : preVal;

            val = this._round(val), ang = this._valueToAngle(val);
            return { value: val, angle: ang };
        },
        _round: function (val) {
            var s = this.options.step.toString().split(".");
            return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val);
        },
        _oriAngle: function (angle) {
            var ang = angle - this._start;
            if (ang < 0) ang += 360;
            return ang;
        },
        _limitAngle: function (angle) {
            if (angle > 360 + this._start) angle -= 360;
            if (angle < this._start) angle += 360;
            return angle;
        },
        _limitValue: function (value) {
            if (value < this.options.min) value = this.options.min;
            if (value > this.options.max) value = this.options.max;
            return value;
        },
        _angleToValue: function (angle) {
            var m = this.options, value;
            value = (this._oriAngle(angle) / this._end) * (m.max - m.min) + m.min;
            return value;
        },
        _valueToAngle: function (value) {
            var m = this.options, angle;
            angle = (((value - m.min) / (m.max - m.min)) * this._end) + this._start;
            return angle;
        },
        _appendHiddenField: function () {
            this._hiddenField = createElement("input").attr({
                "type": "hidden", "name": this.control[0].id || "", value: this.options.value
            });
            this.control.append(this._hiddenField);
        },
        _updateHidden: function () {
            this._hiddenField.val(this.options.value);
        },
        _updateTooltip: function () {
            if (this.tooltip && !this.tooltip.hasClass("hover"))
                this.tooltip.html(this._getTooltipValue());
            this._updateTooltipPos();
        },
        _updateTooltipPos: function () {
            this.tooltip && this.tooltip.css(this._getTooltipPos());
        },
        _getTooltipPos: function () {
            var circleShape = this.options.circleShape, pos;
            if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0)
                return {
                    "margin-top": -this.tooltip.outerHeight() / 2,
                    "margin-left": -this.tooltip.outerWidth() / 2
                };
            else if (circleShape.indexOf("half") != -1) {
                switch (circleShape) {
                    case "half-top":
                    case "half-bottom":
                        pos = { "margin-left": -this.tooltip.outerWidth() / 2 }; break;
                    case "half-left":
                    case "half-right":
                        pos = { "margin-top": -this.tooltip.outerHeight() / 2 }; break;
                }
                return pos;
            }
            return {};
        },
        _getTooltipValue: function (isNormal) {
            if (this._rangeSlider) {
                var p = this.options.value.split(",");
                if (isNormal) return p[0] + " - " + p[1];
                return this._tooltipValue(p[0]) + " - " + this._tooltipValue(p[1]);
            }
            if (isNormal) return this.options.value;
            return this._tooltipValue(this.options.value);
        },
        _tooltipValue: function (value) {
            var val = this._raise("tooltipFormat", { value: value, "handle": this._handleArgs() });
            return (val != null && typeof val !== "boolean") ? val : value;
        },
        _validateStartAngle: function () {
            var start = this.options.startAngle;
            start = (isNumber(start) ? parseFloat(start) : 0) % 360;
            if (start < 0) start += 360;
            this.options.startAngle = start;
            return start;
        },
        _validateEndAngle: function () {
            var end = this.options.endAngle;
            if (typeof end === "string" && isNumber(end) && (end.charAt(0) === "+" || end.charAt(0) === "-")) {
                try { end = eval(this.options.startAngle + end.charAt(0) + Math.abs(parseFloat(end))); }
                catch (e) { console.warn(e); }
            }
            end = (isNumber(end) ? parseFloat(end) : 360) % 360;
            if (end <= this.options.startAngle) end += 360;
            return end;
        },
        _refreshCircleShape: function () {
            var circleShape = this.options.circleShape;
            var circel_shapes = ["half-top", "half-bottom", "half-left", "half-right",
                "quarter-top-left", "quarter-top-right", "quarter-bottom-right", "quarter-bottom-left",
                "pie", "custom-half", "custom-quarter"];
            var shape_codes = ["h1", "h2", "h3", "h4", "q1", "q2", "q3", "q4", "3/4", "ch", "cq"];

            if (circel_shapes.indexOf(circleShape) == -1) {
                var index = shape_codes.indexOf(circleShape);
                if (index != -1) circleShape = circel_shapes[index];
                else if (circleShape == "half") circleShape = "half-top";
                else if (circleShape == "quarter") circleShape = "quarter-top-left";
                else circleShape = "full";
            }
            this.options.circleShape = circleShape;
        },
        _appendOverlay: function () {
            var shape = this.options.circleShape;
            if (shape == "pie")
                this._checkOverlay(".rs-overlay", 270);
            else if (shape == "custom-half" || shape == "custom-quarter") {
                this._checkOverlay(".rs-overlay1", 180);
                if (shape == "custom-quarter")
                    this._checkOverlay(".rs-overlay2", this._end);
            }
        },
        _checkOverlay: function (cls, angle) {
            var overlay = this.container.children(cls);
            if (overlay.length == 0) {
                overlay = createElement("div" + cls + " rs-transition rs-bg-color");
                this.container.append(overlay);
            }
            overlay.rsRotate(this._start + angle);
        },
        _checkDataType: function () {
            var m = this.options, i, prop, value, props = this._props();
            // to check number datatype
            for (i in props.numberType) {
                prop = props.numberType[i], value = m[prop];
                if (!isNumber(value)) m[prop] = this.defaults[prop];
                else m[prop] = parseFloat(value);
            }
            // to check input string
            for (i in props.booleanType) {
                prop = props.booleanType[i], value = m[prop];
                m[prop] = (value == "false") ? false : !!value;
            }
            // to check boolean datatype
            for (i in props.stringType) {
                prop = props.stringType[i], value = m[prop];
                m[prop] = ("" + value).toLowerCase();
            }
        },
        _validateSliderType: function () {
            var type = this.options.sliderType.toLowerCase();
            this._rangeSlider = this._showRange = false;
            if (type == "range") this._rangeSlider = this._showRange = true;
            else if (type.indexOf("min") != -1) {
                this._showRange = true;
                type = "min-range";
            }
            else type = "default";
            this.options.sliderType = type;
        },
        _updateStartEnd: function () {
            var circle = this.options.circleShape;
            if (circle != "full") {
                if (circle.indexOf("quarter") != -1) this.options.endAngle = "+90";
                else if (circle.indexOf("half") != -1) this.options.endAngle = "+180";
                else if (circle == "pie") this.options.endAngle = "+270";

                if (circle == "quarter-top-left" || circle == "half-top") this.options.startAngle = 0;
                else if (circle == "quarter-top-right" || circle == "half-right") this.options.startAngle = 90;
                else if (circle == "quarter-bottom-right" || circle == "half-bottom") this.options.startAngle = 180;
                else if (circle == "quarter-bottom-left" || circle == "half-left") this.options.startAngle = 270;
            }
        },
        _validateStartEnd: function () {
            this._start = this._validateStartAngle();
            this._end = this._validateEndAngle();

            var add = (this._start < this._end) ? 0 : 360;
            this._end += add - this._start;
        },
        _analyzeModelValue: function () {
            var val = this.options.value, min = this.options.min, max = this.options.max, last, t;
            var parts = (typeof val == "string") ? val.split(",") : [val];

            if (this._rangeSlider) {
                if (typeof val == "string") {
                    if (parts.length >= 2) t = (isNumber(parts[0]) ? parts[0] : min) + "," +
                        (isNumber(parts[1]) ? parts[1] : max);
                    else t = isNumber(parts[0]) ? min + "," + parts[0] : min + "," + min;
                }
                else t = isNumber(val) ? min + "," + val : min + "," + min;
            }
            else {
                if (typeof val == "string") last = parts.pop(), t = isNumber(last) ? parseFloat(last) : min;
                else t = isNumber(val) ? parseFloat(val) : min;
            }
            this.options.value = t;
        },
        _validateModelValue: function () {
            var val = this.options.value;
            if (this._rangeSlider) {
                var parts = val.split(","), val1 = parseFloat(parts[0]), val2 = parseFloat(parts[1]);
                val1 = this._limitValue(val1);
                val2 = this._limitValue(val2);
                if (val1 > val2) val2 = val1;

                this._handle1 = this._processStepByValue(val1);
                this._handle2 = this._processStepByValue(val2);
                this.options.value = this._handle1.value + "," + this._handle2.value;
            }
            else {
                var index = this._showRange ? 2 : (this._active || 1);
                this["_handle" + index] = this._processStepByValue(this._limitValue(val));
                if (this._showRange) this._handle1 = this._handleDefaults();
                this.options.value = this["_handle" + index].value;
            }
        },

        // common core methods
        _isBrowserSupported: function () {
            var properties = ["borderRadius", "WebkitBorderRadius", "MozBorderRadius",
	            "OBorderRadius", "msBorderRadius", "KhtmlBorderRadius"];
            for (var i = 0; i < properties.length; i++) {
                if (document.body.style[properties[i]] !== undefined) return true;
            }
        },
        _throwError: function () {
            return "This browser doesn't support the border-radious property.";
        },
        _checkIE: function () {
            var ua = window.navigator.userAgent;
            if (ua.indexOf('MSIE ') >= 0 || ua.indexOf('Trident/') >= 0)
                this.control.css({ "-ms-touch-action": "none", "touch-action": "none" });
        },
        _raise: function (event, args) {
            var fn = this.options[event], val = true;
            args = args || {};
            if (fn) {
                args["type"] = event;
                if (typeof fn === "string") fn = window[fn];
                if ($.isFunction(fn)) {
                    val = fn.call(this, args);
                    val = val === false ? false : val;
                }
            }
            this.control.trigger($.Event ? $.Event(event, args) : event);
            return val;
        },
        _bind: function (element, _event, handler) {
            $(element).bind(_event, $proxy(handler, this));
        },
        _unbind: function (element, _event, handler) {
            if ($.proxy) $(element).unbind(_event, $.proxy(handler, this));
            else $(element).unbind(_event);
        },
        _getInstance: function () {
            return $data(this.control[0], pluginName);
        },
        _removeData: function () {
            var control = this.control[0];
            $.removeData && $.removeData(control, pluginName);
            if (control.id) delete window[control.id];
        },
        _destroyControl: function () {
            this.control.empty().removeClass("rs-control").height("").width("");
            this._removeAnimation();
            this._bindControlEvents("_unbind");
        },

        // methods to dynamic options updation (through option)
        _updateWidth: function () {
            this.lastBlock.css("padding", this.options.width);
            this._refreshHandle();
        },
        _readOnly: function (bool) {
            this._isReadOnly = bool;
            this.container.removeClass("rs-readonly");
            if (bool) this.container.addClass("rs-readonly");
        },

        // get & set for the properties
        _get: function (property) {
            return this.options[property];
        },
        _set: function (property, value) {
            var props = this._props();
            if ($.inArray(property, props.numberType) != -1) {          // to check number datatype
                if (!isNumber(value)) return;
                value = parseFloat(value);
            }
            else if ($.inArray(property, props.booleanType) != -1) {    // to check boolean datatype
                value = (value == "false") ? false : !!value;
            }
            else if ($.inArray(property, props.stringType) != -1) {     // to check input string
                value = value.toLowerCase();
            }

            if (this.options[property] == value) return;
            this.options[property] = value;
            switch (property) {
                case "startAngle":
                case "endAngle":
                    this._validateStartEnd();
                    this._updateSeperator();
                    this._appendOverlay();
                case "min":
                case "max":
                case "step":
                case "value":
                    this._analyzeModelValue();
                    this._validateModelValue();
                    this._setValue();
                    this._updateHidden();
                    this._updateTooltip();
                    break;
                case "radius":
                    this._setRadius();
                    this._updateTooltipPos();
                    break;
                case "width":
                    this._removeAnimation();
                    this._updateWidth();
                    this._updateTooltipPos();
                    this._addAnimation();
                    this.container.children().find(".rs-seperator").css({ "width": this.options.width, "margin-left": this._border() / 2 });
                    break;
                case "handleSize":
                    this._refreshHandle();
                    break;
                case "handleShape":
                    this._setHandleShape();
                    break;
                case "animation":
                    this._setAnimation();
                    break;
                case "showTooltip":
                    this.options.showTooltip ? this._appendTooltip() : this._removeTooltip();
                    break;
                case "editableTooltip":
                    this._tooltipEditable();
                    this._updateTooltipPos();
                    break;
                case "disabled":
                    this.options.disabled ? this.disable() : this.enable();
                    break;
                case "readOnly":
                    this.options.readOnly ? this._readOnly(true) : (!this.options.disabled && this._readOnly(false));
                    break;
                case "mouseScrollAction":
                    this._bindScrollEvents(this.options.mouseScrollAction ? "_bind" : "_unbind");
                    break;
                case "circleShape":
                    this._refreshCircleShape();
                    if (this.options.circleShape == "full") {
                        this.options.startAngle = 0;
                        this.options.endAngle = "+360";
                    }
                case "sliderType":
                    this._destroyControl();
                    this._init();
                    break;
            }
            return this;
        },

        // public methods
        option: function (property, value) {
            if (!this._getInstance() || !this._isBrowserSupport) return;
            if ($isPlainObject(property)) {
                if (property["min"] !== undefined || property["max"] !== undefined) {
                    if (property["min"] !== undefined) {
                        this.options.min = property["min"];
                        delete property["min"];
                    }
                    if (property["max"] !== undefined) {
                        this.options.max = property["max"];
                        delete property["max"];
                    }
                    if (property["value"] == undefined) {
                        this._set("value", this.options.value);
                    }
                }
                for (var prop in property) {
                    this._set(prop, property[prop]);
                }
            }
            else if (property && typeof property == "string") {
                if (value === undefined) return this._get(property);
                this._set(property, value);
            }
            return this;
        },
        getValue: function (index) {
            if (this.options.sliderType == "range" && index && isNumber(index)) {
                var i = parseFloat(index);
                if (i == 1 || i == 2)
                    return this["_handle" + i].value;
            }
            return this._get("value");
        },
        setValue: function (value, index) {
            if (value && isNumber(value)) {
                if (index && isNumber(index)) {
                    if (this.options.sliderType == "range") {
                        var i = parseFloat(index), val = parseFloat(value);
                        if (i == 1) value = val + "," + this._handle2.value;
                        else if (i == 2) value = this._handle1.value + "," + val;
                    }
                    else if (this.options.sliderType == "default") this._active = index;
                }
                this._set("value", value);
            }
        },
        disable: function () {
            this.options.disabled = true;
            this.container.addClass("rs-disabled");
            this._readOnly(true);
        },
        enable: function () {
            this.options.disabled = false;
            this.container.removeClass("rs-disabled");
            if (!this.options.readOnly) this._readOnly(false);
        },
        destroy: function () {
            if (!this._getInstance()) return;
            this._destroyControl();
            this._removeData();
            this._originalObj.insertAfter(this.control);
            this.control.remove();
        }
    };

    $.fn.rsRotate = function (degree) {
        return setTransform(this, degree);
    }

    if (typeof $.fn.outerHeight == "undefined") {
        $.fn.outerHeight = function () { return this[0].offsetHeight; }
        $.fn.outerWidth = function () { return this[0].offsetWidth; }
    }
    if (typeof $.fn.hasClass === "undefined") {
        $.fn.hasClass = function (name) {
            return this[0].className.split(" ").indexOf(name) !== -1;
        }
    }
    if (typeof $.fn.offset === "undefined") {
        $.fn.offset = function () {
            return { left: this[0].offsetLeft, top: this[0].offsetTop };
        }
    }

    function $proxy(fn, that) {
        if (typeof $.proxy === "function") return $.proxy(fn, that);
        return function (e) { fn.call(that, e); };
    }
    function $data(ele, name, data) {
        if (typeof $.data === "function") return $.data(ele, name, data);
        else if (!data) return $(ele).hasClass("rs-control");
    }
    function $isPlainObject(obj) {
        if (typeof $.isPlainObject === "function") return $.isPlainObject(obj);
        else {
            var str = JSON.stringify(obj);
            return typeof obj === "object" && obj.length === undefined &&
            str.length > 2 && str.substr(0, 1) === "{" && str.substr(str.length - 1) === "}";
        }
    }

    //core functions
    function isNumber(number) {
        number = parseFloat(number);
        return typeof number === "number" && !isNaN(number);
    }
    function createElement(tag) {
        var t = tag.split('.');
        return $(document.createElement(t[0])).addClass(t[1] || "");
    }
    function getdistance(p1, p2) {
        return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
    }
    function setTransform(control, value) {
        control.css('-webkit-transform', "rotate(" + value + "deg)");
        control.css('-moz-transform', "rotate(" + value + "deg)");
        control.css('-ms-transform', "rotate(" + value + "deg)");
        control.css('-o-transform', "rotate(" + value + "deg)");
        control.css('transform', "rotate(" + value + "deg)");
        return control;
    }

    // The plugin constructor
    function RoundSlider(control, options) {
        if (control.id) window[control.id] = this;
        this.control = $(control);

        // the options value holds the updated defaults value
        this.options = $.extend({}, this.defaults, options);
        if (this._raise("beforeCreate") !== false) {
            this._init();
            this._raise("create");
        }
        else this._removeData();
    }

    // The plugin wrapper, prevents multiple instantiations
    function CreateRoundSlider(options, args) {

        for (var i = 0; i < this.length; i++) {
            var that = this[i], instance = $data(that, pluginName);
            if (!instance) {
                $data(that, pluginName, new RoundSlider(that, options));
            }
            else if ($isPlainObject(options)) {
                if (typeof instance.option === "function") instance.option(options);
                else if (that.id && window[that.id] && typeof window[that.id].option === "function") {
                    window[that.id].option(options);
                }
            }
            else if (typeof options === "string") {
                if (typeof instance[options] === "function") {
                    if ((options === "option" || (typeof options.startsWith === 'function' && options.startsWith("get"))) && args[2] === undefined) {
                        return instance[options](args[1]);
                    }
                    instance[options](args[1], args[2]);
                }
            }
        }
        return this;
    }

    $.fn[pluginName].prototype = RoundSlider.prototype;

})(jQuery, window);
