/// <reference path="../../Scripts/jquery.enableTouch.js"/>


(function ($, window, undefined) {
    $.widget('ui.timelineSlider', $.ui.slider, {
        options: {
            _locked: {},
            // Slide preview
            getSlidesAt: null,
            // Slide preview widget options
            defaultSlideImage: null,
            slideAspectRatios: null,
            formatTime: null,
            // MarkerInfos - THE NEW WAY
            markerDrawingEngine: new MarkerDrawingEngine(),
            // Markers
            markers: [], // item: { Time: number in ms, Text: string }
            // Events
            slidePreview: $.noop,
            hidePreview: $.noop
        },
        _init: function () {
            var self = this;
            this.initialized = true;

            this._initSlidePreview();
            this._initCurrentTime();
            this._initMarkers();
            this._initTouchArea();
            this.element.watchLinger({
                events: {
                    lingerchange: $.proxy(this.onSlide, this),
                    lingerend: $.proxy(this.onSlideStop, this),
                    start: 'timelinesliderstart timelineslider:start',
                    change: 'timelinesliderslide timelineslider:slide',
                    end: 'timelinesliderstop timelineslider:stop'
                }
            })
        },
        _initSlidePreview: function () {
            if (!this.slidePreview) {
                this.slidePreview = this.element.find(".preview");
                this.slidePreview.slidePreview();
            }
            var options = {};
            for (var key in $.ui.slidePreview.prototype.options) {
                if (!$.ui.slidePreview.prototype.options.hasOwnProperty(key)) continue;

                if (this.options[key]) options[key] = this.options[key];
            }
            
            this.slidePreview.slidePreview("option", options);
        },
        _initCurrentTime: function () {
            if (!this.currentTime) {
                this.currentTime = this.element.find(".preview-time");
                this.currentTime.currentTimePreview();
            }

            var options = {};
            for (var key in $.ui.currentTimePreview.prototype.options) {
                if (!$.ui.currentTimePreview.prototype.options.hasOwnProperty(key)) continue;

                if (this.options[key]) options[key] = this.options[key];
            }
            
            this.currentTime.currentTimePreview("option", options);
        },
        _initMarkers: function () {
            var self = this;
            this.markers = this.element.find('.markers');


            if (!this.markerDrawingEngine) {
                this.markerDrawingEngine = new MarkerDrawingEngine();
                this.markerDrawingEngine.setDuration(self.options.max / 1000);
                this.markerDrawingEngine.setTrackWidth(this.element.width());
                this.markerDrawingEngine.createMarkerView = function () {
                    var view = new MarkerView();
                    view.createElement(self.markers.get(0));
                    return view;
                };
                this.markerDrawingEngine.destroyMarkerView = function (view) {
                    view.destroyElement();
                };
                var thisTrigger = this._trigger.bind(this);
                this.markerDrawingEngine.reportClick = function (markerTime) {
                    var uiHash = {
                        value: markerTime * 1000 // Seconds to milliseconds.
                    };
                    thisTrigger("stop", null, uiHash);
                };
            }


            var i;
            for (i = 0; i < this.options.markers.length; i += 1) {
                this.markerDrawingEngine.addMarker(this.options.markers[i]);
            }
        },
        _initTouchArea: function () {
            this.element
                .watchLinger({
                    selector: '.toucharea, .thumb',
                    events: {
                        'lingerchange': this.onMouseMove.bind(this),
                        'lingerend': this.onMouseOut.bind(this),
                        'end': 'mouseleave touchend adjacenttouchend'
                    }
                })
                .enableTouch();
        },

        _setOption: function (key, value) {
             if (key in this.options._locked) {
                this._setLockedOption(key, value);
                return;
            }

            var rangeChanged = (key === "max" && this.options.max != value)
                || (key === "min" && this.options.min != value);

            this._super(key, value);

            if (rangeChanged || key == "markers") {
                this.markerDrawingEngine.setDuration(this.options.max / 1000);
            }
            if (this.slidePreview && $.ui.slidePreview.prototype.options.hasOwnProperty(key)) {
                this.slidePreview.slidePreview("option", key, value);
            }

            if (key === "trackWidth") {
                this.markerDrawingEngine.setTrackWidth(value);
            }
        },
        _setLockedOption: function(key, value) {
            this.options._locked[key] = value;
        },
        _getLockedOption: function(key) {
            if (key in this.options._locked) {
                return this.options._locked[key];
            } else {
                return this.options[key];
            }
        },
        lockOption: function(key) {
            if (key in this.options._locked) return;
            this._setLockedOption(key, this.options[key]);
        },
        unlockOption: function (key) {
            var value = this._getLockedOption(key);
            delete this.options._locked[key];
            this._setOptions(key, value);
        },
        
        onSlide: function(event) {
            this._lingeringSliding = true;

            this.lockOption('max');
            this.showPreview(event, event.data.value, true);
        },

        onSlideStop: function(event) {
            this._lingeringSliding = false;

            this.unlockOption('max');
            this.hidePreview();
        },

        onMouseMove: function (event) {
            if (this._lingeringSliding) {
                event.data.reset();
                return;
            }

            var position;
            var className = 'timeline-marker-root';
            var target = event.target;
            if (isTimelineMarkerElement(target)) {
                position = getSecondsFromMarkerElement(target) * 1000;
            } else if (isTimelineMarkerElement(target.parentElement)) {
                position = getSecondsFromMarkerElement(target.parentElement) * 1000;
            }

            this.lockOption('max');
            var enlargedSlide = event.data && event.data.extended;
            this.showPreview(event, position, enlargedSlide);
        },

        onMouseOut: function () {
            // window.console && console.log(event.type, event);
            if (this._lingeringSliding) {
                return;
            }
            
            this.unlockOption('max');
            this.hidePreview();
        },

        showPreview: function (positionOn, sliderValue, enlargedSlide) {
            var self = this;

            if (sliderValue === void 0) {
                positionOn = positionOn || this.handle;
                sliderValue = this.getSliderValueFromPosition(positionOn);
            }
            
            if (enlargedSlide) {
                this.slidePreview.slidePreview("hide", true);
                self.showCurrentTime(sliderValue);
                self.showEnlargedSlidePreview(sliderValue);
            } else {
                this._trigger('hidePreview');
                this.hideCurrentTime();
                self.showSlidePreview(sliderValue);
            }
        },
        hidePreview: function () {
            this.slidePreview.slidePreview("hide");
            this.hideCurrentTime();
            this._trigger('hidePreview');
        },
        hideCurrentTime: function() {
            this.currentTime.currentTimePreview("hide", true);
        },

        showSlidePreview: function (time) {
            if (this.options.disabled) return;

            var images = this.options.getSlidesAt(time);
            if (!(images && images.length)) { 
                this.slidePreview.slidePreview("hide"); 
                this.showCurrentTime(time);
            } else {
                var positionAt = 'left+' + (time / this.options.max * 100) + '%' + ' center';
                var urls = $.map(images, function (image, index) { return image.url; });
                this.slidePreview.slidePreview("show", this.element, { images: urls, time: time }, positionAt);
                this.hideCurrentTime();
            }
        },

        showEnlargedSlidePreview: function (sliderValue) {
            if (this.options.disabled) return;

            this._trigger('slidePreview', null, { time: sliderValue });
        },

        showCurrentTime: function (time) {
            if (this.options.disabled) return;
            
            var positionAt = 'left+' + (time / this.options.max * 100) + '%' + ' center';
            this.currentTime.currentTimePreview("show", this.element, {  time: time }, positionAt);
        },

        getSliderValueFromPosition: function (positionOn) {
            var left;

            var e = positionOn;
            while (e && typeof e.pageX === "undefined") {
                e = e.event || e.originalEvent;
            }

            if (e && typeof e.pageX !== "undefined") {
                var sliderLeft = this.element.offset().left;
                left = e.pageX - sliderLeft;
            } else if (typeof positionOn.offsetX !== "undefined") {
                left = positionOn.offsetX;
            }


            var width = this.element.width();

            left = Math.min(Math.max(0, left), width);

            var relativePosition = left / width;
            var sliderValue = this.options.max * relativePosition;

            return sliderValue;
        },

        addMarker: function (marker) {
            this.markerDrawingEngine.addMarker(marker);
        },

        removeMarker: function (id) {
            this.markerDrawingEngine.removeMarker(id);
        },

        checkResized: function () {
            this.markerDrawingEngine.setTrackWidth(this.element.width());
        }
    });

    function isTimelineMarkerElement(element) {
        return element.className.indexOf('timeline-marker-root') > -1;
    }

    function getSecondsFromMarkerElement(element) {
        var str = element.getAttribute('data-time');
        var seconds = parseFloat(str);
        return seconds;
    }


    $.widget('ui.callout', {
        options: {
            fadeIn: 'fast',
            callout: '.callout'
        },
        _init: function () {
            this.callout = this.element.find(this.options.callout);

            this.element.hide();
            this.initialized = true;
            this.options._locked = {};
        },
        _setOption: function (key, value) {
            this._super(key, value);
            if (this.initialized) {
                this.populate();
            }
        },
        show: function (positionOn, value, positionAt) {
            if (typeof value != "undefined") {
                this.options.value = value;
            }
            if (!this.initialized) return;
            value = this.options.value;

            var populated = this.populate(value);
            if (populated !== false) {
                if (!this._visible) this.reveal();
                while (positionOn.originalEvent) {
                    positionOn = positionOn.originalEvent;
                }

                this.position(positionOn, positionAt, this._visible);
            } else {
                this.hide();
            }
        },
        hide: function (instant) {
            if (instant) {
                this.element.hide();
            } else {
                this.element
                    .stop(true, true)
                    .fadeOut('fast');
            }

            this._visible = false;
        },
        reveal: function () {
            this.element
                .stop(true, true)
                .fadeIn(this.options.fadeIn);
            

            this._visible = true;
        },
        position: function (positionOn, positionAt, keepVisible) {
            if (keepVisible === void 0) {
                keepVisible = positionAt;
                positionAt = void 0;
            }
            var self = this;
            var elements = this.element.add(this.callout);

            elements
                .show()
                .position({
                    my: "center",
                    at: positionAt !== void 0 ? positionAt : "center",
                    of: positionOn,
                    collision: 'fit',
                    using: function (offset, ui) {
                        ui.element.element.css({ left: offset.left });
                    }
                });
            if (!keepVisible) { 
                elements.hide(); 
            }
        },
        populate: $.noop  // must return truthy value for callout to show
    });

    $.widget('ui.slidePreview', $.ui.callout, {
        options: {
            callout: '.thumb',
            defaultSlideImage: '', // image URL
            defaultSlideAspectRatio: '16x9', // '16x9' or '4x3'
            slideAspectRatios: [ ],
            theClasses: {
                '4x3': 'preview-4x3',
                '16x9': 'preview-16x9',
                'image': 'image'
            },
            formatTime: function (ms) {
                var minutes = Math.floor(ms / 1000 / 60);
                var seconds = Math.floor(ms / 1000 % 60);
                var display = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
                return display;
            }
        },
        _create: function () {
            this._super();

            this.time = this.element.find('.time');
            this.imageContainer = this.element.find('.images');

            this._updateAspectRatio();

        },
        _setOption: function(key, value) {
            this._super(key, value);
            if (key == 'slideAspectRatio') { this._updateAspectRatio(); }
        },
        populate: function (value) {
            if (!this.initialized || !value) return;

            var time = value.time;

            var images = value.images;
            if (images) {
                this._showImages(images);
            } else if (this.options.defaultSlideImage) {
                this._showImages();
            } else {
                this._hideImages();
            }   

            var formattedTime = this.options.formatTime(time);
            this.time.text(formattedTime);
        },

        _updateAspectRatio: function() {
            this._populateImages(0, true);
            this._showImages();
        },

        _showImages: function (urls) {
            this.urls = urls = urls || this.urls || [];
            this._populateImages(urls.length);

            for (var i = 0, length = this.images.length; i < length; i++) {
                var imageElem = this.images[i];

                var url = urls[i] || this.options.defaultSlideImage;
                imageElem.fittedImage('src', url);
            }

            this.imageContainer.show();
        },
        _hideImages: function() {
            this.imageContainer.hide();
        },
        _populateImages: function (minLength, refresh) {
            var numImages = Math.max(minLength, this.options.slideAspectRatios.length);

            if (!this.images || this.images.length < numImages || refresh) {
                this.imageContainer.empty();
                this.images = [];

                for (var i = 0; i < numImages; i++) {
                    var aspectRatio = this.options.slideAspectRatios[i];
                    var aspectRatioClass = this.options.theClasses[aspectRatio] || this.options.theClasses[this.options.defaultSlideAspectRatio];

                    var image = $("<div><img></div>").appendTo(this.imageContainer);

                    image.addClass(this.options.theClasses.image);
                    image.addClass(aspectRatioClass);

                    var imageElem = image.find('img');
                    imageElem.fittedImage(image);

                    this.images.push(imageElem);
                }
            }

            return this.images.length;
        },

        position: function() {
            this._superApply(arguments);

            var widths = this.imageContainer.children().map(function (index, element) { return $(element).outerWidth(true); });
            var widthSum = 0; $.each(widths, function (index, width) { widthSum += width; });

            this.imageContainer.css('width', widthSum + 'px');


            var images = this.imageContainer.children();
            if (images.length % 2 == 0) {
                this.time.position({
                    my: "right bottom",
                    at: "right bottom",
                    of: images[images.length / 2 - 1],
                    using: function (offset, ui) {
                        ui.element.element.css('left', offset.left);
                    }
                });
            }
        }



    });

    $.widget('ui.currentTimePreview', $.ui.callout, {
        options: {
            fadeIn: 0,
            callout: '.thumb',
            formatTime: function (ms) {
                var minutes = Math.floor(ms / 1000 / 60);
                var seconds = Math.floor(ms / 1000 % 60);
                var display = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
                return display;
            }
        },
        _create: function () {
            this._super();

            this.time = this.element.find('.time');

        },
        populate: function (value) {
            if (!this.initialized || !value) return;

            var time = value.time;
            var formattedTime = this.options.formatTime(time);
            this.time.text(formattedTime);
        }
    });


    // Marker = {
    //     id: 'unique-id',
    //     time: 60,
    //     text: 'UI Text'
    // };

    function MarkerDrawingEngine() {
        var self = this;
        var duration = 0;
        var trackWidth = 0;
        var markerInfoLookup = new Mediasite.Player.LookupTable();
        var viewLookup = new Mediasite.Player.LookupTable();
        var sortedMarkerInfosCache = []; // Use getSortedMarkersInfos() to access this.

        var timeMarkerInfoListLastSorted = 0;
        var timeMarkerInfoListLastChanged = 0;
        


        

        this.createMarkerView = function () { throw "Not Implemented"; };
        this.destroyMarkerView = function (markerView) { throw "Not Implemented"; };
        this.reportClick = function (markerTime) { throw "Not Implemented"; };


        this.getNowTimeDelegate = function () { return (new Date()).getTime(); };


        this.addMarker = function (markerInfo) {
            if (markerInfoLookup.hasItemForKey(markerInfo.id)) {
                // This is a repeat add, so erase before redraw.
                eraseMarker(markerInfo.id);
            }

            markerInfoLookup.add(markerInfo.id, markerInfo);
            markerInfoListChanged();

            drawMarker(markerInfo);

            // TODO: This requires restacking.
            
            lazyAdjustTargetEdges();
        };

        this.removeMarker = function (markerId) {
            markerInfoLookup.remove(markerId);
            markerInfoListChanged();

            eraseMarker(markerId);

            // TODO: This requires restacking IF this was part of a stack.
            
            lazyAdjustTargetEdges();
        };


        this.setDuration = function (seconds) {
            if (seconds === duration) {
                return;
            }

            duration = seconds;

            updateExistingMarkerPositions();

            // TODO: This requires restacking.
            
            // Must happen after marker positions are updated, since positions are used to calculate target edges.
            lazyAdjustTargetEdges();
        };

        this.setTrackWidth = function (pixels) {
            trackWidth = pixels;

            // TODO: This requires restacking.
            
            lazyAdjustTargetEdges();
        };



        function drawMarker(markerInfo) {
            var view = getViewForMarkerId(markerInfo.id);
            view.markerInfo = markerInfo;
            view.clickFunction = clickFunction;
            view.position = timeToPercent(markerInfo.time);
            view.color = markerInfo.color;
            view.draw();
        }

        function clickFunction(markerInfo) { self.reportClick(markerInfo.time); /*Mediasite.Player.API.seekTo(markerInfo.time);*/ };

        function eraseMarker(markerId) {
            var view = viewLookup.getByKey(markerId);
            if (view) {
                viewLookup.remove(markerId);
                self.destroyMarkerView(view);
            }
        }
        


        function timeToPercent(seconds) {
            if (duration <= 0) {
                return 0;
            }

            return (seconds / duration) * 100;
        }

        //var lazyDrawAll = Mediasite.Player.debounce(drawAll, 350);
        
        //function drawAll() {
        //    var i, info;
        //    var sortedInfos = getSortedMarkersInfos();
         
        //    for (i = 0; i < sortedInfos.length; i += 1) {
        //        info = sortedInfos[i];
        //    }
        //}


        function updateExistingMarkerPositions() {
            var i, view;
            var views = viewLookup.getAll();

            views.sort(compareViews);

            for (i = 0; i < views.length; i += 1) {
                view = views[i];
                view.position = timeToPercent(view.markerInfo.time);
                view.draw();
            }
        }


        var lazyAdjustTargetEdges = Mediasite.Player.debounce(adjustTargetEdges, 350);

        function adjustTargetEdges() {

            var i, lastIndex, view, nextView, leftView, rightView, positionDiff, pxBetween;
            var pxPerPercent = trackWidth / 100;
            var views = viewLookup.getAll();

            views.sort(compareViews);

            lastIndex = views.length - 1;
            for (i = 1; i <= lastIndex; i += 1) {
                leftView = views[i - 1];
                rightView = views[i];

                positionDiff = rightView.position - leftView.position;
                halfPixelsBetween = positionDiff * pxPerPercent / 2;

                leftView.targetRightMaxWidth = halfPixelsBetween;
                rightView.targetLeftMaxWidth = halfPixelsBetween;
                
                if (i === 1) {
                    leftView.targetLeftMaxWidth = 999;
                }

                if (i === lastIndex) {
                    rightView.targetRightMaxWidth = 999;
                }

                leftView.draw();
                rightView.draw();
            }
        }


        function getViewForMarkerId(markerId) {
            var view = viewLookup.getByKey(markerId);

            if (!view) {
                view = self.createMarkerView();
                viewLookup.add(markerId, view);
            }

            return view;
        }

        


        function comparePosition(a, b) {
            return (a.position - b.position);
        }

        function compareViews(a, b) {
            if (a.position != b.position) {
                return (a.position - b.position);
            } else if (a.markerInfo.id == b.markerInfo.id) {
                return 0;
            } else {
                return a.markerInfo.id < b.markerInfo.id ? -1 : 1;
            }
        }



        function getSortedMarkersInfos() {
            var list;

            if (timeMarkerInfoListLastChanged > timeMarkerInfoListLastSorted) {
                list = markerInfoLookup.getAll();
                list.sort(compareTime);

                sortedMarkerInfosCache = list;
                timeMarkerInfoListLastSorted = self.getNowTimeDelegate();
            }

            return sortedMarkersCache;
        }

        function markerInfoListChanged() {
            timeMarkerListLastChanged = self.getNowTimeDelegate();
        }



        function findArrayItemsNotInOtherArray(arrayA, arrayB) {
            var i;
            var result = [];

            for (i = 0; i < arrayA.length; i += 0) {
                if (!arrayB.indexOf(arrayA[i])) {
                    result.push(arrayA[i]);
                }
            }

            return result;
        }


    }



    // The visible marker is quite small, but its touch/click target area extends 
    // farther to the left and right. 
    // 
    //  L   P   R
    //  |   |   |
    // 


    function MarkerView() {
        this.markerInfo = {};

        this.position = 0.0; // %
        this.targetLeftMaxWidth = 10; // px
        this.targetRightMaxWidth = 10; // px

        this._rootElement = null;
        this._markShapeElement = null;
        this._markSvgElement = null;
        this._targetElement = null;
        this._previewElement = null;
        this._textWrapperElement = null;
        this._timeTextElement = null;
        this._mainTextElement = null;
        this._calloutSvgElement = null;
        this._calloutPathElement = null;

        this._lastDrawValues = {};
    }
    MarkerView.prototype = {
        _rootElement: null,
        _markSvgElement: null,
        _markShapeElement: null,
        _targetElement: null,
        _previewElement: null,
        _textWrapperElement: null,
        _timeTextElement: null,
        _mainTextElement: null,
        _calloutSvgElement: null,
        _calloutPathElement: null,
        _lastDrawValues: {},
        _onClick: function (e) { this.clickFunction(this.markerInfo); },

        normalTargetWidthPerSide: 10,
        previewTextHorizontalPadding: 4,

        markerInfo: {},

        clickFunction: function (markerInfo) { },

        position: 0.0, // %
        targetLeftMaxWidth: 999, // px
        targetRightMaxWidth: 999, // px

        draw: function () {
            var visibility, position, color, targetLeft, targetRight, lightColor, text, time;
            var textChanged, positionChanged, timeChanged;
            var widthOfBox, leftVal;
            var last = this._lastDrawValues;

            if (!this._rootElement) {
                throw new Error("draw() was called on a destoyed MarkerView instance.");
            }

            visibility = 'visible';
            position = this.position + '%';
            color = this.markerInfo.color;
            targetLeft = Math.min(this.normalTargetWidthPerSide, this.targetLeftMaxWidth);
            targetRight = Math.min(this.normalTargetWidthPerSide, this.targetRightMaxWidth);
            text = this.markerInfo.text;
            time = this.markerInfo.time;

            // Only update properties that need to change.
            if (visibility !== last.visibility) {
                this._rootElement.style.visibility = visibility;
                last.visibility = visibility;
            }

            positionChanged = position !== last.position;
            if (positionChanged) {
                this._rootElement.style.left = position;
                last.position = position;
            }

            if (color !== last.color) {
                lightColor = Mediasite.Color.lighten(color);
                this._markShapeElement.style.fill = color;
                this._previewElement.style.backgroundColor = lightColor;
                this._calloutPathElement.style.fill = lightColor;
                last.color = color;
            }

            if (targetLeft !== last.targetLeft || targetRight !== last.targetRight) {
                this._targetElement.style.left = -targetLeft + 'px';
                this._targetElement.style.width = targetLeft + targetRight + 'px';
                last.targetLeft = targetLeft;
                last.targetRight = targetRight;
            }

            textChanged = text !== last.text;
            timeChanged = time !== last.time;
            if (textChanged || timeChanged) {
                this._timeTextElement.innerText = formatMarkerTime(time);
                this._mainTextElement.innerText = this.markerInfo.text;
            }

            if (textChanged || positionChanged) {
                widthOfBox = this._previewElement.getBoundingClientRect().width - (2 * this.previewTextHorizontalPadding);
                leftVal = -(this.position / 100) * widthOfBox + -this.previewTextHorizontalPadding;
                this._previewElement.style.left = leftVal + 'px';
                this._calloutSvgElement.style.left = -leftVal + 'px'
            }



            //this._previewElement.className = 'marker-preview ready'; // else 'marker-preview'

            this._rootElement.setAttribute('data-time', this.markerInfo.time);
        },

        svgNameSpace: "http://www.w3.org/2000/svg",

        createMarkSvg: function () {
            var svg = document.createElementNS(this.svgNameSpace, "svg");
            svg.setAttribute("viewBox", "0 0 100 100");
            return svg;
        },

        createMarkCircle: function () {
            var circle = document.createElementNS(this.svgNameSpace, "circle");
            circle.setAttribute("cx", "50");
            circle.setAttribute("cy", "50");
            circle.setAttribute("r", "50");
            circle.setAttribute("fill", "#fff");
            circle.setAttribute("opacity", "1.0");
            return circle;
        },

        createCalloutSvg: function () {
            var svg = document.createElementNS(this.svgNameSpace, "svg");
            svg.setAttribute("viewBox", "0 0 8 16");
            return svg;
        },

        createCalloutShape: function () {
            var path = document.createElementNS(this.svgNameSpace, "path");
            path.setAttribute("d", "M0 7 V 0 H 8 V 7 L 4 16 Z");
            return path;
        },

        createElement: function (containerElement) {
            var rootElement, markSvgElement, markShapeElement, targetElement, previewElement, textWrapperElement, timeTextElement, mainTextElement, calloutSvgElement, calloutPathElement;
            // Add root element.
            // Stylesheet will set position:absolute, width:0, z-index, etc.
            rootElement = document.createElement('a');
            rootElement.className = 'timeline-marker-root';
            rootElement.style.visibility = 'hidden';
            containerElement.appendChild(rootElement);

            // Add element for visual marker.
            // Stylesheet will set position, size, etc.
            markSvgElement = this.createMarkSvg();
            markSvgElement.setAttribute("class", "mark");
            markShapeElement = this.createMarkCircle();
            markShapeElement.setAttribute("class", "marker-shape");
            markSvgElement.appendChild(markShapeElement);
            rootElement.appendChild(markSvgElement);

            // Add child element for touch/click target.
            targetElement = document.createElement('div');
            targetElement.className = 'target';
            rootElement.appendChild(targetElement);

            // Add child elements for preview.
            previewElement = document.createElement('div');
            previewElement.className = 'marker-preview';
            rootElement.appendChild(previewElement);

            calloutSvgElement = this.createCalloutSvg();
            calloutSvgElement.setAttribute("class", "callout");
            calloutPathElement = this.createCalloutShape();
            calloutSvgElement.appendChild(calloutPathElement);
            previewElement.appendChild(calloutSvgElement);

            textWrapperElement = document.createElement('div');
            textWrapperElement.className = 'text';
            timeTextElement = document.createElement("span");
            timeTextElement.className = "time";
            textWrapperElement.appendChild(timeTextElement);
            mainTextElement = document.createElement("span");
            textWrapperElement.appendChild(mainTextElement);
            previewElement.appendChild(textWrapperElement);

            rootElement.addEventListener('mousedown', stopPropagation);
            rootElement.addEventListener('click', this._onClick.bind(this));

            this._rootElement = rootElement;
            this._markSvgElement = markSvgElement;
            this._markShapeElement = markShapeElement;
            this._targetElement = targetElement;
            this._previewElement = previewElement;
            this._textWrapperElement = textWrapperElement;
            this._timeTextElement = timeTextElement;
            this._mainTextElement = mainTextElement;
            this._calloutSvgElement = calloutSvgElement;
            this._calloutPathElement = calloutPathElement;
        },

        destroyElement: function () {
            if (this._rootElement.parentNode) {
                this._rootElement.parentNode.removeChild(this._rootElement);
            }
            this._rootElement = null;
            this._markShapeElement = null;
            this._markSvgElement = null;
            this._targetElement = null;
            this._previewElement = null;
            this._textWrapperElement = null;
            this._timeTextElement = null;
            this._mainTextElement = null;
            this._calloutSvgElement = null;
            this._calloutPathElement = null;
        }
    };

    function StackView() {

    }


    function MarkerStack(arrayOfMarkers) {
        var i;
        var id;
        var markers = [];

        this.getIdentifier = function () {
            if (typeof id === 'undefined') {
                id = makeIdentifier();
            }

            return id;
        };
        
        this.getMarkers = function () {
            return markers;
        };

        this.addMarker = function (marker) {
            markers.push(marker);
            id = undefined;
        };


        function makeIdentifier() {
            var i
            var ids = [];

            for (i = 0; i < markers.length; i += 1) {
                ids.push(markers[i].id);
            }

            ids.sort();

            return ids.join('|');
        }
    }

    function formatMarkerTime(seconds) {
        return Mediasite.Player.TimeHelper.getDisplayFromDuration(seconds * 1000);
    }

    function compareTime(a, b) {
        return (a.time - b.time);
    }

    function stopPropagation(e) {
        e.stopPropagation();
    }


})(jQuery, window);
