/// <reference path="../ScriptReference/jquery-1.7.1-vsdoc.js" />
/// <reference path="./Mediasite.Core.js" />
/// <reference path="./Mediasite.Geometry.js" />
/// <reference path="./ImageSizeProvider.js" />

if (!Mediasite) {
	var Mediasite = {};
}
Mediasite.Player = Mediasite.Player || {};


// ZoomHud
// +==================================================================
// Events: zoominclicked, zoomoutclicked
// ===================================================================
Mediasite.Player.ZoomHud = function (element) {
    var _element = $(element);
    var _zoomFactor = 0;
    var _isExpanded = false;
    var _percentFormat = 'NUMBER%'; // TODO: i18n?
    var _self = this;

    // track is zoom buttons we're clicked so we can reset focus appropriately in this.expand() and this.collapse()
    var _zoominclicked = false;
    var _zoomoutclicked = false;

    var _hudExpanded, _percentageText, _zoomInButton, _zoomOutButton, _hudCollapsed, _zoomInButton2;

    function init() {
        _hudExpanded = $('<div />').appendTo(_element).addClass("zoomhud-expanded");
        _percentageText = $('<div />').appendTo(_hudExpanded).addClass("zoomhud-percentage");
        _zoomInButton = $('<div tabindex="0" />').appendTo(_hudExpanded).addClass("zoomhud-zoomInButton").imageButton();
        _zoomOutButton = $('<div tabindex="0" />').appendTo(_hudExpanded).addClass("zoomhud-zoomOutButton").imageButton();
        _hudCollapsed = $('<div />').appendTo(_element).addClass("zoomhud-collapsed");
        _zoomInButton2 = $('<div tabindex="0" />').appendTo(_hudCollapsed).addClass("zoomhud-zoomInButton").imageButton();

        _zoomInButton.click(zoomInButton_click);
        _zoomInButton2.click(zoomInButton_click);
        _zoomOutButton.click(zoomOutButton_click);

        _zoomInButton.bind("keypress", function (e) {
            if (e.keyCode == 13)
                _zoomInButton.trigger("click");
        });

        _zoomInButton2.bind("keypress", function (e) {
            if (e.keyCode == 13)
                _zoomInButton2.trigger("click");
        });

        _zoomOutButton.bind("keypress", function (e) {
            if (e.keyCode == 13)
                _zoomOutButton.trigger("click");
        });

        this.collapse(0);
    }


    // = Accessors =
    this.getElement = function () { return _element; };

    this.getZoomFactor = function () { return _zoomFactor; };
    this.setZoomFactor = function (value) {
        _zoomFactor = value;
        _percentageText.text(toPercentageText(value));
    };

    this.getZoomInEnabled = function () { return !_zoomInButton.imageButton('getIsDisabled'); };
    this.setZoomInEnabled = function (value) {
        _zoomInButton.imageButton(value ? 'enable' : 'disable');
        _zoomInButton2.imageButton(value ? 'enable' : 'disable');
    };

    this.getZoomOutEnabled = function () { return !_zoomOutButton.imageButton('getIsDisabled'); };
    this.setZoomOutEnabled = function (value) {
        _zoomOutButton.imageButton(value ? 'enable' : 'disable');
    };

    this.getIsExpanded = function () { return _isExpanded; };

    // = Methods =
    this.expand = function (speed) {
        _isExpanded = true;
        _hudExpanded.stop(true, true);
        _hudCollapsed.stop(true, true);
        _hudExpanded.fadeIn(speed);
        _hudCollapsed.fadeOut(speed, refocusZoomInButton);

        // if zoomhud was expanded by clicking/enter key - maintain focus on the newly visible zoomInButton
        function refocusZoomInButton() {
            if (_zoominclicked) {
                _zoominclicked = false;
                $(".zoomhud-expanded .zoomhud-zoomInButton").focus();
            }
        }
    };

    this.collapse = function (speed) {
        _isExpanded = false;
        _hudExpanded.stop(true, true);
        _hudCollapsed.stop(true, true);
        _hudExpanded.fadeOut(speed);
        _hudCollapsed.fadeIn(speed, refocusZoomInButton);

        // if zoomhud was collapse by clicking/enter key - maintain focus on the newly visible zoomInButton
        function refocusZoomInButton() {
            if (_zoomoutclicked) {
                _zoomoutclicked = false;
                $(".zoomhud-collapsed .zoomhud-zoomInButton").focus();
            }
        }


    };

    this.bind = function () {
        _element.bind.apply(_element, arguments);
    }

    this.unbind = function () {
        _element.unbind.apply(_element, arguments);
    }


    // = Event Handlers =
    function zoomInButton_click() { _zoominclicked = true; _element.trigger('zoominclicked'); }
    function zoomOutButton_click() { _zoomoutclicked = true; _element.trigger('zoomoutclicked'); }


    // = Functions =
    function toPercentageText(factor) {
        if (typeof (factor) != 'number') {
            return '';
        }

        var percentage = Math.max(Math.round(factor * 100), 0);
        return _percentFormat.replace(/NUMBER/, percentage);
    }


    init.call(this);
};

