/// <reference path="../ScriptReference/jquery-1.5.2.js" />
/// <reference path="./Mediasite.Core.js" />
/// <reference path="./Mediasite.Geometry.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;

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

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

        _zoomInButton.click(zoomInButton_click);
        _zoomInButton2.click(zoomInButton_click);
        _zoomOutButton.click(zoomOutButton_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);
    };

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

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

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


    // = Event Handlers =
    function zoomInButton_click() { _element.trigger('zoominclicked'); }
    function zoomOutButton_click() { _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 _hudContainer, _percentageText, _zoomInButton, _zoomInButton2, _zoomOutButton; // HUD elements
    var _isTransparentWhenZoomedOut = false;

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

        // 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,
            'mouseleave': element_mouseleave
        });


        checkZoomedAllOut();
        //updateHudMargin();
    }


    // = 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();
    };

    // Do we need this?
    //this.getHudMargin = function () { };
    //this.setHudMargin = function (value) { };
    //function updateHudMargin() { }


    // = 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() {
        // TODO: goto MouseOverState.
    }
    function element_mouseleave() {
        // TODO: goto MouseOffState.
    }
    function hud_zoominclicked() {
        _zoomEngine.zoomIn();
    }
    function hud_zoomoutclicked() {
        _zoomEngine.zoomOut();
    }


    // = 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;
    }


    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.


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

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

    this.getCanZoomOut = function () {
        return _zoomFactor > _minFactor;
    };

    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) {
        var point = zoomPoint || getViewportCenter();
        _zoomFactor = getZoomJumpFactors(_zoomFactor).down;

        respondToZoom(point);
    };

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

        respondToZoom(point);
    };

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

    this.zoomAllOut = function () {
        this.zoom(_zeroPoint, 0);
    };

    this.pan = function (xAmount, yAmount) {
        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 />').width('100%').height('100%').appendTo(_container);
    _img.bind('dragstart', function (e) { e.preventDefault(); }); // stop browser stupidity.

    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-'];

    // 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 Modernizr.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();
        _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 = null;
        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();
        }
        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;

    _element.bind({
        'containerresize': element_resize,
        'mousewheel': element_mousewheel,
        '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);

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

            onMouseDragged(delta);
        }

        _lastPosition = 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_touchmove(event) {
        event.preventDefault();

        var e = event.originalEvent;

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

        if (e.targetTouches.length == 1) {
            var touchPosition = getFirstTouchPosition(e.targetTouches[0]);
            var delta = calculateDelta(_lastTouchPosition, touchPosition);
            _lastTouchPosition = touchPosition;

            onMouseDragged(delta);

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

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

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

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


    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 calculateDelta(fromPoint, toPoint) {
        return {
            x: toPoint.x() - fromPoint.x(),
            y: toPoint.y() - fromPoint.y()
        };
    }

    function getFirstTouchPosition(touch) {
        return pos(touch.pageX, touch.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);
    }
};
// -=====



Mediasite.Player.ImageSizeProvider = function () {
    var self = this;
    var _knownSizes = {};
    var _defaultSize = new Mediasite.Geometry.Size(800, 600);
    var _lastSize = _defaultSize;
    var _imageRuler = new Mediasite.Player.ImageRuler();

    this.getSize = function (src, completed) {
        if (!src) {
            completed(Mediasite.Geometry.Size.Zero());
            return;
        }

        function foundSize(size) {
            debug('foundSize ' + size.toString());
            self.rememberSize(src, size);
            _lastSize = size;
            completed(size);
        }

        runSizer(fetchKnownSize, src, foundSize, function () {
            runSizer(makeHeadRequestForSize, src, foundSize, function () {
                runSizer(measureImageForSize, src, foundSize, function () {
                    runSizer(guessSize, src, foundSize, function () {
                        // This should never happen.
                        throw 'Failed to determine slide dimensions.';
                    });
                });
            });
        });

    };


    // Runs asynchronous sizer methods in a manner that allows failover chaining.
    function runSizer(sizer, src, success, failure) {
        sizer(src, function (size) {
            if (size) {
                success(size);
            } else {
                failure();
            }
        });
    }


    this.rememberSize = function (url, size) {
        if (url) {
            _knownSizes[url] = size;
        }

        return size;
    }


    function parseHeaderToSize(value) {
        if (typeof value !== 'string') {
            return null;
        }

        var parts = value.split('x');
        if (parts.length !== 2) {
            return null;
        }

        var x = parseInt(parts[0]);
        var y = parseInt(parts[1]);

        if (isNaN(x) || isNaN(y)) {
            return null;
        }

        return new Mediasite.Geometry.Size(x, y);
    }

    function debug(msg) {
        //console.log('[ImageSizeProvider] ' + msg);
    }


    // Sizer Methods

    function fetchKnownSize(src, callback) {
        debug('fetchKnownSize');
        callback(_knownSizes[src]);
    }

    function makeHeadRequestForSize(src, callback) {
        debug('makeHeadRequestForSize');

        if (!Mediasite.Player.ImageSizeProvider.useXHR) {
            callback(null);
            return;
        }

        var jqxhr = $.ajax(src, { type: 'HEAD' })
            .done(function () {
                var headerString = jqxhr.getResponseHeader('x-native-dimensions');
                var size = parseHeaderToSize(headerString);
                callback(size);
            })
            .fail(function () {
                debug('ajax fail');
                callback(null);
            });
    }

    function measureImageForSize(src, callback) {
        debug('measureImageForSize');
        _imageRuler.measure(src, function (size) {
            if (size && !Mediasite.Geometry.Size.Zero().equals(size)) {
                callback(size);
            } else {
                callback(null);
            }
        });
    }

    function guessSize(src, callback) {
        debug('guessSize');

        // Consider adding more sophisticated guessing logic.
        callback(_lastSize || _defaultSize);
    }
};
Mediasite.Player.ImageSizeProvider.useXHR = (window.location.href.indexOf('file:') != 0);


Mediasite.Player.ImageRuler = function () {
    var img = $('<img />');

    this.measure = function (src, callback) {
        img.bind({
            'load': function () {
                clearImg();
                callback(new Mediasite.Geometry.Size(this.width, this.height));
            },
            'error': function () {
                clearImg();
                callback(Mediasite.Geometry.Size.Zero());
            }
        });

        if (typeof src === 'string' && src.length > 0) {
            img.attr('src', src);
        } else {
            img.trigger('error');
        }
    };

    function clearImg() {
        img.unbind();
        img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
    }
}

Mediasite.Player.SlideImage = function (slideNumber, slideUrlProvider) {
	var _slideUrlProvider = slideUrlProvider;
	var _slideNumber = slideNumber;


	this.getSlideNumber = function () { return _slideNumber; };

	this.getFullUrl = function () {
		return _slideUrlProvider.GetSlideUrl(_slideNumber);
	};

	this.getCacheFriendlyUrl = function (height, aspect) {
		return _slideUrlProvider.GetCacheFriendlySlideUrl(_slideNumber, height, aspect);
	};
}