<?xml version="1.0" encoding="UTF-8" standalone="no"?><addon xmlns="http://icplayer.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Image_Viewer_Public" xsi:schemaLocation="http://icplayer.com ../../tools/addon.xsd">
	<model>
		<property name="Image" nameLabel="Image_Viewer_Public_property_image" type="image"/>
		<property name="Frames" nameLabel="Image_Viewer_Public_property_frames" type="string"/>
		<property name="Frame size" nameLabel="Image_Viewer_Public_property_frame_size" type="{Original, Keep aspect ratio, Stretch}"/>
		<property name="Sounds" nameLabel="Image_Viewer_Public_property_sounds" type="list">
			<property name="MP3 sound" nameLabel="Image_Viewer_Public_property_mp3_sound" type="audio"/>
			<property name="OGG sound" nameLabel="Image_Viewer_Public_property_ogg_sound" type="audio"/>
			<property name="AAC sound" nameLabel="Image_Viewer_Public_property_aac_sound" type="audio"/>
		</property>
		<property name="Frame names" nameLabel="Image_Viewer_Public_property_frame_names" type="list">
			<property name="name" nameLabel="Image_Viewer_Public_property_name" type="string"/>
			<property name="frame" nameLabel="Image_Viewer_Public_property_frames" type="string"/>
		</property>
		<property name="Alternative texts" nameLabel="Image_Viewer_Public_property_alternative_texts" type="list">
			<property name="Alternative text" nameLabel="Image_Viewer_Public_property_alternative_text" type="text"/>
			<property name="frame" nameLabel="Image_Viewer_Public_property_frames" type="string"/>
		</property>
		<property name="isClickDisabled" nameLabel="Image_Viewer_Public_property_is_click_disabled" type="boolean"/>
		<property name="Labels" nameLabel="Image_Viewer_Public_property_labels" type="list">
			<property isLocalized="true" name="Text" nameLabel="Image_Viewer_Public_property_text" type="string"/>
			<property name="Top" nameLabel="Image_Viewer_Public_property_top" type="string"/>
			<property name="Left" nameLabel="Image_Viewer_Public_property_left" type="string"/>
			<property name="Frames" nameLabel="Image_Viewer_Public_property_frames" type="string"/>
		</property>
		<property name="Show watermark" nameLabel="Image_Viewer_Public_property_show_watermark" type="boolean"/>
		<property name="Watermark color" nameLabel="Image_Viewer_Public_property_watermark_color" type="string"/>
		<property name="Watermark opacity" nameLabel="Image_Viewer_Public_property_watermark_opacity" type="string"/>
		<property name="Watermark size" nameLabel="Image_Viewer_Public_property_watermark_size" type="string"/>
		<property name="Show frame" nameLabel="Image_Viewer_Public_property_show_frame" type="string"/>
		<property name="Animation" nameLabel="Image_Viewer_Public_property_animation" type="{None, Linear, Fading}"/>
		<property name="Correct frames" nameLabel="Image_Viewer_Public_property_correct_frames" type="string"/>
		<property name="Do not reset" nameLabel="Image_Viewer_Public_property_do_not_reset" type="boolean"/>
		<property name="Random frame" nameLabel="Image_Viewer_Public_property_random_frame" type="boolean"/>
		<property name="Initial frame" nameLabel="Image_Viewer_Public_property_initial_frame" type="string"/>
		<property name="Show frame counter" nameLabel="Image_Viewer_Public_property_show_frame_counter" type="boolean"/>
		<property name="langAttribute" nameLabel="Image_Viewer_Public_property_lang_attribute" type="string"/>
		<property name="Base width" nameLabel="Image_Viewer_Public_property_base_width" type="string"/>
		<property name="Base height" nameLabel="Image_Viewer_Public_property_base_height" type="string"/>
	</model>
<css>.image-viewer {
    border: none;
    padding: 0;
    margin: 0;
    float: left;
    overflow: hidden;
    background: no-repeat 0 0;
    -ms-touch-action: none;
}

.image-viewer-audio-wrapper {
    display: none;
}

.image-viewer-audio-wrapper audio {
    display: inherit;
}

.image-viewer-hidden-image {
    display: none;
}

.image-viewer-watermark,
.image-viewer-loading-image {
    border: 0;
    margin: 0;
    padding: 0;
    display: none;
    position: absolute;
}

.image-viewer-label {
    border: 0;
    margin: 0;
    padding: 0;
    position: absolute;
}

.image-viewer-loading-image {
    z-index: 10;
    width: 50px;
    height: 50px;
}

.image-viewer-watermark {
    display: block;
}

.frame-counter-wrapper {
    position: absolute;
    bottom: 5px;
    height: 10px;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
}

.frame-counter {
    height: 100%;
    margin-left: auto;
    margin-right: auto;
    background: none;
    display: table;
}

.dot {
    float: left;
    width: 10px;
    height: 10px;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    border-radius: 5px;
    border: 1px solid black;
    background-color: #f0f8ff;
    margin: 0 1px;
    cursor: pointer;
}

.dot.current {
    background-color: #696969;
    cursor: default;
}
</css><view>&lt;div class='image-viewer'&gt;
    &lt;div class="image-viewer-helper"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img src="" alt="Loading..." class="image-viewer-loading-image"/&gt;
&lt;div class="image-viewer-watermark"&gt;&lt;/div&gt;
&lt;div class="frame-counter-wrapper"&gt;
    &lt;div class="frame-counter"&gt;
        &lt;div class="dot current"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</view><preview>&lt;div class='image-viewer'&gt;
    &lt;div class="image-viewer-helper"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img src="/media/images/loading.gif" alt="Loading..." class="image-viewer-loading-image"/&gt;