// -=====

// SlideInspector
// +==================================================================
// Events: 
// ===================================================================
Mediasite.Player.SlideInspector = function (element) {

    var _element, _slideElement; // primary elements
    var _slide, _viewport, _zoomEngine, _hud; // logical actors
    var _isTransparentWhenZoomedOut = false;
    var _canZoom = true;

    function init() {
        // Primary Elements
        _element = $(element).css(getSelectFocusDragCss()).css('overflow', 'hidden');
        if (_element.css('position') == 'static') {
            _element.css('position', 'relative');
        }
        _slideElement = $('<div />').appendTo(_element).addClass("inspector-zoomable-slide");
        _hudElement = $('<div />').appendTo(_element).addClass("zoomhud");

        // Logical Actors
        _slide = new Mediasite.Player.ZoomableSlide(_slideElement);
        _viewport = new Mediasite.Player.ZoomViewport(_element);
        _zoomEngine = new Mediasite.Player.ZoomEngine(_slide.getNativeSize(), _viewport.getSize());
        _hud = new Mediasite.Player.ZoomHud(_hudElement);

        _viewport.bind({
            'mousedragged': viewport_mousedragged,
            'mousewheelup': viewport_mousewheelup,
            'mousewheeldown': viewport_mousewheeldown,
            'sizechanged': viewport_sizechanged,
            'pinched': viewport_pinched
        });

        _slide.bind('nativesizechanged', slide_nativesizechanged);

        _zoomEngine.bind({
            'contentrectchanged': zoomEngine_contentrectchanged,
            'capabilitieschanged': zoomEngine_capabilitieschanged,
            'zoomfactorchanged': zoomEngine_zoomfactorchanged
        });

        _hud.bind({
            'zoominclicked': hud_zoominclicked,
            'zoomoutclicked': hud_zoomoutclicked
        });

        _element.bind({
            'mouseenter': element_mouseenter,
            'mousemove': element_mousemove,
            'mouseleave': element_mouseleave,
            'touchstart': element_touchstart
        });

        checkZoomedAllOut();
        hideHud(true);
    }


    // = Accessors =
    this.getSlideImage = function () { return _slide.getSlideImage(); };
    this.setSlideImage = function (value) { _slide.setSlideImage(value); };

    this.getSlideToolTip = function () { return _slide.getToolTip(); };
    this.setSlideToolTip = function (value) { _slide.setToolTip(value); };

    this.getSlideAspectRatio = function () { return _slide.getAspectRatio(); };
    this.setSlideAspectRatio = function (value) { _slide.setAspectRatio(value); };

    this.getIsTransparentWhenZoomedOut = function () { return _isTransparentWhenZoomedOut; };
    this.setIsTransparentWhenZoomedOut = function (value) {
        _isTransparentWhenZoomedOut = value;
        checkZoomedAllOut();
    };

    this.getCanZoom = function () { return _canZoom; }
    this.setCanZoom = function (value) {
        _canZoom = value;
        _zoomEngine.setCanZoom(_canZoom);
        if (!_canZoom) {
            hideHud(true);
        }
    }

    // = Event Handlers =
    function viewport_mousedragged(e, data) {
        _zoomEngine.pan(data.delta.x, data.delta.y);
    }
    function viewport_mousewheelup(e, data) {
        _zoomEngine.zoomIn(data.position);
    }
    function viewport_mousewheeldown(e, data) {
        _zoomEngine.zoomOut(data.position);
    }
    function viewport_sizechanged() {
        _zoomEngine.setViewportSize(_viewport.getSize());
    }
    function viewport_pinched(e, data) {
        _zoomEngine.zoom(data.position, data.scaleFactor);
    }
    function slide_nativesizechanged() {
        _zoomEngine.setNativeSize(_slide.getNativeSize());
    }
    function zoomEngine_contentrectchanged(e, data) {
        _slide.applyRect(data.rect, data.mayAnimate);
    }
    function zoomEngine_capabilitieschanged() {
        _hud.setZoomInEnabled(_zoomEngine.getCanZoomIn());
        _hud.setZoomOutEnabled(_zoomEngine.getCanZoomOut());

        checkZoomedAllOut();

        _element.css('cursor', (_zoomEngine.getCanZoomOut()) ? 'pointer' : 'default');
    }
    function zoomEngine_zoomfactorchanged() {
        _hud.setZoomFactor(_zoomEngine.getZoomFactor());
    }
    function element_mouseenter() {
        showHud();
    }
    function element_mousemove() {
        showHud();
    }
    function element_mouseleave() {
        hideHud();
    }

    var hudTimer;
    function element_touchstart() {

        if (hudTimer) {
            clearTimeout(hudTimer);
        }

        hudTimer = setTimeout(function () {
            hideHud();
        }, 5000);

        showHud();
    }

    function hud_zoominclicked() {
        _zoomEngine.zoomIn();
        _zoominclicked = false;
    }
    function hud_zoomoutclicked() {
        _zoomEngine.zoomOut();
        _zoomoutclicked = false;
    }


    // = Functions =
    function checkZoomedAllOut() {
        if (_zoomEngine.getCanZoomOut()) {
            handleNotZoomedAllOut();
        } else {
            handleZoomedAllOut();
        }
    }
    function handleZoomedAllOut() {
        if (_isTransparentWhenZoomedOut) {
            // TODO: _slide.hide();
        }

        _hud.collapse(200);
    }
    function handleNotZoomedAllOut() {
        // TODO: _slide.show();

        _hud.expand(200);
    }

    function getSelectFocusDragCss() {
        var prefixes = ['-webkit-', '-khtml-', '-moz-', '-o-', ''];
        var result = {};
        var i;
        for (i = 0; i < prefixes.length; i++) {
            var prefix = prefixes[i];
            result[prefix + 'user-select'] = 'none';
            result[prefix + 'user-focus'] = 'ignore';
            result[prefix + 'user-drag'] = 'none';
        }
        return result;
    }

    function showHud() {
        if (!_canZoom) return;
        _hud.getElement().stop(true, true).fadeIn();
    }

    function hideHud(forceHide) {
        if (forceHide) {
            _hud.getElement().hide();
        }
        else if (!_zoomEngine.getCanZoomOut()) {
            _hud.getElement().stop(true, true).fadeOut();
        }
    }


    init();
};

