define([
    "jquery"
], function (
    jq
     ) {

    "use strict";

    /**
     * @constructor
     */

    return function( options, context, metadata ){
        var sliderElement = null,
            disabledContainer = null,
            thumbWidth = options.thumb.radius * 2,
            thumbHeight = options.thumb.radius * 2,
            trackWidth = metadata.getWidth(),
            trackHeight = options.track.height,
            thumbRadius = options.thumb.radius,
            pointerUpEventNames, pointerDownEventNames, pointerMoveEventNames, pointerLeaveEventNames,
            isMoving = false,
            sliderStyles = null,
            sliderCSS = "",
            allAcceptedSolutions = new Array(),
            elementVariableNameSpace = "com_tts",
            elementVariableId = null,
            elementType = "slider",
            pageEvaluatedId = null,
            answer = null,
            isRight = false,
            isEnabled = true;

        sliderCSS =
            // Track default style
            // webkit (Edge, Google, Safari)
            ".sliderTrackStyle_" + metadata.getId() + "::-webkit-slider-runnable-track {\n" +
            //"margin-top: " + options.trackOffset + "px;\n" +
            "width: " + trackWidth + "px;\n" +
            "height: " + trackHeight + "px;\n" +
            "background-color: " + asRgbaValue(options.track.color.normal, options.track.transparency) + ";\n" +
            "}\n" +
            // moz (Firefox)
            ".sliderTrackStyle_" + metadata.getId() + "::-moz-range-track {\n" +
            //"margin-top: " + options.trackOffset + "px;\n" +
            "width: " + trackWidth + "px;\n" +
            "height: " + trackHeight + "px;\n" +
            "background-color: " + asRgbaValue(options.track.color.normal, options.track.transparency) + ";\n" +
            "}\n" +
            // ms (Internet Explorer 10+)
            ".sliderTrackStyle_" + metadata.getId() + "::-ms-track {\n" +
            //"margin-top: " + options.trackOffset + "px;\n" +
            "color: transparent;\n" +
            "background: transparent;\n" +
            "border: 0;\n" +
            "width: " + trackWidth + "px;\n" +
            "height: " + trackHeight + "px;\n" +
            "background-color: " + asRgbaValue(options.track.color.normal, options.track.transparency) + ";\n" +
            "}\n" +
            ".sliderTrackStyle_" + metadata.getId() + "::-ms-fill-lower {\n" +
            "background: transparent;\n" +
            "}\n" +

            // Track hover style
            // webkit (Edge, Google, Safari)
            ".sliderTrackHoverStyle_" + metadata.getId() + "::-webkit-slider-runnable-track:hover {\n" +
            "background-color: " + asRgbaValue(options.track.color.hover, options.track.transparency) + ";\n" +
            //"background-repeat: no-repeat;\n" +
            "}\n" +
            // moz (Firefox)
            ".sliderTrackHoverStyle_" + metadata.getId() + "::-moz-range-track:hover {\n" +
            "background-color: " + asRgbaValue(options.track.color.hover, options.track.transparency) + ";\n" +
            //"background-repeat: no-repeat;\n" +
            "}\n" +
            // ms (Internet Explorer 10+)
            ".sliderTrackHoverStyle_" + metadata.getId() + "::-ms-track:hover {\n" +
            "background-color: " + asRgbaValue(options.track.color.hover, options.track.transparency) + ";\n" +
            //"background-repeat: no-repeat;\n" +
            "}\n" +

            // Thumb default style
            // webkit (Edge, Google, Safari)
            ".sliderThumbStyle_" + metadata.getId() + "::-webkit-slider-thumb {\n" +
            "-webkit-appearance: none;\n" +
            //"margin-top: " + options.thumbOffset + "px;\n" +
            "margin-top: " + ((trackHeight / 2) - (thumbHeight / 2)) + "px;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.normal, 1) + ";\n" +
            "cursor: pointer;\n" +
            "}\n" +
            // moz (Firefox)
            ".sliderThumbStyle_" + metadata.getId() + "::-moz-range-thumb {\n" +
            "-moz-appearance: none;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.normal, 1) + ";\n" +
            "cursor: pointer;\n" +
            "}\n" +
            // ms (Internet Explorer 10+)
            ".sliderThumbStyle_" + metadata.getId() + "::-ms-thumb {\n" +
            "appearance: none;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.normal, 1) + ";\n" +
            "cursor: pointer;\n" +
            "}\n" +

            // Thumb hover style
            // webkit (Edge, Google, Safari)
            ".sliderThumbHoverStyle_" + metadata.getId() + "::-webkit-slider-thumb:hover {\n" +
            "background-color: " + asRgbaValue(options.thumb.color.hover, 1) + ";\n" +
            "cursor: pointer;\n" +
            "}\n" +
            // moz (Firefox)
            ".sliderThumbHoverStyle_" + metadata.getId() + "::-moz-range-thumb:hover {\n" +
            "background-color: " + asRgbaValue(options.thumb.color.hover, 1) + ";\n" +
            "cursor: pointer;\n" +
            "}\n" +
            // ms (Internet Explorer 10+)
            ".sliderThumbHoverStyle_" + metadata.getId() + "::-ms-thumb:hover {\n" +
            "background-color: " + asRgbaValue(options.thumb.color.hover, 1) + ";\n" +
            "cursor: pointer;\n" +
            "}\n" +

            // Thumb style (Correct)
            // webkit (Edge, Google, Safari)
            ".sliderThumbCorrectStyle_" + metadata.getId() + "::-webkit-slider-thumb {\n" +
            "-webkit-appearance: none;\n" +
            "appearance: none;\n" +
            "margin-top: " + ((trackHeight / 2) - (thumbHeight / 2)) + "px;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.correct, 1) + ";\n" +
            "cursor: default;\n" +
            "}\n" +
            // moz (Firefox)
            ".sliderThumbCorrectStyle_" + metadata.getId() + "::-moz-range-thumb {\n" +
            "-moz-appearance: none;\n" +
            "border: none;\n" +
            "border-radius: 0;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.correct, 1) + ";\n" +
            "cursor: default;\n" +
            "}\n" +
            // ms (Internet Explorer 10+)
            ".sliderThumbCorrectStyle_" + metadata.getId() + "::-ms-thumb {\n" +
            "appearance: none;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.correct, 1) + ";\n" +
            "cursor: default;\n" +
            "}\n" +

            // Thumb style (Incorrect)
            // webkit (Edge, Google, Safari)
            ".sliderThumbIncorrectStyle_" + metadata.getId() + "::-webkit-slider-thumb {\n" +
            "-webkit-appearance: none;\n" +
            "appearance: none;\n" +
            "margin-top: " + ((trackHeight / 2) - (thumbHeight / 2)) + "px;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.incorrect, 1) + ";\n" +
            "cursor: default;\n" +
            "}\n" +
            // moz (Firefox)
            ".sliderThumbIncorrectStyle_" + metadata.getId() + "::-moz-range-thumb {\n" +
            "-moz-appearance: none;\n" +
            "appearance: none;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.incorrect, 1) + ";\n" +
            "cursor: default;\n" +
            "}\n" +
            // ms (Internet Explorer 10+)
            ".sliderThumbIncorrectStyle_" + metadata.getId() + "::-ms-thumb {\n" +
            "appearance: none;\n" +
            "width: " + thumbWidth + "px;\n" +
            "height: " + thumbHeight + "px;\n" +
            "border: none;\n" +
            "border-radius: " + thumbRadius + "px;\n" +
            "background-color: " + asRgbaValue(options.thumb.color.incorrect, 1) + ";\n" +
            "cursor: default;\n" +
            "}";

        function asRgbaValue(colorValue, transparencyValue) {

            var color = hexToRgb(colorValue),
                alpha = asAlpha(transparencyValue);

            return "rgba(" + color.r + ", " + color.g + ", " + color.b + ", " + alpha + ")";
        }

        function hexToRgb(hex) {
            var parsingResult = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
            return parsingResult ? {
                r: parseInt(parsingResult[1], 16),
                g: parseInt(parsingResult[2], 16),
                b: parseInt(parsingResult[3], 16)
            } : {
                r: 0,
                g: 0,
                b: 0
            };
        }

        function asAlpha(percentValue) {
            return (100 - percentValue) / 100;
        }

        function createSlider() {
            sliderStyles = document.createElement('style');
            sliderStyles.id = "sliderStyleClasses_" + metadata.getId();
            sliderStyles.type = "text/css";
            sliderStyles.innerHTML = sliderCSS;
            injectCSS();

            sliderElement = document.createElement('input');
            sliderElement.id = "sliderElement_" + metadata.getId();
            sliderElement.type = 'range';
            sliderElement.min = options.minValue;
            sliderElement.max = options.maxValue;
            sliderElement.value = options.startValue;
            sliderElement.step = options.stepValue;
            sliderElement.style.width = trackWidth + "px";
            sliderElement.style.height = trackHeight >= (thumbRadius * 2) ? trackHeight + "px" : (thumbRadius * 2) + "px";
            sliderElement.style.position = "absolute";
            sliderElement.style.top = "0px";
            sliderElement.style.left = "0px";
            sliderElement.style.margin = "0";
            sliderElement.style.padding = "0";
            sliderElement.style.WebkitAppearance = "none";
            sliderElement.style.backgroundColor = "transparent";
            sliderElement.classList.add("sliderTrackStyle_" + metadata.getId());
            sliderElement.classList.add("sliderThumbStyle_" + metadata.getId());
            sliderElement.classList.add("sliderTrackHoverStyle_" + metadata.getId());
            sliderElement.classList.add("sliderThumbHoverStyle_" + metadata.getId());

            disabledContainer = document.createElement("div");
            disabledContainer.style.position = "absolute";
            disabledContainer.style.top = "0px";
            disabledContainer.style.left = "2px";
            disabledContainer.style.width = trackWidth + "px";
            disabledContainer.style.height = thumbHeight + "px";
            disabledContainer.style.display = "none";

            sliderElement.appendChild(disabledContainer);

            sliderElement.style.transform = "rotate(" + options.rotation + "deg)";

            getAllAcceptedSolutions();

            elementVariableId = "slider" + metadata.getId();

            if (!(options.isInAuthoringMode) && options.isQuestionPage) {
                context.page.quiz.add(options.instance, options.groupId + elementType, true);
            }
        }

        function getAllAcceptedSolutions() {
            var acceptedSolutions = (options.solutionRange||"").trim();

            acceptedSolutions = acceptedSolutions.split(";");

            for (var i = 0; i < acceptedSolutions.length; i++) {
                if (acceptedSolutions[i].toString().indexOf('-') == -1) {
                    allAcceptedSolutions.push(parseFloat(acceptedSolutions[i]));
                } else {
                    allAcceptedSolutions.push(acceptedSolutions[i]);
                }
            }
        }

        function updateSliderValue() {
            if (isEvaluated()) {
                return;
            }
            setUserAnswer(getCurrentAnswer());

            var values = {};

            values.id = options.id;
            values.value = answer;
            context.page.event.fire(options.instance, "onSliderValueChange", values);
        }

        function getRange(minValue, maxValue) {
            if (minValue > maxValue) {
                return minValue - maxValue;
            } else {
                return maxValue - minValue;
            }
        }

        function getCurrentAnswer() {
            return parseFloat(sliderElement.value);
        }

        function switchToUserSolution() {
            disableSlider();

            if (options.isStandardPlayer) {
                context.stage.variable.set(pageEvaluatedId, true, elementVariableNameSpace);
            }

            getSavedAnswer();

            sliderElement.classList.add("sliderTrackStyle_" + metadata.getId());

            if (isRight) {
                sliderElement.classList.remove("sliderThumbStyle_" + metadata.getId());
                sliderElement.classList.add("sliderThumbCorrectStyle_" + metadata.getId());
            } else {
                sliderElement.classList.remove("sliderThumbStyle_" + metadata.getId());
                sliderElement.classList.add("sliderThumbIncorrectStyle_" + metadata.getId());
            }
        }

        function switchToBestPracticeSolution() {
            disableSlider();

            sliderElement.classList.remove("sliderThumbCorrectStyle_" + metadata.getId());
            sliderElement.classList.remove("sliderThumbIncorrectStyle_" + metadata.getId());
            sliderElement.classList.add("sliderThumbStyle_" + metadata.getId());

            sliderElement.value = options.sampleSolution;
        }

        function disableSlider() {
            if (!isEnabled) {
                return;
            }

            sliderElement.classList.remove("sliderTrackHoverStyle_" + metadata.getId(), "sliderThumbHoverStyle_" + metadata.getId());
            sliderElement.disabled = true;
            disabledContainer.style.display = "block";
            isEnabled = false;
        }

        function lockNavigation() {
            if (options.isInAuthoringMode || !(options.hasStageTriggerSupport)) {
                return;
            }
            context.stage.trigger.lock(metadata.getId());
        }

        function unlockNavigation() {
            if (options.isInAuthoringMode || !(options.hasStageTriggerSupport)) {
                return;
            }
            context.stage.trigger.unlock(metadata.getId());
        }

        function initPointerEventNames() {
            if (!pointerUpEventNames) {
                var nodeName = getHotspotElementNodeName();
                pointerDownEventNames = context.device.getDownEvents(nodeName);
                pointerUpEventNames = context.device.getUpEvents(nodeName);
                pointerMoveEventNames = context.device.getMoveEvents(nodeName);
                pointerLeaveEventNames = context.device.getReleaseEvents(nodeName);
            }
        }

        function getHotspotElementNodeName(e) {
            return sliderElement.nodeName
                ? sliderElement.nodeName
                : "document";
        }

        function bindEvents() {
            if (options.isInAuthoringMode) {
                return;
            }

            removeEvents();

            if (isIOS()) {
                context.device.attachFirstSupportedEvent(sliderElement, pointerDownEventNames, slideReady);
                context.device.attachFirstSupportedEvent(sliderElement, pointerUpEventNames, slideFinish);

            } else {
                context.device.attachFirstSupportedEvent(sliderElement, pointerDownEventNames, onMouseDown);
                context.device.attachFirstSupportedEvent(sliderElement, pointerUpEventNames, onMouseUp);
                sliderElement.addEventListener("change", updateSliderValue);
            }
        }

        function removeEvents() {
            unlockNavigation();
            if (!sliderElement) {
                return;
            }

            initPointerEventNames();

            if (isIOS()) {
                context.device.removeFirstSupportedEvent(sliderElement, pointerDownEventNames, slideReady);
                context.device.removeFirstSupportedEvent(sliderElement, pointerUpEventNames, slideFinish);
            } else {
                context.device.removeFirstSupportedEvent(sliderElement, pointerDownEventNames, onMouseDown);
                context.device.removeFirstSupportedEvent(sliderElement, pointerUpEventNames, onMouseUp);
                sliderElement.removeEventListener("change", updateSliderValue);
            }
        }

        function onMouseDown(e) {
            context.device.cancelEvent();
            lockNavigation();
        }

        function onMouseUp(e) {
            context.device.cancelEvent();
            unlockNavigation();
        }

        function slideReady() {
            lockNavigation();

            isMoving = true;
            context.device.attachFirstSupportedEvent(sliderElement, pointerMoveEventNames, slideMove);
        }

        function slideFinish() {
            unlockNavigation();

            isMoving = false;
            context.device.removeFirstSupportedEvent(sliderElement, pointerMoveEventNames, slideMove);
            updateSliderValue();
        }

        function slideMove(e) {
            if (isMoving == 0) {
                return false;
            }

            var cursorPosition = getCursorPositionToSliderElement(e),
                stageZoom = context.stage.getZoom(),
                stageZoomedCursorPosition = {
                    x: cursorPosition.x + (cursorPosition.x * (1 - stageZoom)),
                    y: cursorPosition.y + (cursorPosition.y * (1 - stageZoom))
                },
                range = getRange(options.minValue, options.maxValue),
                sliderElementBoundingClientRect = sliderElement.getBoundingClientRect(),
                scaling = {
                    x: range / sliderElementBoundingClientRect.width,
                    y: range / sliderElementBoundingClientRect.height
                },
                rotationInDegree = options.rotation % 360,
                newValue;

            // prevent cursor position to be outside the slider element
            if (stageZoomedCursorPosition.x < 0) {
                stageZoomedCursorPosition.x = 0;
            }

            if (stageZoomedCursorPosition.y < 0) {
                stageZoomedCursorPosition.y = 0;
            }

            if (stageZoomedCursorPosition.x > sliderElementBoundingClientRect.width) {
                stageZoomedCursorPosition.x = sliderElementBoundingClientRect.width;
            }

            if (stageZoomedCursorPosition.y > sliderElementBoundingClientRect.height) {
                stageZoomedCursorPosition.y = sliderElementBoundingClientRect.height;
            }

            // differentiate according to angle degree
            if (rotationInDegree == 0) {
                newValue = (stageZoomedCursorPosition.x * scaling.x) + options.minValue;
                sliderElement.value = newValue;
                return;
            }

            if (rotationInDegree > 0 && rotationInDegree < 90) {
                newValue = (stageZoomedCursorPosition.x * scaling.x) + options.minValue;
                sliderElement.value = newValue;
                return;
            }

            if (rotationInDegree == 90) {
                newValue = (stageZoomedCursorPosition.y * scaling.y) + options.minValue;
                sliderElement.value = newValue;
                return;
            }

            if (rotationInDegree > 90 && rotationInDegree < 180) {
                newValue = options.maxValue - (stageZoomedCursorPosition.x * scaling.x);
                sliderElement.value = newValue;
                return;
            }

            if (rotationInDegree == 180) {
                newValue = options.maxValue - (stageZoomedCursorPosition.x * scaling.x);
                sliderElement.value = newValue;
                return;
            }

            if (rotationInDegree > 180 && rotationInDegree < 270) {
                newValue = options.maxValue - (stageZoomedCursorPosition.x * scaling.x);
                sliderElement.value = newValue;
                return;
            }

            if (rotationInDegree == 270) {
                newValue = options.maxValue - (stageZoomedCursorPosition.y * scaling.y);
                sliderElement.value = newValue;
                return;
            }

            if (rotationInDegree > 270 && rotationInDegree < 360) {
                newValue = (stageZoomedCursorPosition.x * scaling.x) + options.minValue;
                sliderElement.value = newValue;
                return;
            }
        }

        function getCursorPositionToSliderElement(e) {
            var sliderElementOffset,
                result = {
                    x: 0,
                    y: 0
                };

            if (isIOS()) {
                e = e.originalEvent.touches[0];
            } else {
                e = e || window.event;
            }

            sliderElementOffset = jq(sliderElement).offset();
            result.x = e.pageX - sliderElementOffset.left;
            result.y = e.pageY - sliderElementOffset.top;
            result.x = result.x - window.pageXOffset;
            result.y = result.y - window.pageYOffset;

            return result;
        }

        function isIOS() {
            return (/iPad|iPhone|iPod/.test(navigator.userAgent)) || (navigator.platform === 'iPad' || navigator.platform === 'iPhone' || navigator.platform === 'iPod') || ('ontouchstart' in window) || (navigator.MaxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);
        }

        function isEvaluated() {

            if (options.isInPreviewMode) {
                return false;
            }

            if (options.isStandardPlayer || options.isInLearningMode) {
                return context.stage.variable.get(pageEvaluatedId, elementVariableNameSpace);
            }

            return context.page.quiz.hasQuestion() && context.page.quiz.getQuestion().isFinalized();
        }

        function getSavedAnswer() {
            answer = parseFloat(context.stage.variable.get(elementVariableId, elementVariableNameSpace));

            if (!isNaN(answer)) {
                sliderElement.value = answer;
                isRight = isCorrect(answer);
            } else {
                sliderElement.value = parseFloat(options.sampleSolution);
                isRight = true;
            }
        }

        function setSavedAnswer(value) {
            if (!isNaN(value)) {
                context.stage.variable.set(elementVariableId, parseFloat(value), elementVariableNameSpace);
            } else {
                context.stage.variable.set(elementVariableId, false, elementVariableNameSpace);
            }
        }

        function setUserAnswer(value) {
            answer = value;
            isRight = isCorrect(answer);
            setSavedAnswer(answer);

            if (options.isQuestionPage) {
                context.page.quiz.setSelected(options.instance, isRight);
            }
        }

        function isCorrect(value) {
            for (var i = 0; i < allAcceptedSolutions.length; i++) {
                if (typeof allAcceptedSolutions[i] != "number") {
                    var range = allAcceptedSolutions[i].split("-");

                    if (parseFloat(range[0]) <= value && value <= parseFloat(range[1])) {
                        return true;
                    }
                } else {
                    if (parseFloat(allAcceptedSolutions[i]) == value) {
                        return true;
                    }
                }
            }

            return false;
        }

        function injectCSS() {
            var css = document.getElementById('sliderStyleClasses_' + metadata.getId());

            if (!css) {
                document.getElementsByTagName('head')[0].appendChild(sliderStyles);
            }
        }

        function removeCSS() {
            var css = document.getElementById('sliderStyleClasses_' + metadata.getId());

            if (css) {
                document.getElementById('sliderStyleClasses_' + metadata.getId()).remove();
            }
        }

        return {
            build : function(){
                createSlider();
                return {
                    getElement: function () {
                        return sliderElement;
                    },
                    getDisabledElement: function () {
                        return disabledContainer;
                    },
                    bindEvents : function(){
                        bindEvents();
                    },
                    removeEvents: function () {
                        removeEvents();
                    },
                    onEnterPage: function () {
                        pageEvaluatedId = metadata.page.getId() + "_evaluated";
                        if (options.isStandardPlayer) {
                            injectCSS();
                        }
                    },
                    onExitPage: function () {
                        if (options.isStandardPlayer) {
                            removeCSS();
                        }
                    },
                    onAllComponentsReady: function () {
                        updateSliderValue();
                    },
                    onUserSolution: function () {
                        switchToUserSolution();
                    },
                    onBestPracticeSolution: function () {
                        switchToBestPracticeSolution();
                    }
                }
            }
        }
    }
});