&lt;div class="image-viewer-watermark"&gt;&lt;/div&gt;
&lt;div class="frame-counter-wrapper"&gt;
    &lt;div class="frame-counter"&gt;
        &lt;div class="dot current"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</preview><presenter>function AddonImage_Viewer_Public_create() {
    var presenter = function () {};

    var playerController = null;
    var watermarkElement;
    var loadingScreen = {
        element: null,
        counter: 0
    };
    var audioElements = [];

    presenter.addonId = null;
    presenter.upgradedModel = null;

    presenter.lastMovePosition = null;
    presenter.totalPercentage = 0;
    presenter.lastReceivedEvent = null;
    presenter.isMouseDown = false;
    presenter.eventBus = null;
    presenter.isWCAGSelected = false;

    presenter.ERROR_CODES = {
        'IM_01': "Image must be uploaded to display Addon!",
        'FN_01': "Frame number must be set to properly display Addon",
        'FN_02': "Frame number is not a number!",
        'FN_03': "Frame number must be a positive integer!",
        'FN_04': "Frame name cannot be empty!",
        'FN_05': "Frame names - frame number must be between 1 and frames count",
        'LA_01': "Label text cannot be empty!",
        'LA_02': "Label top value incorrect!",
        'LA_03': "Label left value incorrect!",
        'LA_04': "All label fields must be filled correctly!",
        'FL_01': "Label must be assigned to at least one frame!",
        'FL_02': "Frames list syntax incorrect! Check for separators!",
        'FL_03': "Frame number is invalid!",
        'FL_04': "Frame number missing inside list!",
        'FL_05': "Frame numbers range incorrect!",
        'WM_01': "Watermark color must be provided in #RRGGBB format!",
        'WM_02': "Watermark opacity must be a value from 0.0 to 1.0!",
        'WM_03': "Watermark size must be a positive integer number!",
        'CF_01': "Correct frame number must be between 1 and frames count!",
        'IF_01': "Initial frame is out of range. Please choose number between 1 and frames count.",
        'ID_01': "Base width and height must be either positive integers or empty"
    };

    presenter.FRAME_SIZE = {
        'Original': 'ORIGINAL',
        'Keep aspect ratio': 'SCALED',
        'Stretch': 'STRETCHED',
        DEFAULT: 'Original'
    };

    presenter.ANIMATION = {
        'None': 'NONE',
        'Linear': 'LINEAR',
        'Fading': 'FADING',
        DEFAULT: 'None'
    };

    presenter.configuration = {};

    presenter.changeFrameData = {
        isPreview: false,
        isReverseOrder: false,
        triggerEvent: false
    };

    presenter.upgradeModel = function (model) {
        var upgradedModel = presenter.upgradeFrom_01(model);
        upgradedModel =  presenter.upgradeFrom_02(upgradedModel);
        upgradedModel = presenter.upgradeAddAltTexts(upgradedModel);
        upgradedModel = presenter.upgradeAddBaseDimensions(upgradedModel);
        return upgradedModel;
    };

    presenter.upgradeFrom_01 = function (model) {
        var upgradedModel = {};
        $.extend(true, upgradedModel, model); // Deep copy of model object

        if (!upgradedModel["Do not reset"]) {
            upgradedModel["Do not reset"] = false;
        }

        return upgradedModel;
    };

    presenter.upgradeFrom_02 = function (model) {
        var upgradedModel = {};
        $.extend(true, upgradedModel, model); // Deep copy of model object

        if (!upgradedModel["Random frame"]) {
            upgradedModel["Random frame"] = false;
        }

        if (!upgradedModel["Initial frame"]) {
            upgradedModel["Initial frame"] = "";
        }

        return upgradedModel;
    };

    presenter.upgradeAddAltTexts = function (model) {
        var upgradedModel = {};
        $.extend(true, upgradedModel, model);

        if (!upgradedModel["langAttribute"]) {
            upgradedModel["langAttribute"] = "";
        }

        if (!upgradedModel["Alternative texts"]) {
            upgradedModel["Alternative texts"] =
                [{
                    "Alternative text": "",
                    "frame": ""
                }];
        }

        return upgradedModel;
    };

    presenter.upgradeAddBaseDimensions = function (model) {
        var upgradedModel = {};
        $.extend(true, upgradedModel, model); // Deep copy of model object

        if (!upgradedModel["Base width"]) {
            upgradedModel["Base width"] = "";
        }

        if (!upgradedModel["Base height"]) {
            upgradedModel["Base height"] = "";
        }

        return upgradedModel;
    };

    function showErrorMessage(element, errorCode) {
        $(element).html(presenter.ERROR_CODES[errorCode]);
    }

    function setContainerDimensions(wrapper) {
        var elementReducedSize = DOMOperationsUtils.calculateReducedSize(wrapper, presenter.$element);
        $(presenter.$element).css({
            width: elementReducedSize.width,
            height: elementReducedSize.height
        });

        presenter.configuration.containerDimensions = {
            width: elementReducedSize.width,
            height: elementReducedSize.height
        };
    }

    function loadSounds() {
        // Check for browser audio tag support
        if (!buzz.isSupported()) {
            return;
        }

        buzz.defaults.autoplay = false;
        buzz.defaults.loop = false;
        showLoadingScreen();

        for (var i = 0; i &lt; presenter.configuration.frames; i++) {
            if ((i &gt; presenter.configuration.sounds.length - 1 ) || presenter.configuration.sounds[i].isEmpty) {
                audioElements[i] = null;
            } else {
                if (presenter.configuration.sounds[i].MP3 !== "" &amp;&amp; buzz.isMP3Supported()) {
                    audioElements[i] = new buzz.sound(presenter.configuration.sounds[i].MP3);
                } else if (presenter.configuration.sounds[i].OGG !== "" &amp;&amp; buzz.isOGGSupported()) {
                    audioElements[i] = new buzz.sound(presenter.configuration.sounds[i].OGG);
                } else {
                    audioElements[i] = new buzz.sound(presenter.configuration.sounds[i].AAC);
                }

                audioElements[i].load();
            }
        }

        hideLoadingScreen();
    }

    function hideWatermarkIfVisible() {
        if ($(watermarkElement).is(':visible')) {
            $(watermarkElement).hide();
        }
    }

    function showWatermarkIfNotVisible() {
        if ($(watermarkElement).not(':visible')) {
            $(watermarkElement).show();
        }
    }

    function clickHandler(e) {
        if (e)
            e.stopPropagation();

        if (presenter.configuration.isClickDisabled) return;
        if (presenter.configuration.isErrorMode &amp;&amp; presenter.configuration.correctFrames.isExerciseMode) return;
        if(presenter.isShowAnswersActive) return;

        if (presenter.mouseData.isMouseDragged) {
            presenter.mouseData.isMouseDragged = false;
            return;
        }

        hideWatermarkIfVisible();

        presenter.next();
    }

    function isMoreThanOneFingerGesture(event) {
        var touch, touchPoints = (typeof event.changedTouches != 'undefined') ? event.changedTouches : [event];
        if (event.hasOwnProperty('touches'))
            touchPoints = event.touches
        return touchPoints.length&gt; 1;
    }

    function touchStartCallback (event) {
        if (isMoreThanOneFingerGesture(event)) return;

        var touchPoints = (typeof event.changedTouches != 'undefined') ? event.changedTouches : [event];

        if (event.hasOwnProperty('touches'))
            touch = event.touches[0]
        else
            touch = touchPoints[0];
        mouseDownCallback(touch);
    }

    function getFixedBackgroundPosition() {
        return parseInt(presenter.$element.css('backgroundPosition'), 10);
    }

    function mouseDownCallback(event) {
        if (presenter.configuration.isClickDisabled) return;
        if (presenter.configuration.isErrorMode &amp;&amp; presenter.configuration.correctFrames.isExerciseMode) return;
        if(presenter.isShowAnswersActive) return;

        presenter.mouseData.isMouseDown = true;
        presenter.mouseData.oldPosition.x = event.pageX;
        presenter.mouseData.oldPosition.y = event.pageY;
    }

    function touchEndCallback () {
        mouseUpCallback();
    }

    function getRequiredShift() {
        return (presenter.$element.width() / 100) * 30; // 30% of frame width
    }

    function mouseUpCallback() {
        if (presenter.configuration.isClickDisabled) return;
        if (presenter.configuration.isErrorMode &amp;&amp; presenter.configuration.correctFrames.isExerciseMode) {
            presenter.mouseData.isMouseDown = false;
            delete presenter.mouseData.originalBackgroundPosition;
            return;
        }
        if(presenter.isShowAnswersActive) return;

        var originalAnimation = presenter.configuration.animation;
        presenter.configuration.animation = presenter.ANIMATION.Linear;

        if (presenter.mouseData.isMouseDragged) {
            var currentBackgroundPosition = getFixedBackgroundPosition(),
                shift = Math.abs(currentBackgroundPosition - presenter.mouseData.originalBackgroundPosition),
                isNextFrameShift = currentBackgroundPosition - presenter.mouseData.originalBackgroundPosition &lt; 0;

            // Restoring percentage calculation of background position
            var newPosition = Math.abs(currentBackgroundPosition) / (presenter.configuration.backgroundImageWidth - presenter.$element.width());
            newPosition *= 100;
            presenter.$element.css('backgroundPosition', newPosition + '%');

            if (shift &gt; getRequiredShift()) {
                var currentFrame = presenter.configuration.currentFrame;
                var framesCount = presenter.configuration.frames;

                if (isNextFrameShift) {
                    if (currentFrame &lt; framesCount - 1) {
                        presenter.next();
                    } else {
                        presenter.configuration.currentFrame = 0;
                        presenter.$element.css({
                            'background-position':'-' + presenter.configuration.frameWidthPercentage + '%'
                        });
                        presenter.$element.animate({
                            'background-position':'0%'
                        }, 500, "linear");
                        presenter.changeFrameLogic(false, true);
                    }
                } else {
                    if (currentFrame !== 0) {
                        presenter.previous();
                    } else {
                        presenter.configuration.currentFrame = framesCount - 1;
                        presenter.$element.css({
                            'background-position':(100 + presenter.configuration.frameWidthPercentage) + '%'
                        });
                        presenter.$element.animate({
                            'background-position':'100%'
                        }, 500, "linear")
                        presenter.changeFrameLogic(false, true);
                    }
                    presenter.configuration.currentFrame = currentFrame &gt; 1 ? currentFrame - 1 : framesCount - 1;
                }

            } else {
                var isReverseOrder = presenter.configuration.currentFrame === 0;
                presenter.changeFrame(false, isReverseOrder, false, false);
            }
        }

        presenter.configuration.animation = originalAnimation;
        presenter.mouseData.isMouseDown = false;
        delete presenter.mouseData.originalBackgroundPosition;

        if (!MobileUtils.isWindowsMobile(window.navigator) &amp;&amp; presenter.isMobileDevice) {
            presenter.mouseData.isMouseDragged = false;
        }
        if (MobileUtils.isWindowsMobile(window.navigator) &amp;&amp; shift &lt;= getRequiredShift()) {
            presenter.mouseData.isMouseDragged = false;
            clickHandler(false);
            presenter.mouseData.isMouseDragged = true;
        }
    }

    function touchMoveCallback (event) {
        if (isMoreThanOneFingerGesture(event)) return;

        event.preventDefault();

        var touch;
        var touchPoints = (typeof event.changedTouches != 'undefined') ? event.changedTouches : [event];

        if (event.hasOwnProperty('touches'))
            touch = event.touches[0]
        else
            touch = touchPoints[0];
        mouseMoveCallback(touch);
    }

    function mouseMoveCallback(event) {
        if (presenter.configuration.isClickDisabled) return;
        if (presenter.configuration.isErrorMode &amp;&amp; presenter.configuration.correctFrames.isExerciseMode) return;
        if(presenter.isShowAnswersActive) return;

        if (presenter.mouseData.isMouseDown) {
            presenter.mouseData.isMouseDragged = true;

            if (presenter.$element.css('background-position').indexOf('px') == -1) {
                var oldPositionInPx = (-1 * presenter.configuration.currentFrame) * presenter.$element.width();
                presenter.$element.css('backgroundPosition', oldPositionInPx + 'px');
                presenter.mouseData.originalBackgroundPosition = oldPositionInPx;
            }

            var relativeDistance = event.pageX - presenter.mouseData.oldPosition.x,
                oldPosition = parseInt(presenter.$element.css('backgroundPosition'), 10);
            presenter.$element.css('backgroundPosition', (oldPosition + relativeDistance) + 'px');

            presenter.mouseData.oldPosition.x = event.pageX;
            presenter.mouseData.oldPosition.y = event.pageY;
        }
    }

    /**
     * Calculate scale for image containing element depending on frame aspect ratio
     *
     * @return {Object} calculated horizontal and vertical scale
     */
    presenter.calculateContainerDimensions = function(imageWidth, imageHeight, containerWidth, containerHeight) {
        var imageRatio = imageWidth / imageHeight;
        var containerRatio = containerWidth / containerHeight;

        var horizontal = imageRatio &gt;= containerRatio ? containerWidth : containerHeight * imageRatio;
        var vertical = imageRatio &gt;= containerRatio ? containerWidth / imageRatio : containerHeight;

        return {
            horizontal: horizontal,
            vertical: vertical
        };
    };


    // This function returns string containing CSS declaration of elements
    // background image size in percentage measure
    function calculateBackgroundSize(size, framesCount) {
        var cssValue;

        switch(size) {
            case 'SCALED':
            case 'STRETCHED':
                cssValue = framesCount * 100;
                cssValue += '% 100%';
                break;
            default:
                cssValue = '';
        }

        return cssValue;
    }

    function shouldShowWatermark() {
        return presenter.configuration.showWatermark &amp;&amp; !presenter.configuration.isClickDisabled;
    }

    function calculateWidth (containerWidth, imageWidth) {
        var differenceContainerElement = containerWidth - (imageWidth / presenter.configuration.frames);
        var differenceImage = differenceContainerElement * presenter.configuration.frames;

        var scaleWidth = imageWidth + differenceImage;

        return {
            scaleWidth: scaleWidth,
            differenceContainerElement: differenceContainerElement,
            elementWidth: (imageWidth / presenter.configuration.frames) + differenceContainerElement
        }
    }

    function loadImage(preview) {
        var tempImageElement = document.createElement('img');
        $(tempImageElement).addClass('image-viewer-hidden-image');
        $(tempImageElement).attr('src', presenter.configuration.imageSrc);
        $(presenter.$view).append(tempImageElement);

        $(tempImageElement).load(function() {
            var containerWidth = presenter.configuration.containerDimensions.width;
            var containerHeight = presenter.configuration.containerDimensions.height;
            var imageWidth = $(this).width();
            var imageHeight = $(this).height();
            var containerDimensions = presenter.calculateContainerDimensions(imageWidth / presenter.configuration.frames, imageHeight, containerWidth, containerHeight);
            var backgroundSize = calculateBackgroundSize(presenter.configuration.frameSize, presenter.configuration.frames);

            var elementWidth;
            var elementHeight;

            switch (presenter.configuration.frameSize) {
                case 'ORIGINAL':
                    elementWidth = imageWidth / presenter.configuration.frames;
                    elementHeight = imageHeight;
                    break;
                case 'SCALED':
                    var calculatedWidthScale = calculateWidth(containerWidth, imageWidth);
                    $(this).width(calculatedWidthScale.scaleWidth);

                    elementWidth = calculatedWidthScale.elementWidth;
                    elementHeight = containerDimensions.vertical;
                    break;
                case 'STRETCHED':
                    var calculatedWidthStretched = calculateWidth(containerWidth, imageWidth);
                    $(this).width(calculatedWidthStretched.scaleWidth);

                    elementWidth = calculatedWidthStretched.elementWidth;
                    elementHeight = containerHeight;
                    break;
            }

            presenter.$element.css({
                width: elementWidth + 'px',
                height: elementHeight + 'px',
                backgroundImage: "url('" + presenter.configuration.imageSrc + "')"
            });
            presenter.$elementHelper.css({
                width: elementWidth + 'px',
                height: elementHeight + 'px'
            });

            if (backgroundSize) {
                $(presenter.$element).css('background-size', backgroundSize);
            }

            presenter.configuration.backgroundImageWidth = $(this).width();

            $(this).remove();
            presenter.changeFrame(true, false, false, false);

            if (shouldShowWatermark()) {
                var watermarkOptions = {
                    "color": presenter.model["Watermark color"],
                    "opacity": presenter.model["Watermark opacity"],
                    "size": presenter.model["Watermark size"]
                };
                Watermark.draw(watermarkElement, watermarkOptions);
            }

            hideLoadingScreen();
            presenter.$view.trigger("onLoadImageEnd", [preview]);
        });
    }

    function prepareLoadingScreen(containerWidth, containerHeight) {
        $(loadingScreen.element).css({
            top: ((containerHeight - $(loadingScreen.element).height()) / 2) + 'px',
            left: ((containerWidth - $(loadingScreen.element).width()) / 2) + 'px'
        });
    }

    function showLoadingScreen() {
        $(loadingScreen.element).show();
        loadingScreen.counter++;
    }

    function hideLoadingScreen() {
        loadingScreen.counter--;
        if (loadingScreen.counter === 0) {
            $(loadingScreen.element).hide();
        }
    }

    /**
     * Creates label SPAN element and appends it to Addon space
     *
     * @param label
     * @return newly created element DOM reference
     */
    function createLabelElement(label) {
        var labelElement = document.createElement('span');
        var scaleX = 1.0;
        var scaleY = 1.0;
        if (presenter.configuration.baseDimensions.width != 0) {
            scaleX = presenter.configuration.containerDimensions.width / presenter.configuration.baseDimensions.width;
        }
         if (presenter.configuration.baseDimensions.height != 0) {
            scaleY = presenter.configuration.containerDimensions.height / presenter.configuration.baseDimensions.height;
        }

        $(labelElement).addClass('image-viewer-label');
        $(labelElement).html(label.text);
        if (scaleX != 1.0 || scaleY != 1.0) {
            $(labelElement).css(generateTransformDict(scaleX, scaleY));
        }
        $(labelElement).css({
            top: (label.top * scaleY) + 'px',
            left: (label.left * scaleX) + 'px'
        });

        $(presenter.$view).append(labelElement);

        return labelElement;
    }

    function generateTransformDict(scaleX, scaleY) {
        var scale = "scale(" + scaleX + "," + scaleY + ")";
        return {
            'transform': scale,
            '-ms-transform': scale,
            '-webkit-transform': scale,
            '-o-transform': scale,
            '-moz-transform': scale,
           "-webkit-transform-origin": "top left",
           "-ms-transform-origin": "top left",
           "transform-origin": "top left"
        }
    }

    function loadLabels() {
        for (var i = 0; i &lt; presenter.configuration.labels.length; i++) {
            presenter.configuration.labels[i].element = createLabelElement(presenter.configuration.labels[i]);
        }
    }

    presenter.hideLabels = function () {
        $(presenter.$view).find('.image-viewer-label').css('visibility', 'hidden');
    };

    /**
     * Displays labels that are assigned to given frame.
     *
     * @param frame frame number counted from 1 to n
     */
    presenter.displayLabels = function (frame) {
        $(presenter.$view).find('.image-viewer-label').css('visibility', 'hidden');
        for (var i = 0; i &lt; presenter.configuration.labels.length; i++) {
            var label = presenter.configuration.labels[i];
            for (var j = 0; j &lt; label.frames.length; j++) {
                if (frame === label.frames[j]) {
                    $(label.element).css('visibility', 'visible');
                }
            }
        }
    };

    function attachEventHandlers() {

        presenter.isMobileDevice = MobileUtils.isMobileUserAgent(navigator.userAgent) || MobileUtils.isEventSupported('touchend');
        if (MobileUtils.isWindowsMobile(window.navigator)) {
            presenter.$element[0].addEventListener("MSPointerDown", touchStartCallback, false);
            presenter.$element[0].addEventListener("MSPointerUp", touchEndCallback, false);
            presenter.$element[0].addEventListener("MSPointerMove", touchMoveCallback, false);
        }
        else if (presenter.isMobileDevice) {
            presenter.$element[0].ontouchstart = touchStartCallback;
            presenter.$element[0].ontouchend = touchEndCallback;
            presenter.$element[0].ontouchmove = touchMoveCallback;
        }
        else {
            presenter.$element.mousedown(mouseDownCallback);
            presenter.$element.mouseup(mouseUpCallback);
            presenter.$element.mousemove(mouseMoveCallback);
        }

        presenter.$element.click(clickHandler);

        $(watermarkElement).click(function(e) {
            e.stopPropagation();

            $(watermarkElement).hide();
            clickHandler(e);
        });

        if (presenter.configuration.showFrameCounter) {
            var $frameCounter = presenter.$view.find('.frame-counter:first');

            $frameCounter.find('.dot').each(function (index) {
                $(this).click(function (e) {
                    e.stopPropagation();

                    if (presenter.configuration.isClickDisabled) return;
                    if (presenter.configuration.isErrorMode &amp;&amp; presenter.configuration.correctFrames.isExerciseMode) return;
                    if (index === presenter.configuration.currentFrame) return;

                    hideWatermarkIfVisible();

                    presenter.moveToFrame(index + 1);
                });
            });
        }
    }

    function loadImagesCallback(preview) {
        if (!presenter.configuration.correctFrames.isExerciseMode) {
            presenter.getMaxScore = undefined;
            presenter.getScore = undefined;
            presenter.getErrorCount = undefined;
        }

        if (!preview) {
            loadSounds();

            if (!presenter.configuration.isClickDisabled) {
                attachEventHandlers();
            }
        }
        presenter.imageLoadedDeferred.resolve();
    }

    function loadImageEndCallback() {
        var configuration = presenter.configuration;

        if(!configuration.showWatermark) {
            $(watermarkElement).remove();
        }
        presenter.setVisibility(configuration.currentVisibility);
        presenter.changeFrame(presenter.changeFrameData.isPreview, presenter.changeFrameData.isReverseOrder, presenter.changeFrameData.triggerEvent, false);
    }

    function presenterLogic(view, model, isPreview) {
        var upgradedModel = presenter.upgradeModel(model);

        presenter.imageLoadedDeferred = new jQuery.Deferred();
        presenter.imageLoaded = presenter.imageLoadedDeferred.promise();
        presenter.addonId = upgradedModel.ID;
        presenter.$view = $(view);
        presenter.model = upgradedModel;
        presenter.isPreview = isPreview;
        presenter.$element = $(presenter.$view.find('.image-viewer:first')[0]);
        presenter.$elementHelper = $(presenter.$view.find('.image-viewer-helper:first')[0]);
        loadingScreen.element = presenter.$view.find('.image-viewer-loading-image:first')[0];
        watermarkElement = presenter.$view.find('.image-viewer-watermark:first')[0];

        if (!isPreview) {
            var loadingSrc = DOMOperationsUtils.getResourceFullPath(playerController, "media/loading.gif");
            if (loadingSrc) $(loadingScreen.element).attr('src', loadingSrc);
        }

        var configuration = presenter.validateModel(upgradedModel);
        if (configuration.isError) {
            showErrorMessage(view, configuration.errorCode);
        } else {
            showLoadingScreen();

            presenter.configuration = configuration;
            presenter.configuration.frameWidthPercentage = 100 / (presenter.configuration.frames - 1);
            presenter.configuration.isErrorMode = false;

            // Initialize mouse data
            presenter.mouseData = {
                isMouseDown : false,
                oldPosition : { x: 0, y: 0 },
                isMouseDragged : false
            };

            if (isPreview) {
                presenter.configuration.currentFrame = presenter.configuration.showFrame &lt;= presenter.configuration.frames ? presenter.configuration.showFrame - 1 : 0;
            } else {
                presenter.setCurrentFrame();
            }

            setContainerDimensions(view);
            prepareLoadingScreen(upgradedModel.Width, upgradedModel.Height);
            presenter.adjustFrameCounter();
            loadLabels();

            presenter.$view.bind("onLoadImageEnd", function (event, isPreview) {
                loadImagesCallback(isPreview);
            });
            if(!isPreview){
                presenter.pageLoaded.then(function() {
                    loadImage(isPreview);
                });
            }else{
                loadImage(isPreview);
            }
            presenter.setVisibility(presenter.configuration.defaultVisibility || isPreview);
            if (presenter.configuration.defaultVisibility) {
                presenter.displayLabels(1);
            }

            if (!configuration.showWatermark) {
                hideWatermarkIfVisible();
            }
        }
    }

    presenter.adjustFrameCounter = function () {
        var $wrapper = presenter.$view.find('.frame-counter-wrapper:first'), i, $dot,
            $frameCounter = $wrapper.find('.frame-counter:first');

        if (!presenter.configuration.showFrameCounter) {
            $wrapper.remove();
            return;
        }

        for (i = 0; i &lt; presenter.configuration.frames - 1; i++) {
            $dot = $(document.createElement('div'));
            $dot.addClass('dot');
            $frameCounter.append($dot);
        }
    };

    presenter.changeCurrentDot = function () {
        var $frameCounter = presenter.$view.find('.frame-counter:first');

        $frameCounter.find('.dot.current').removeClass('current');
        $frameCounter.find('.dot:eq(' + presenter.configuration.currentFrame + ')').addClass('current');
    };

    presenter.setCurrentFrame = function() {
        if(presenter.configuration.initialFrame) {
            presenter.configuration.currentFrame = presenter.configuration.initialFrame - 1;
        } else if(presenter.configuration.isRandomFrame) {
            presenter.configuration.currentFrame = presenter.getRandomFrame();
        } else {
            presenter.configuration.currentFrame = 0;
        }
    };

    presenter.next = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        var currentFrame = presenter.configuration.currentFrame,
            framesCount = presenter.configuration.frames;

        presenter.configuration.shouldCalcScore = true;
        presenter.configuration.currentFrame = currentFrame === framesCount - 1 ? 0 : currentFrame + 1;
        presenter.changeFrame(false, false, true, true);
    };

    presenter.previous = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        var currentFrame = presenter.configuration.currentFrame,
            framesCount = presenter.configuration.frames;

        presenter.configuration.shouldCalcScore = true;
        presenter.configuration.currentFrame = currentFrame === 0 ? framesCount - 1 : currentFrame - 1;
        presenter.changeFrame(false, true, true, true);
    };

    presenter.isValidFrameNumber = function(frame, framesCount) {
        return !isNaN(frame) &amp;&amp; frame &gt; 0 &amp;&amp; frame &lt;= framesCount;
    };

    presenter.moveToFrameCommand = function(params) {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        var framesCount = presenter.configuration.frames,
            currentFrame = presenter.configuration.currentFrame,
            validatedFrame = ModelValidationUtils.validateIntegerInRange(params[0], framesCount, 1);

        presenter.configuration.shouldCalcScore = true;

        if (validatedFrame.isValid &amp;&amp; validatedFrame.value - 1 !== currentFrame) {
            var isReverseOrder = currentFrame &gt; validatedFrame.value - 1;
            presenter.configuration.currentFrame = validatedFrame.value - 1;

            presenter.changeFrameData = {
                isPreview: false,
                isReverseOrder: isReverseOrder,
                triggerEvent: true
            };

            $.when(presenter.imageLoaded).then(loadImageEndCallback);
            presenter.readFrame(presenter.configuration.currentFrame);
        }
    };

    presenter.moveToFrame = function (frame) {
        presenter.moveToFrameCommand([frame]);
    };

    presenter.isValidFrameName = function (frameName) {
        var isInvalid = !frameName || frameName === "";

        return !isInvalid;
    };

    presenter.findFrame = function (name, frames) {
        for (var i = 0, length = frames.length; i &lt; length; i++) {
            if (frames[i].name == name) {
                return {
                    found: true,
                    frameNumber: parseInt(frames[i].frame, 10)
                }
            }
        }

        return {
            found: false
        };
    };

    presenter.moveToFrameNameCommand = function (params) {
        var frameFindResult, frameNumber;

        presenter.configuration.shouldCalcScore = true;

        if (!presenter.isValidFrameName(params[0])) return;

        frameFindResult = presenter.findFrame(params[0], presenter.configuration.frameNames);
        if (!frameFindResult.found) return;

        frameNumber = frameFindResult.frameNumber;
        if (frameNumber - 1 !== presenter.configuration.currentFrame) {
            var currentFrame = presenter.configuration.currentFrame;
            var isReverseOrder = currentFrame &gt; frameNumber - 1;
            presenter.configuration.currentFrame = frameNumber - 1;
            presenter.changeFrame(false, isReverseOrder, true, true);
        }
    };

    presenter.moveToFrameName = function (frameName) {
        presenter.moveToFrameNameCommand([frameName]);
    };

    presenter.getRandomFrame = function() {
        var frames = presenter.configuration.frames;
        return Math.floor( Math.random() * frames );
    };

    presenter.setClickDisabled = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        presenter.configuration.isClickDisabled = true;
        hideWatermarkIfVisible();
    };
    presenter.setClickEnabled = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        presenter.configuration.isClickDisabled = false;
        if (presenter.configuration.showWatermarkbyDefault) {
            showWatermarkIfNotVisible();
        }
    };

    presenter.executeCommand = function(name, params) {
        if (presenter.configuration.isErrorMode &amp;&amp; presenter.configuration.correctFrames.isExerciseMode) return;

        var commands = {
            'next': presenter.next,
            'previous': presenter.previous,
            'moveToFrame': presenter.moveToFrameCommand,
            'moveToFrameName': presenter.moveToFrameNameCommand,
            'getCurrentFrame': presenter.getCurrentFrame,
            'show': presenter.show,
            'hide': presenter.hide,
            'markAsCorrect': presenter.markAsCorrect,
            'markAsWrong': presenter.markAsWrong,
            'setClickDisabled': presenter.setClickDisabled,
            'setClickEnabled': presenter.setClickEnabled
        };

        return Commands.dispatch(commands, name, params, presenter);
    };

    presenter.setVisibility = function(isVisible) {
        presenter.$view.css("visibility", isVisible ? "visible" : "hidden");
    };

    presenter.show = function() {
        presenter.configuration.shouldCalcScore = true;
        presenter.setVisibility(true);
        presenter.displayLabels(presenter.configuration.currentFrame + 1);
        presenter.configuration.currentVisibility = true;
    };

    presenter.hide = function() {
        presenter.configuration.shouldCalcScore = true;
        presenter.setVisibility(false);
        presenter.hideLabels();
        presenter.configuration.currentVisibility = false;
    };

    presenter.markAsCorrect = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        presenter.$element.addClass('correct');
        presenter.$element.removeClass('wrong');
    };

    presenter.markAsWrong = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        presenter.$element.addClass('wrong');
        presenter.$element.removeClass('correct');
    };

    presenter.getCurrentFrame = function () {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        return presenter.configuration.currentFrame + 1;
    };

    presenter.createPreview = function(view, model){
        presenterLogic(view, model, true);
    };

    presenter.run = function(view, model){
        presenter.pageLoadedDeferred = new $.Deferred();
        presenter.pageLoaded = presenter.pageLoadedDeferred.promise();

        presenterLogic(view, model, false);

        presenter.eventBus.addEventListener('ShowAnswers', this);
        presenter.eventBus.addEventListener('HideAnswers', this);
    };

    presenter.validateAnimation = function (animation) {
        return ModelValidationUtils.validateOption(presenter.ANIMATION, animation);
    };

    presenter.validateFrameSize = function(size) {
        return ModelValidationUtils.validateOption(presenter.FRAME_SIZE, size);
    };

    presenter.validateImage = function(imageSrc) {
        if (ModelValidationUtils.isStringWithPrefixEmpty(imageSrc, '/file/')) {
            return { isError : true, errorCode: "IM_01" };
        }

        return { isError: false, image : imageSrc };
    };

    presenter.validateFrameNumber = function(frame) {
        if (!frame) return { isError: true, errorCode: "FN_01" };

        var parsedFrame = parseInt(frame, 10);

        if (isNaN(parsedFrame)) return { isError:true, errorCode:"FN_02" };
        if (parsedFrame &lt; 1) return { isError:true, errorCode:"FN_03" };

        return { isError: false, frames : parsedFrame };
    };

    presenter.validateAlternativeTexts = function (altTexts, framesNumber) {
        var parsedAltTexts = [];

        for (var i = 0; i &lt; framesNumber; i++) parsedAltTexts.push("");

        if (altTexts.length != 1 || altTexts[0]["Alternative text"] != "") {
            for (var i = 0; i &lt; altTexts.length; i++) {
                var alt = altTexts[i];
                var parsedNumber = presenter.validateFrameNumber(alt.frame);
                if (parsedNumber.isError) return parsedNumber;
                parsedAltTexts[parsedNumber.frames - 1] = alt["Alternative text"];
            }
        }
        return { isError: false, altTexts: parsedAltTexts };

    };

    presenter.validateSound = function(soundsArray) {
        var sounds = [];

        if (soundsArray &amp;&amp; $.isArray(soundsArray)) {
            for (var i = 0; i &lt; soundsArray.length; i++) {
                var isMP3Empty = ModelValidationUtils.isStringWithPrefixEmpty(soundsArray[i]['MP3 sound'], '/file/');
                var isAACEmpty = ModelValidationUtils.isStringWithPrefixEmpty(soundsArray[i]['AAC sound'], '/file/');
                var isOGGEmpty = ModelValidationUtils.isStringWithPrefixEmpty(soundsArray[i]['OGG sound'], '/file/');
                var isEmpty = isMP3Empty &amp;&amp; isAACEmpty &amp;&amp; isOGGEmpty;

                sounds.push({
                    AAC : isAACEmpty ? "" : soundsArray[i]['AAC sound'],
                    OGG : isOGGEmpty ? "" : soundsArray[i]['OGG sound'],
                    MP3 : isMP3Empty ? "" : soundsArray[i]['MP3 sound'],
                    isEmpty : isEmpty
                });
            }
        }

        return {
            sounds : sounds
        };
    };

    presenter.validateFrameNames = function(frameNames, frames) {
        var frameNamesArray = [];

        var frameNamesEmpty = false;
        if (frameNames &amp;&amp; $.isArray(frameNames)) {
            var firstName = frameNames[0].name;
            var firstFrame = frameNames[0].frame;
            if (firstFrame === "" &amp;&amp; firstName === "") {
                frameNamesEmpty = true;
            } else {
                for (var j = 0; j &lt; frameNames.length; j++) {
                    var frame = frameNames[j]['frame'];
                    var name = frameNames[j]['name'];

                    if (!name || name === null || name === "") {
                        return {
                            isError: true,
                            errorCode: "FN_04"
                        };
                    }

                    var validatedFrame = presenter.validateFrameNumber(frame);
                    if (validatedFrame.isError) {
                        return {
                            isError: true,
                            errorCode: validatedFrame.errorCode
                        };
                    }

                    if (validatedFrame.frames &gt; frames) {
                        return {
                            isError: true,
                            errorCode: "FN_05"
                        };
                    }

                    frameNamesArray.push({
                        name: name,
                        frame: frame
                    });
                }
            }
        }

        return {
            isError: false,
            frameNames: frameNamesArray,
            frameNamesEmpty: frameNamesEmpty
        };
    };

    /**
     * This function validates and converts number from string representation to integer value
     */
    presenter.validatePosition = function (position) {
        var validatedPosition = ModelValidationUtils.validateInteger(position);

        if (!validatedPosition.isValid) {
            return { isError:true };
        }

        if (validatedPosition.value &lt; 0) {
            return { isError:true };
        }

        return {
            isError: false,
            position: validatedPosition.value
        };
    };

    /**
     * Removes duplicates from sorted array of numbers
     *
     * @param array sorted array of numbers
     * @return array with removed duplicates
     */
    presenter.removeDuplicatesFromArray = function(array) {
        if (array.length === 0) {
            return [];
        }

        var results = [];

        for (var i = 0; i &lt; array.length - 1; i++) {
            if (array[i + 1] !== array[i]) {
                results.push(array[i]);
            }
        }

        results.push(array[array.length - 1]);

        return results;
    };

    /**
     * Validates frames numbers list separated with commas
     *
     * @param frames string representation of frames list
     * @param count frames count
     *
     * @return array of frames numbers counted from 1 to n
     */
    presenter.validateFramesList = function (frames, count) {
        var list = [];

        if (ModelValidationUtils.isStringEmpty(frames)) {
            return {
                isError: true,
                errorCode: "FL_01"
            };
        }

        var regExp = new RegExp('[0-9a-zA-Z\,\-]+'); // Only digits and commas are allowed in slides list
        var matchResult = frames.match(regExp);
        if (matchResult === null || frames.length !== matchResult[0].length) {
            return {
                isError: true,
                errorCode: "FL_02"
            };
        }

        var splittedFrames = frames.split(',');
        for (var i = 0; i &lt; splittedFrames.length; i++) {
            if (ModelValidationUtils.isStringEmpty(splittedFrames[i])) {
                return {
                    isError: true,
                    errorCode: "FL_04"
                };
            }

            var indexOfRange = splittedFrames[i].search('-');
            if (indexOfRange !== -1) {
                var rangeEnd = splittedFrames[i].split('-')[1];
                var validatedRangeEnd = ModelValidationUtils.validateIntegerInRange(rangeEnd, count, 1);
                if (!validatedRangeEnd.isValid) {
                    return {
                        isError: true,
                        errorCode: "FL_05"
                    };
                }

                var rangeStart = splittedFrames[i].split('-')[0];
                var validatedRangeStart = ModelValidationUtils.validateIntegerInRange(rangeStart, rangeEnd.value, 1);
                if (!validatedRangeStart.isValid || validatedRangeStart.value &gt; validatedRangeEnd.value) {
                    return {
                        isError: true,
                        errorCode: "FL_05"
                    };
                }

                for (var frameNumber = validatedRangeStart.value; frameNumber &lt;= validatedRangeEnd.value; frameNumber++) {
                    list.push(frameNumber);
                }

                continue;
            }

            var validatedFrame = ModelValidationUtils.validateIntegerInRange(splittedFrames[i], count, 1);
            if (!validatedFrame.isValid) {
                return {
                    isError: true,
                    errorCode: "FL_03"
                };
            }

            list.push(validatedFrame.value);
        }

        list = list.sort();
        list = presenter.removeDuplicatesFromArray(list);

        return {
            isError: false,
            list: list
        };
    };

    presenter.validateLabels = function (labelsArray, framesCount) {
        var labels = [], element;

        // Ugly fix for Editor problems
        if (labelsArray === undefined) return { isError:false, labels:labels };

        for (var i = 0; i &lt; labelsArray.length; i++) {
            element = {
                Text: labelsArray[i].Text,
                Frames: labelsArray[i].Frames,
                Top: labelsArray[i].Top,
                Left: labelsArray[i].Left
            };

            if (ModelValidationUtils.isArrayElementEmpty(element)) {
                if (labelsArray.length == 1)
                    return { isError:false, labels:labels };
                else
                    return { isError:true, errorCode:"LA_04" };
            }

            if (ModelValidationUtils.isStringEmpty(element.Text)) {
                return { isError:true, errorCode:"LA_01" };
            }

            var validatedFramesList = presenter.validateFramesList(element.Frames, framesCount);
            if (validatedFramesList.isError) return { isError:true, errorCode: validatedFramesList.errorCode };

            var validatedTopPosition = ModelValidationUtils.validateInteger(element.Top);
            if (!validatedTopPosition.isValid) return { isError:true, errorCode:"LA_02" };

            var validatedLeftPosition = ModelValidationUtils.validateInteger(element.Left);
            if (!validatedLeftPosition.isValid) return { isError:true, errorCode:"LA_03" };

            var text = {
                text: element.Text,
                frames: validatedFramesList.list,
                top: validatedTopPosition.value,
                left: validatedLeftPosition.value
            };

            labels.push(text);
        }

        return { isError:false, labels:labels };
    };

    presenter.validateInitialFrame = function(frame, framesCount) {
        if(frame &amp;&amp; !ModelValidationUtils.validateIntegerInRange(frame, framesCount, 1).isValid) {
            return {
                isError: true,
                errorCode: 'IF_01'
            }
        }
        var isValid = this.isValidFrameNumber(frame, framesCount);
        return isValid ? frame : false;
    };

    presenter.validateOpacity = function (opacity) {
        if (ModelValidationUtils.isStringEmpty(opacity)) return { isError: false, opacity: 1.0 };

        var parsedOpacity = ModelValidationUtils.validateFloatInRange(opacity, 1.0, 0.0, 2);
        if (!parsedOpacity.isValid) return { isError: true };

        return { isError: false, opacity: parsedOpacity.value };
    };

    presenter.validateCorrectFrame = function (frames, framesCount) {
        if (ModelValidationUtils.isStringEmpty(frames)) return { isExerciseMode: false };

        var framesArray = frames.split(',');
        var correctFrames = [];

        for (var i = 0, length = framesArray.length; i &lt; length; i++) {
            var frameNumber = ModelValidationUtils.validateInteger(framesArray[i]);
            if (!frameNumber.isValid) return { errorCode: "FN_02" };

            frameNumber = ModelValidationUtils.validateIntegerInRange(framesArray[i], framesCount, 1);
            if (!frameNumber.isValid) return { errorCode: "CF_01" };

            correctFrames.push(frameNumber.value - 1);
        }

        return { frames: correctFrames, isExerciseMode: true };
    };

    presenter.validateModel = function(model) {
        var validatedImage = presenter.validateImage(model.Image);
        if (validatedImage.isError) return { isError: true, errorCode: validatedImage.errorCode };

        var validatedFrames = presenter.validateFrameNumber(model.Frames);
        if (validatedFrames.isError) return { isError: true, errorCode: validatedFrames.errorCode };

        var validatedFrameNames = presenter.validateFrameNames(model["Frame names"], validatedFrames.frames);
        if (validatedFrameNames.isError) return { isError: true, errorCode: validatedFrameNames.errorCode };

        var validatedLabels = presenter.validateLabels(model.Labels, validatedFrames.frames);
        if (validatedLabels.isError) return { isError: true, errorCode: validatedLabels.errorCode };

        var validatedCorrectFrames = presenter.validateCorrectFrame(model["Correct frames"], validatedFrames.frames);
        if (validatedCorrectFrames.errorCode) return { isError: true, errorCode: validatedCorrectFrames.errorCode };

        var showFrame = 1;
        var validatedShowFrame = ModelValidationUtils.validateIntegerInRange(model["Show frame"], validatedFrames.frames, 1);
        if (validatedShowFrame.isValid) {
            showFrame = validatedShowFrame.value;
        }

        var validatedInitialFrame = this.validateInitialFrame(model["Initial frame"], validatedFrames.frames);
        if(validatedInitialFrame.errorCode) return { isError: true, errorCode: validatedInitialFrame.errorCode };

        var validatedAltTexts = presenter.validateAlternativeTexts(model["Alternative texts"], validatedFrames.frames);
        if(validatedAltTexts.errorCode) return { isError: true, errorCode: validatedAltTexts.errorCode };

        var baseDimensions = {
            width: 0,
            height: 0
        };

        if (model['Base width'].trim().length !== 0) {
            var validatedBaseWidth = ModelValidationUtils.validatePositiveInteger(model['Base width']);
            if (validatedBaseWidth.isValid) {
                baseDimensions.width = validatedBaseWidth.value;
            } else {
                validatedBaseWidth.errorCode = 'ID_01';
                validatedBaseWidth.isError = true;
                return validatedBaseWidth;
            }
        }
        if (model['Base height'].trim().length !== 0) {
            var validatedBaseHeight = ModelValidationUtils.validatePositiveInteger(model['Base height']);
            if (validatedBaseHeight.isValid) {
                baseDimensions.height = validatedBaseHeight.value;
            } else {
                validatedBaseHeight.errorCode = 'ID_01';
                validatedBaseHeight.isError = true;
                return validatedBaseHeight;
            }
        }

        var validatedSound = presenter.validateSound(model.Sounds);
        var isClickDisabled = ModelValidationUtils.validateBoolean(model.isClickDisabled);
        var frameSize = presenter.validateFrameSize(model["Frame size"]);
        var animation = presenter.validateAnimation(model.Animation);
        var defaultVisibility = ModelValidationUtils.validateBoolean(model["Is Visible"]);
        var isDoNotReset = ModelValidationUtils.validateBoolean(model["Do not reset"]);
        var isRandomFrame = ModelValidationUtils.validateBoolean(model["Random frame"]);

        return {
            isError: false,
            imageSrc : validatedImage.image,
            frames : validatedFrames.frames,
            sounds : validatedSound.sounds,
            frameNames: validatedFrameNames.frameNames,
            frameNamesEmpty: validatedFrameNames.frameNamesEmpty,
            isClickDisabled: isClickDisabled,
            isClickDisabledbyDefault: isClickDisabled,
            frameSize: frameSize,
            labels: validatedLabels.labels,
            showWatermark: ModelValidationUtils.validateBoolean(model["Show watermark"]),
            showWatermarkbyDefault: ModelValidationUtils.validateBoolean(model["Show watermark"]),
            showFrame: showFrame,
            animation: animation,
            correctFrames: validatedCorrectFrames,
            defaultVisibility: defaultVisibility,
            currentVisibility: defaultVisibility,
            isDoNotReset: isDoNotReset,
            isRandomFrame: isRandomFrame,
            initialFrame: validatedInitialFrame,
            showFrameCounter: ModelValidationUtils.validateBoolean(model["Show frame counter"]),
            shouldCalcScore: false,
            lang: model["langAttribute"],
            altTexts: validatedAltTexts.altTexts,
            baseDimensions: baseDimensions
        };
    };

    function animateLinearly(element, backgroundPositionValue) {
        $(element).animate({
            'background-position':backgroundPositionValue
        }, 500, "linear");
    }

    function animateLinearlyLastElement(element) {
        $(element).animate({
            'background-position':(100 + presenter.configuration.frameWidthPercentage) + '%'
        }, {
            duration:500,
            easing:"linear",
            complete:function () {
                $(element).css({
                    'background-position':'-' + presenter.configuration.frameWidthPercentage + '%'
                });
                $(element).animate({
                    'background-position':'0%'
                }, 500, "linear")
            }
        });
    }

    function animateLinearlyFirstElement(element) {
        $(element).animate({
            'background-position':'-' + presenter.configuration.frameWidthPercentage + '%'
        }, {
            duration:500,
            easing:"linear",
            complete:function () {
                $(element).css({
                    'background-position':(100 + presenter.configuration.frameWidthPercentage) + '%'
                });
                $(element).animate({
                    'background-position':'100%'
                }, 500, "linear")
            }
        });
    }

    function animateFading(element, backgroundPositionValue) {
        $(element).fadeOut({
            duration: 750,
            complete: function() {
                $(element).css({
                    'background-position': backgroundPositionValue
                });
                $(element).fadeIn(750);
            }
        });
    }

    presenter.changeBackgroundPosition = function (isPreview, element, isReverseOrder) {
        var backgroundPositionValue = (presenter.configuration.currentFrame * presenter.configuration.frameWidthPercentage + '%');

        if (isPreview || presenter.configuration.animation === 'NONE') {
            $(element).css('background-position', backgroundPositionValue);
        } else {
            switch (presenter.configuration.animation) {
                case 'LINEAR':
                    if (isReverseOrder) {
                        if (presenter.configuration.currentFrame === presenter.configuration.frames - 1) {
                            animateLinearlyFirstElement(element);
                        } else {
                            animateLinearly(element, backgroundPositionValue);
                        }
                    } else {
                        if (presenter.configuration.currentFrame !== 0) {
                            animateLinearly(element, backgroundPositionValue);
                        } else {
                            animateLinearlyLastElement(element);
                        }
                    }
                    break;
                case 'FADING':
                    animateFading(element, backgroundPositionValue);
                    break;
            }
        }
    };

    presenter.changeFrameLogic = function (isPreview, triggerEvent) {
        presenter.changeCurrentDot();

        if (presenter.configuration.currentFrame === 0) {
            if (!isPreview) presenter.stopAllAudio();
        } else {
            if (!isPreview) presenter.playAudio();
        }

        if (presenter.configuration.currentVisibility) {
            presenter.displayLabels(presenter.configuration.currentFrame + 1);
        } else {
            presenter.hideLabels();
        }

        if (triggerEvent &amp;&amp; !isPreview) {
            presenter.triggerFrameChangeEvent(presenter.configuration.currentFrame + 1);
        }
    };
    presenter.changeFrame = function(isPreview, isReverseOrder, triggerEvent, readFrame) {
        presenter.changeBackgroundPosition(isPreview, presenter.$element, isReverseOrder);
        presenter.changeFrameLogic(isPreview, triggerEvent);
        if (readFrame) {
            presenter.readFrame(presenter.configuration.currentFrame);
        }
    };

    presenter.stopAllAudio = function () {
        for (var i = 0; i &lt; audioElements.length; i++) {
            if (audioElements[i] !== null ) {
                stopAudio(audioElements[i]);
            }
        }
    };

    function stopAudio(audio) {
        audio.pause();
        audio.setTime(0);
    }

    presenter.playAudio = function() {
        presenter.stopAllAudio();

        var audio = audioElements[presenter.configuration.currentFrame - 1];
        if (audio) {
            audio.play();
        }
    };

    presenter.createEventData = function(frameNumber, eventScore) {
        return {
            source : this.addonId,
            item : "" + frameNumber,
            value : '',
            score : eventScore
        }
    };

    presenter.triggerFrameChangeEvent = function(frameNumber) {
        var eventScore;

        if(presenter.configuration.correctFrames.frames){
            if(presenter.configuration.correctFrames.frames.indexOf(frameNumber-1) &gt; -1){
                eventScore = 1;
            }else{
                eventScore = 0;
            }
        }else{
            eventScore = '';
        }

        var eventData = this.createEventData(frameNumber, eventScore);

        if (playerController != null &amp;&amp; !presenter.isShowAnswersActive) {
            playerController.getEventBus().sendEvent('ValueChanged', eventData);
        }
    };

    presenter.setPlayerController = function(controller) {
        playerController = controller;
        presenter.eventBus = controller.getEventBus();
        presenter.eventBus.addEventListener('PageLoaded', this);
    };

    presenter.isCurrentFrameCorrectlySelected = function () {
        var correctFrames = presenter.configuration.correctFrames.frames;

        for (var i = 0, length = correctFrames.length; i &lt; length; i++) {
            if (presenter.configuration.currentFrame === correctFrames[i]) {
                return true;
            }
        }

        return false;
    };

    presenter.getState = function() {
        return JSON.stringify({
            currentFrame : this.configuration.currentFrame,
            currentVisibility : this.configuration.currentVisibility,
            showWatermark : ($(watermarkElement).is(':visible')) ? true : false,
            showWatermarkbyDefault : this.configuration.showWatermarkbyDefault,
            isClickDisabled: presenter.configuration.isClickDisabled,
            isClickDisabledbyDefault : presenter.configuration.isClickDisabledbyDefault,
            shouldCalcScore: presenter.configuration.shouldCalcScore
        });
    };

    presenter.setState = function(state_string) {
        var configuration = presenter.configuration;
        var state = JSON.parse(state_string);
        configuration.currentFrame = state.currentFrame;
        configuration.currentVisibility = state.currentVisibility;
        configuration.showWatermark = state.showWatermark;
        configuration.showWatermarkbyDefault = state.showWatermarkbyDefault;
        configuration.shouldCalcScore = state.shouldCalcScore;
        configuration.isClickDisabled = state.isClickDisabled;
        configuration.isClickDisabledbyDefault = state.isClickDisabledbyDefault;

        $.when(presenter.imageLoaded).then(loadImageEndCallback);
    };

    presenter.reset = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        presenter.configuration.shouldCalcScore = true;
        presenter.configuration.isErrorMode = false;

        if (presenter.configuration.isDoNotReset) return;

        presenter.setCurrentFrame();
        presenter.changeFrame(false, false, false, false);

        if (shouldShowWatermark()) {
            showWatermarkIfNotVisible();
        }

        presenter.configuration.isClickDisabled = presenter.configuration.isClickDisabledbyDefault;

        var isVisible = presenter.configuration.currentVisibility = presenter.configuration.defaultVisibility;
        presenter.setVisibility(isVisible);
        removeCorrectnessClasses();
    };

    presenter.getScore = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        if (!presenter.configuration.correctFrames.isExerciseMode) return 0;
        if (!presenter.configuration.shouldCalcScore) return 0;

        return presenter.isCurrentFrameCorrectlySelected() ? 1 : 0;
    };

    presenter.getErrorCount = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        if (!presenter.configuration.correctFrames.isExerciseMode) { return 0; }
        if (!presenter.configuration.shouldCalcScore) return 0;

        return presenter.isCurrentFrameCorrectlySelected() ? 0 : 1;
    };

    presenter.getMaxScore = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        if(presenter.configuration.correctFrames &amp;&amp; !presenter.configuration.correctFrames.isExerciseMode) { return 0; }
        return 1;
    };

    presenter.setShowErrorsMode = function() {
        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        presenter.configuration.isErrorMode = true;
        presenter.configuration.shouldCalcScore = true;

        if(!presenter.configuration.correctFrames.isExerciseMode) return;

        var isCorrect = presenter.isCurrentFrameCorrectlySelected();

        presenter.$element.addClass(isCorrect ? 'correct' : 'wrong');
    };

    function removeCorrectnessClasses() {
        presenter.$element.removeClass('correct');
        presenter.$element.removeClass('wrong');
    }

    presenter.setWorkMode = function() {
        presenter.configuration.isErrorMode = false;
        if(!presenter.configuration.correctFrames.isExerciseMode) return;

        removeCorrectnessClasses();
    };

    presenter.onEventReceived = function(eventName) {
        if (eventName == 'PageLoaded') {
            presenter.pageLoadedDeferred.resolve();
        }

        if (eventName == "ShowAnswers") {
            presenter.showAnswers();
        }

        if (eventName == "HideAnswers") {
            presenter.hideAnswers();
        }
    };

    presenter.moveToFrameCommandShowAnswers = function(params) {
        var framesCount = presenter.configuration.frames,
            currentFrame = presenter.configuration.currentFrame,
            validatedFrame = ModelValidationUtils.validateIntegerInRange(params[0], framesCount, 1);

        presenter.configuration.shouldCalcScore = true;

        if (validatedFrame.isValid &amp;&amp; validatedFrame.value - 1 !== currentFrame) {
            var isReverseOrder = currentFrame &gt; validatedFrame.value - 1;
            presenter.configuration.currentFrame = validatedFrame.value - 1;

            presenter.changeFrameData = {
                isPreview: false,
                isReverseOrder: isReverseOrder,
                triggerEvent: true
            };

            $.when(presenter.imageLoaded).then(loadImageEndCallback);
        }
    };

    presenter.moveToFrameShowAnswers = function (frame) {
        presenter.moveToFrameCommandShowAnswers([frame]);
    };

    presenter.showAnswers = function () {
        presenter.isShowAnswersActive = true;

        if(presenter.configuration.correctFrames.frames){
            presenter.setWorkMode();
            presenter.currentFrameShowAnswers = presenter.configuration.currentFrame;
            presenter.moveToFrameShowAnswers(presenter.configuration.correctFrames.frames[0]+1);
            presenter.$element.addClass('show-answers');
        }
    };

    presenter.hideAnswers = function () {
        if (!presenter.isShowAnswersActive) {
            return;
        }

        if(presenter.currentFrameShowAnswers != undefined){
            presenter.moveToFrameShowAnswers(presenter.currentFrameShowAnswers+1);
            presenter.$element.removeClass('show-answers');
        }

        presenter.isShowAnswersActive = false;
    };

    presenter.readFrame = function (frameId) {
        if (playerController &amp;&amp; playerController.isWCAGOn()) {
            var voiceObjects = presenter.getTextVoiceObjectForFrame(frameId);
            if (presenter.isWCAGSelected) {
                presenter.speak(voiceObjects);
            } else {
                presenter.speakWhenIdle(voiceObjects);
            }
        }
    };

    presenter.keyboardController = function(keycode, isShiftKeyDown, event) {

        if (keycode == window.KeyboardControllerKeys.ENTER) {
            if (!isShiftKeyDown) {
                event.preventDefault();
                presenter.readFrame(presenter.configuration.currentFrame);
            } else {
                presenter.setWCAGStatus(false);
            }

        } else if (keycode == window.KeyboardControllerKeys.SPACE) {
            event.preventDefault();
            if (!presenter.configuration.isClickDisabled) {
                hideWatermarkIfVisible();
                presenter.next();
            }

        } else if (keycode == window.KeyboardControllerKeys.ESCAPE) {
            presenter.isWCAGSelected = false;
        }
    };

    presenter.setWCAGStatus = function(isOn) {
        presenter.isWCAGSelected = isOn;
    };

    presenter.getTextVoiceObjectForFrame = function (frameId) {
        return [window.TTSUtils.getTextVoiceObject(presenter.configuration.altTexts[frameId], presenter.configuration.lang)];
    };

    presenter.getTextToSpeechOrNull = function (playerController) {
        if (playerController) {
            return playerController.getModule('Text_To_Speech1');
        }

        return null;
    };

    presenter.speak = function(data, callback) {
        var tts = presenter.getTextToSpeechOrNull(playerController);

        if (tts &amp;&amp; playerController.isWCAGOn()) {
            tts.speakWithCallback(data, callback);
        }
    };

    presenter.speakWhenIdle = function (data, callback) {
        var tts = presenter.getTextToSpeechOrNull(playerController);
        if (tts &amp;&amp; playerController.isWCAGOn()) {
            tts.speakWhenIdle(data, callback);
        }
    };

    return presenter;
}
</presenter></addon>