// -=====

// ZoomEngine
// +==========================================================================
// Events: contentrectchanged, capabilitieschanged, zoomfactorchanged, overpan
// ===========================================================================
Mediasite.Player.ZoomEngine = function (nativeSize, viewportSize) {

    var _zeroSize = new Mediasite.Geometry.Size(0, 0);
    var _zeroRect = new Mediasite.Geometry.Rect(0, 0, 0, 0);
    var _zeroPoint = new Mediasite.Geometry.Point(0, 0);
    var _nativeSize = nativeSize || _zeroSize;
    var _viewportSize = viewportSize || _zeroSize;
    var _standardZoomLevels = [0.05, 0.10, 0.15, 0.25, 0.50, 0.75, 1.00, 1.50, 2.00];
    var _maxFactor = _standardZoomLevels[_standardZoomLevels.length - 1];
    var _minFactor = calculateJustFitsFactor(_nativeSize, _viewportSize);
    var _minZoomJumpFactor = 1.2;
    var _zoomFactor = _minFactor;
    var _contentRect = new Mediasite.Geometry.Rect(0, 0, _viewportSize.width(), _viewportSize.height());
    var _eventSender = $('<div />'); // A dummy element used for event binding and triggering.
    var _canZoom = true;

    // = Accessors =
    this.getZoomFactor = function () { return _zoomFactor; };

    this.getCanZoomIn = function () {
        return _canZoom && _zoomFactor < _maxFactor;
    };

    this.getCanZoomOut = function () {
        return _canZoom && _zoomFactor > _minFactor;
    };
    this.getCanZoom = function() { return _canZoom; };
    this.setCanZoom = function(value) {
        if (!value) {
            this.zoomAllOut();
        }
        _canZoom = value;
    };

    this.getNativeSize = function () { return _nativeSize; };
    this.setNativeSize = function (value) {
        _nativeSize = value || _zeroSize;
        updateMinLevel();
        respondToZoom(getViewportCenter());
    };

    this.getViewportSize = function () { return _viewportSize; };
    this.setViewportSize = function (value) {
        _viewportSize = value || _zeroSize;
        updateMinLevel();
        respondToZoom(getViewportCenter());
    };

    this.getContentRect = function () { return _contentRect; };


    // = Events =
    function onContentRectChanged(mayAnimate) {
        _eventSender.trigger('contentrectchanged', { rect: _contentRect, mayAnimate: mayAnimate });
    }
    function onCapabilitiesChanged() {
        _eventSender.trigger('capabilitieschanged');
    }
    function onZoomFactorChanged() {
        _eventSender.trigger('zoomfactorchanged');
    }
    function onOverPan(x, y) {
        _eventSender.trigger('overpan', { x: x, y: y });
    }


    // = Methods =
    this.zoomOut = function (zoomPoint) {
        if (!_canZoom) return;
        var point = zoomPoint || getViewportCenter();
        _zoomFactor = getZoomJumpFactors(_zoomFactor).down;

        respondToZoom(point);
    };

    this.zoomIn = function (zoomPoint) {
        if (!_canZoom) return;
        var point = zoomPoint || getViewportCenter();
        _zoomFactor = getZoomJumpFactors(_zoomFactor).up;

        respondToZoom(point);
    };

    this.zoom = function (zoomPoint, scaleBy) {
        if (!_canZoom) return;
        _zoomFactor = Math.min(_maxFactor, Math.max(_minFactor, scaleBy * _zoomFactor));
        respondToZoom(zoomPoint);
    }

    this.zoomAllOut = function () {
        if (!_canZoom) return;
        this.zoom(_zeroPoint, 0);
    };

    this.pan = function (xAmount, yAmount) {
        if (!_canZoom) return;
        var rect = new Mediasite.Geometry.Rect(
			_contentRect.left() + xAmount,
			_contentRect.top() + yAmount,
			_contentRect.width(),
			_contentRect.height()
		);

        setContentRect(rect, false);
    };

    this.bind = function () {
        _eventSender.bind.apply(_eventSender, arguments);
    };

    this.unbind = function () {
        _eventSender.unbind.apply(_eventSender, arguments);
    };


    // = Functions =
    function getZoomJumpFactors(factor) {
        var result = { up: _maxFactor, down: _minFactor };
        var i;
        for (i = 0; i < _standardZoomLevels.length; i++) {
            var level = _standardZoomLevels[i];
            var isActualSize = (level == 1.0);
            var isSignificantlyLargerThanMinimum = level > _minFactor * _minZoomJumpFactor;

            if (isSignificantlyLargerThanMinimum || isActualSize) {

                if (level < factor) {
                    result.down = level;
                } else if (level > factor) {
                    result.up = level;
                    break;
                }

            }
        }

        // Don't allow zooming out to smaller than the minimum.
        result.down = Math.max(result.down, _minFactor);

        // In case the current factor is greater than the typical max,
        // we don't want to zoom out when the user clicks zoom in.
        // Better to just stay at the current large zoom factor. 
        // This can hapen when the just-fits/minimum factor is greater than 
        // the typical maximum factor. For example a very small slide shown 
        // in a large slide area.
        result.up = Math.max(result.up, factor);

        return result;
    }

    function areSizesValid() {
        return _nativeSize.width() > 0 && _nativeSize.height() > 0 &&
            _viewportSize.width() > 0 && _nativeSize.height() > 0;
        //return !_nativeSize.equals(_zeroSize) && !_viewportSize.equals(_zeroSize);
    }

    function setContentRect(rect, mayAnimateChanges) {
        var contained = enforceContainment(rect);

        if (!contained.equals(rect)) {
            var x = rect.left() - contained.left();
            var y = rect.top() - contained.top();
            onOverPan(x, y);
        }

        if (!_contentRect.equals(contained)) {
            _contentRect = contained;
            onContentRectChanged(mayAnimateChanges);
        }
    }

    function enforceContainment(wish) {
        var x = fixOffset(wish.left(), wish.width(), _viewportSize.width());
        var y = fixOffset(wish.top(), wish.height(), _viewportSize.height());

        return new Mediasite.Geometry.Rect(x, y, wish.width(), wish.height());
    }

    function fixOffset(offset, contentDimension, viewportDimension) {
        if (contentDimension < viewportDimension) {
            return Math.round((viewportDimension - contentDimension) / 2);
        } else {
            var minimum = viewportDimension - contentDimension;
            return Math.ceil(Math.max(Math.min(offset, 0.0), minimum));
        }
    }

    function respondToZoom(zoomPoint) {
        var rect = calculateRect(zoomPoint);
        setContentRect(rect, true);

        onZoomFactorChanged();
        onCapabilitiesChanged();
    }

    function calculateRect(zoomPoint) {
        if (!areSizesValid()) {
            return _zeroRect;
        }

        var size = calculateAdjustedSize();
        var left = calculateZoomedOffset(_contentRect.left(), zoomPoint.x(), _contentRect.width(), size.width());
        var top = calculateZoomedOffset(_contentRect.top(), zoomPoint.y(), _contentRect.height(), size.height());
        var rect = new Mediasite.Geometry.Rect(left, top, size.width(), size.height());

        return rect;
    }

    function calculateZoomedOffset(offsetBefore, zoomPointDistance, dimensionBefore, dimensionAfter) {
        if (dimensionBefore == 0) {
            return 0;
        }

        var factor = dimensionAfter / dimensionBefore;
        var contentRelativeZoomPointDistance = zoomPointDistance - offsetBefore;
        var contentRelativeZoomPointDistanceAfter = contentRelativeZoomPointDistance * factor;
        return -(contentRelativeZoomPointDistanceAfter - zoomPointDistance);
    }

    function calculateAdjustedSize() {
        return new Mediasite.Geometry.Size(
			Math.round(_nativeSize.width() * _zoomFactor),
			Math.round(_nativeSize.height() * _zoomFactor)
		);
    }

    function updateMinLevel() {
        var wasAtMinFactor = (_zoomFactor == _minFactor);

        _minFactor = calculateJustFitsFactor(_nativeSize, _viewportSize);

        // Use the new minimum zoom factor if
        // - we were using the old minimum factor or
        // - the current zoom factor is now too low.
        if (wasAtMinFactor || _zoomFactor < _minFactor) {
            _zoomFactor = _minFactor;
        }
    }

    function getViewportCenter() {
        return new Mediasite.Geometry.Point(_viewportSize.width() / 2, _viewportSize.height() / 2);
    }

    function calculateJustFitsFactor(nativeSize, viewportSize) {
        if (!areSizesValid()) {
            return 0.0;
        }

        var widthFactor = viewportSize.width() / nativeSize.width();
        var heightFactor = viewportSize.height() / nativeSize.height();
        return Math.min(widthFactor, heightFactor);
    }
};
// -=====

// ZoomableSlide
// +==================================================================
// Events: nativesizechanged
// ===================================================================
Mediasite.Player.ZoomableSlide = function (element) {
    var _lastAppliedHeight = 0;
    var self = this;

    var _container = element;
    _container.css('position', 'absolute');
    var _img = $('<img />').appendTo(_container).width('100%').height('100%');
    _img.bind('dragstart', function (e) { e.preventDefault(); }); // stop browser stupidity.
    _img.bind('contextmenu', function (e) { return false; }); // bug43739

    var _imageSizeProvider = new Mediasite.Player.ImageSizeProvider();
    var _slideImage;
    var _emptySize = new Mediasite.Geometry.Size(0, 0);
    var _nativeSize = new Mediasite.Geometry.Size(800, 600);
    var _defaultAspect = 4 / 3;
    var _aspectRatio = _defaultAspect;
    var _toolTip = '';
    var _cssTransofrmRulePrefixes = ['-webkit-', '-moz-', '-o-', '-ms-'];

    // accessors
    // +========
    this.getElement = function () { return _container; };

    this.setImageSizeProvider = function (value) { _imageSizeProvider = value; };

    this.getSlideImage = function () { return _slideImage; };
    this.setSlideImage = function (value) {
        _slideImage = value;
        loadSlide();
    };

    this.getNativeSize = function () { return _nativeSize; };
    function setNativeSize(value) {
        if (_nativeSize.equals(value)) {
            return;
        }

        _nativeSize = value;
        onNativeSizeChanged();
    };

    this.getAspectRatio = function () {
        return _aspectRatio;
    };
    this.setAspectRatio = function (value) {
        _aspectRatio = (value > 0) ? value : _defaultAspect;
        loadSlide();
    };

    this.getToolTip = function () { return _img.attr('title'); };
    this.setToolTip = function (value) {
        _img.attr('alt', value).attr('title', value);
    };

    // -=====


    this.applyRect = function (rect, mayAnimate) {
        if (_lastAppliedHeight != rect.height()) {
            loadRightSizedImage(rect.height());
            _lastAppliedHeight = rect.height();
        }

        if (areTransformsAvailable()) {
            applyRectFancy(rect, mayAnimate);
        } else {
            applyRectBasic(rect, mayAnimate);
        }
    }

    this.bind = function () {
        _container.bind.apply(_container, arguments);
    }

    this.unbind = function () {
        _container.unbind.apply(_container, arguments);
    }


    // this.show = function ()
    // this.hide = function ()



    function onNativeSizeChanged() {
        _container.trigger('nativesizechanged');
    }


    function areTransformsAvailable() {
        return Mediasite.Player.Support.csstransforms3d;
    }

    function applyRectBasic(rect, mayAnimate) {
        _container.css({
            'left': rect.left(),
            'top': rect.top()
        });

        _img.css({
            'left': 0,
            'top': 0,
            'width': rect.width(),
            'height': rect.height()
        });
    }

    function applyRectFancy(rect, mayAnimate) {
        var scale = rect.width() / _nativeSize.width();

        if (_img.height() != _nativeSize.height() || _img.width() != _nativeSize.width()) {
            _img.css({
                'width': _nativeSize.width(),
                'height': _nativeSize.height()
            });
        }

        _container.css(makeTransformCssMap(rect.left(), rect.top(), 1.0));
        _img.css(makeTransformCssMap(0, 0, scale));
    }

    function makeTransformCssMap(left, top, scale) {
        var map = {};
        var i;
        for (i = 0; i < _cssTransofrmRulePrefixes.length; i++) {
            var prefix = _cssTransofrmRulePrefixes[i];
            map[prefix + 'transform-origin'] = 'top left';
            //map[prefix + 'transform'] = 'scale(' + scale + ') ' + makeTranslateValue(left, top);
            map[prefix + 'transform'] = 'scale3d(' + scale + ',' + scale + ',1.0) ' + makeTranslateValue(left, top);
        }
        return map;
    }

    function makeTranslateValue(x, y) {
        return 'translate3d(' + x + 'px, ' + y + 'px, 0px)';
    }

    function loadSlide() {
        if (!hasSlide()) {
            showImage('');
            setNativeSize(_emptySize);
            return;
        }

        var fullUrl = _slideImage.getFullUrl();
        if (!fullUrl || !fullUrl.resolve) {
            fullUrl = $.Deferred().resolve(fullUrl);
        }
        fullUrl.done(function (fullUrl) {
            _imageSizeProvider.getSize(fullUrl, function (size) {
                setNativeSize(size);
                loadRightSizedImage();
            });
        });
    }

    function loadRightSizedImage(height) {
        var h = height || _lastAppliedHeight;

        if (!hasSlide()) {
            return;
            // TODO: Unload last slide image?
        }

        var url;
        var nativeAspect = self.getNativeSize().width() / self.getNativeSize().height();
        var cacheFriendlySize = Mediasite.Slide.getCacheFriendlyImageSize(h, nativeAspect);
        if (cacheFriendlySize.height() < self.getNativeSize().height()) {
            url = _slideImage.getCacheFriendlyUrl(h, nativeAspect);
        } else {
            url = _slideImage.getFullUrl();
        }
        if (!url || !url.resolve) {
            url = $.Deferred().resolve(url);
        }
        url.done(function (url) {
            showImage(url);
        });
    }

    function hasSlide() {
        return (_slideImage && typeof (_slideImage.getSlideNumber()) == "number");
    }

    var _lastShownImageSrc = null;
    function showImage(src) {
        if (src === _lastShownImageSrc) {
            return;
        } else if (typeof src === 'string' && src.length > 0) {
            _img.attr('src', src);
            setImgOpacity(1);
        } else {
            _img.removeAttr('src');
            setImgOpacity(0);
        }

        _lastShownImageSrc = src;

        // TODO: Animate the transition.
    }

    function setImgOpacity(value) {
        var currentValue = Number(_img.css('opacity'));
        if (value !== currentValue) {
            _img.css('opacity', value);
        }
    }
};
// -=====

// ZoomViewport
// +========================================================================
// Events: sizechanged, mousedragged, mousewheelup, mousewheeldown, pinched
// =========================================================================
Mediasite.Player.ZoomViewport = function (element) {

    var _element = $(element);
    var _isMouseOver = false;
    var _isGrabbed = false;
    var _lastPosition = null;
    var _lastTouchPosition = null;
    var _lastPinchPosition = null;
    var _lastPinchScale = null;
    var _msGesture = null;


    _element.bind({
        'containerresize': element_resize,
        'mousewheel': element_mousewheel
    });

    if (window.navigator && window.navigator.msPointerEnabled && typeof MSGesture !== 'undefined') {
        // IE10+ on Win8
        var myEl = _element.get(0);
        myEl.addEventListener('MSPointerDown', element_MSPointerDown);
        myEl.addEventListener('MSPointerMove', element_MSPointerMove);
        myEl.addEventListener('MSPointerUp', element_MSPointerUp);
        myEl.addEventListener('MSGestureStart', element_MSGestureStart);
        myEl.addEventListener('MSGestureChange', element_MSGestureChange);
        myEl.addEventListener('MSGestureEnd', element_MSGestureEnd);
        _msGesture = new MSGesture();
        _msGesture.target = myEl;
    } else {
        // everybody else
        _element.bind({
            'mousedown': element_mousedown,
            'mouseup': element_mouseup,
            'mousemove': element_mousemove,
            'touchstart': element_touchstart,
            'touchmove': element_touchmove,
            'touchend': element_touchend
        });
    }


    this.getElement = function () {
        return _element;
    }

    this.getSize = function () {
        return new Mediasite.Geometry.Size(_element.width(), _element.height());
    }


    this.bind = function () {
        _element.bind.apply(_element, arguments);
    }

    this.unbind = function () {
        _element.unbind.apply(_element, arguments);
    }


    function element_resize(e) {
        e.stopPropagation();
        // The containerresize event should only be handled when triggered from above.
        // If this handler is called from an event propagating up, 
        // then a handler farther down the DOM tree should prevent propagation

        onSizeChanged();
    }

    function element_mousewheel(e, delta) {
        if (delta > 0) {
            onMouseWheelUp();
        } else if (delta < 0) {
            onMouseWheelDown();
        }
    }

    function element_mousedown(e) {
        _isGrabbed = true;
        _lastPosition = pos(e.pageX, e.pageY);

        // TODO: capture mouse.
    }

    function element_mouseup(e) {
        _isGrabbed = false;

        // TODO: release mouse.
    }

    function element_mousemove(e) {
        var newPosition = pos(e.pageX, e.pageY);

        handleMouseMove(newPosition);
    }

    function element_touchstart(event) {
        var e = event.originalEvent;
        if (!e || !e.targetTouches) {
            return;
        }

        if (e.targetTouches.length == 1) {
            _lastTouchPosition = getFirstTouchPosition(e.targetTouches[0]);
        } else if (e.targetTouches.length == 2) {
            _lastPinchScale = 1.0;
            _lastPinchPosition = getCenter(e.targetTouches[0], e.targetTouches[1]);
        }
    }

    function element_MSPointerDown(event) {
        if (isTouchPointerEvent(event)) {
            _lastPinchPosition = null;
            _msGesture.addPointer(event.pointerId);
        } else {
            _isGrabbed = true;
        }
    }

    function element_touchmove(event) {
        event.preventDefault();

        var e = event.originalEvent;

        if (!e || !e.targetTouches) {
            return;
        }

        if (e.targetTouches.length == 1) {
            handleSingleTouchMove(getFirstTouchPosition(e.targetTouches[0]));

        } else if (e.targetTouches.length == 2) {
            var pinchPosition = getCenter(e.targetTouches[0], e.targetTouches[1]);
            handleDoubleTouchMove(pinchPosition, e.scale);
        }
    }

    function element_MSPointerMove(event) {
        event.preventDefault();

        var position = getMSPointerPosition(event);

        if (!isTouchPointerEvent(event) && event.isPrimary) {
            // MOUSE (or PEN, I guess).
            handleMouseMove(position);
        }
    }

    function element_touchend(event) {
        var e = event.originalEvent;
        if (e.targetTouches.length == 1) {
            _lastTouchPosition = getFirstTouchPosition(e.targetTouches[0]);
        }
    }

    function element_MSPointerUp(event) {
        _lastPinchPosition = null;
        _isGrabbed = false;
    }

    function element_MSGestureStart(event) {
        _lastPinchPosition = null;
    }

    function element_MSGestureChange(event) {
        var pinchPosition = getMSGestureCenter(event);

        if (_lastPinchPosition) {
            var delta = calculateDelta(_lastPinchPosition, pinchPosition);
            onMouseDragged(delta); // drag before zoom so that gesture center tracks properly.
        }
        _lastPinchPosition = pinchPosition;

        onPinched(pinchPosition, event.scale);
    }

    function element_MSGestureEnd(event) {
        _lastPinchPosition = null;
    }

    function getMSGestureCenter(event) {
        var doc = document.documentElement;
        return pos(event.clientX + doc.scrollLeft, event.clientY + doc.scrollTop);
    }

    function onMouseDragged(delta) {
        _element.trigger('mousedragged', { delta: delta });
    }

    function onMouseWheelDown() {
        _element.trigger('mousewheeldown', { position: makePositionRelativeToElement(_lastPosition) });
    }

    function onMouseWheelUp() {
        _element.trigger('mousewheelup', { position: makePositionRelativeToElement(_lastPosition) });
    }

    function onSizeChanged() {
        _element.trigger('sizechanged');
    }

    function onPinched(position, scaleFactor) {
        _element.trigger('pinched', { position: makePositionRelativeToElement(position), scaleFactor: scaleFactor });
    }


    function isTouchPointerEvent(msPointerEvent) {
        return msPointerEvent.pointerType === msPointerEvent.MSPOINTER_TYPE_TOUCH || msPointerEvent.pointerType === "touch";
    }

    function handleMouseMove(newPosition) {
        if (_isGrabbed) {
            var delta = {
                x: newPosition.x() - _lastPosition.x(),
                y: newPosition.y() - _lastPosition.y()
            };

            onMouseDragged(delta);
        }

        _lastPosition = newPosition;
    }

    function handleSingleTouchMove(touchPosition) {
        var delta = calculateDelta(_lastTouchPosition, touchPosition);

        _lastTouchPosition = touchPosition;

        onMouseDragged(delta);
    }

    function handleDoubleTouchMove(pinchPosition, scale) {
        var delta = calculateDelta(_lastPinchPosition, pinchPosition);
        _lastPinchPosition = pinchPosition;

        var scaleFactor = (scale / _lastPinchScale);
        _lastPinchScale = scale;

        onMouseDragged(delta); // drag before zoom so that gesture center tracks properly.
        onPinched(pinchPosition, scaleFactor);
    }

    function calculateDelta(fromPoint, toPoint) {
        return {
            x: toPoint.x() - fromPoint.x(),
            y: toPoint.y() - fromPoint.y()
        };
    }

    function getFirstTouchPosition(touch) {
        return pos(touch.pageX, touch.pageY);
    }

    function getMSPointerPosition(event) {
        return pos(event.pageX, event.pageY);
    }

    function getCenter(one, two) {
        var x = Math.round(one.pageX + (two.pageX - one.pageX) / 2);
        var y = Math.round(one.pageY + (two.pageY - one.pageY) / 2);
        return pos(x, y);
    }

    function makePositionRelativeToElement(point) {
        var offset = _element.offset();
        return new Mediasite.Geometry.Point(point.x() - offset.left, point.y() - offset.top);
    }

    function pos(pageX, pageY) {
        return new Mediasite.Geometry.Point(pageX, pageY);
    }
};
// -=====
