var Mediasite = Mediasite || {};

Mediasite.PlaybackTracker = (function () {
    var module = Mediasite$TimelineCoverage$Tracker;

    module.TrackerDefaults = {
        tolerance: 1    // player reports contiguous segments as [1:2] [3:4], not [1:2] [2:3].
    };

    function Mediasite$TimelineCoverage$Tracker(player, options) {
        var self = this;
        // #region Private
        var _tolerance; // seconds
        var _rawTimeRanges = [];
        var _duration = -1;


        // Set local variables from options/defaults
        if (!options) options = {};
        var defaults = module.TrackerDefaults;
        for (var option in defaults) {
            if (!options.hasOwnProperty(option)) {
                options[option] = defaults[option];
            }
        }
        _tolerance = options.tolerance;
        if (player) {
            attachToApi(player);
        }


        function addRawTimeRange(newTimeRange) {
            // Insert time range sorted by .start
            var newStart = newTimeRange.start();
            for (var i = 0, len = _rawTimeRanges.length; i < len; i++) {
                var currentTimeRange = _rawTimeRanges[i];
                if (currentTimeRange.start() > newStart) {
                    // Insert item into array at index i
                    _rawTimeRanges.splice(i, 0, newTimeRange);
                    return;
                }
            }

            // newTimeRange is after all the existing time ranges, append it
            _rawTimeRanges.push(newTimeRange);
        }

        var _normalizedTimeRanges = []; // lazy add time ranges
        var _lastRawRangeNormalized = -1;

        // Currently-viewing time range
        var _currentStart, _currentPosition;
        resetCurrentTimeRange();

        function resetCurrentTimeRange() {
            _currentPosition = -1;
            _currentStart = -1;
        }

        function getCurrentNormalizedTimeRanges() {
            updateNormalizedTimeRanges();
            var normalizedTimeRanges = _normalizedTimeRanges;

            // Add currently-viewing time range
            if (0 <= _currentStart && _currentStart < _currentPosition) {
                normalizedTimeRanges = normalizedTimeRanges.slice(0); // clone to keep original _normalizedTimeRanges safe
                var currentTimeRange = new module.TimeRange(_currentStart, _currentPosition);
                normalizedTimeRanges = addNormalizedTimeRange(currentTimeRange, normalizedTimeRanges);
            }

            return normalizedTimeRanges;
        }

        function updateNormalizedTimeRanges() {
            var numRawRanges = _rawTimeRanges.length;
            for (var i = _lastRawRangeNormalized + 1; i < numRawRanges; i++) {
                var timeRange = _rawTimeRanges[i];
                _normalizedTimeRanges = addNormalizedTimeRange(timeRange, _normalizedTimeRanges);
            }

            _lastRawRangeNormalized = numRawRanges - 1;
        }

        function addNormalizedTimeRange(newTimeRange, normalizedTimeRanges) {
            var timeRangeAdded = false;
            // Insert time range sorted by .start
            var newStart = newTimeRange.start();
            var newEnd = newTimeRange.end()
            for (var i = 0, len = normalizedTimeRanges.length; i < len; i++) {
                var currentTimeRange = normalizedTimeRanges[i];
                if (currentTimeRange.intersects(newTimeRange, _tolerance)) {
                    currentTimeRange.merge(newTimeRange, _tolerance);

                    // Swallow up following time ranges which are now contiguous
                    var nextTimeRange;
                    while ((nextTimeRange = normalizedTimeRanges[i + 1]) /* there exists a nextTimeRange */
                        && currentTimeRange.intersects(nextTimeRange, _tolerance)) { /* and it should be swallowed */
                        currentTimeRange.merge(nextTimeRange, _tolerance);  // swallow that TimeRange
                        normalizedTimeRanges.splice(i + 1, 1);  // and remove it from the array
                    }

                    timeRangeAdded = true;
                    break;
                } else if (currentTimeRange.start() > newStart) {
                    // new time range is non-contiguous and fits in right before the current time range
                    normalizedTimeRanges.splice(i, 0, newTimeRange);
                    timeRangeAdded = true;
                    break;
                }
            }

            if (!timeRangeAdded) {
                // newTimeRange is after all the existing time ranges, append it
                normalizedTimeRanges.push(newTimeRange);
            }

            return normalizedTimeRanges;
        }

        // #endregion

        // #region Public methods
        self.attachToPlayer = attachToApi;
        function attachToApi(playerApi) {

            playerApi.addHandler({
                "ready": onReady,
                "timerangeviewed": function (start, end) {
                    self.add(start, end);
                },
                "timerangeviewing": function (start) {
                    self.setCurrentStart(start);
                },
                "currenttimechanged": function (time) {
                    self.setCurrentPosition(time);
                },
                "durationchanged": function (time) {
                    self.setDuration(time);
                }
            });

            if (playerApi.isReady()) {
                onReady();
            }

            function onReady() {
                self.setDuration(playerApi.getDuration());
                var currentTime = playerApi.getCurrentTime();
                self.setCurrentStart(currentTime);
                self.setCurrentPosition(currentTime);
            }
        }

        self.add = function (startTime, endTime, currentlyWatching) {
            if (startTime == endTime) return; // ignore blips

            var timeRange = new module.TimeRange(startTime, endTime);
            addRawTimeRange(timeRange);
            resetCurrentTimeRange();

            if (!Object.defineProperty) {
                self.played.length = getTimeRangeCount();
            }
        };

        self.played = {
            start: getTimeRangeStart,
            end: getTimeRangeEnd,
            length: 0
        };

        if (Object.defineProperty) {
            Object.defineProperty(self.played, "length", {
                get: getTimeRangeCount,
                set: function () { /* read only */ },
                configurable: false
            });
        }


        function getTimeRangeCount() {
            /// <returns type="Number[integer]">Number of normalized time ranges available</returns>
            var timeRanges = getCurrentNormalizedTimeRanges();
            return timeRanges.length;
        };

        function getTimeRangeStart(index) {
            /// <param name="index" type="Number[integer]">Which TimeRange you want</param>
            /// <returns type="Number[integer:seconds]">Start time for the given TimeRange</returns>
            index = 0 + index;
            if (index < 0) return;

            var timeRanges = getCurrentNormalizedTimeRanges();
            if (index >= timeRanges.length) return;

            return timeRanges[index].start();
        };

        function getTimeRangeEnd(index) {
            /// <param name="index" type="Number[integer]">Which TimeRange you want</param>
            /// <returns type="Number[integer:seconds]">End time for the given TimeRange</returns>
            index = 0 + index;
            if (index < 0) return;

            var timeRanges = getCurrentNormalizedTimeRanges();
            if (index >= timeRanges.length) return;

            return timeRanges[index].end();
        };

        self.setTolerance = function (tolerance) {
            /// <param name="tolerance" type="Number[integer:seconds]"></param>
            _tolerance = tolerance;
        };

        self.getTolerance = function () {
            /// <returns type="Number[integer:seconds]"></returns>
            return _tolerance;
        }

        self.setCurrentStart = function (time) {
            /// <param name="time" type="Number[integer:seconds]"></param>
            if (typeof time == "undefined" || time < 0) return;
            _currentStart = time;
        };

        self.setCurrentPosition = function (time) {
            /// <param name="time" type="Number[integer:seconds]"></param>
            if (typeof time == "undefined" || time < 0) return;
            _currentPosition = time;
        };

        self.setDuration = function (time) {
            /// <param name="time" type="Number[integer:seconds]"></param>
            if (typeof time == "undefined" || time <= 0) return;
            _duration = time;
        };

        self.getPercentageWatched = function () {
            /// <returns type="Number[0 to 1]">
            if (_duration <= 0) return;

            var durationWatchedTotal = 0;

            var numTimeRanges = self.played.length;
            for (var i = 0; i < numTimeRanges; i++) {
                var start = self.played.start(i);
                var end = self.played.end(i);
                var durationWatched = end - start + 1; // + 1 because assume that start and end are inclusive
                durationWatchedTotal += durationWatched;
            }

            var percentage = durationWatchedTotal / _duration;

            return percentage;
        };
        // #region
    }


    module.TimeRange = function Mediasite$TimeRange(startTime, endTime) {
        var self = this;
        var _startTime, _endTime;

        // Set start and end times; reverse them if endTime < startTime
        if (startTime < endTime) {
            _startTime = startTime;
            _endTime = endTime;
        } else {
            _startTime = endTime;
            _endTime = startTime;
        }

        self.start = function () { return _startTime; }
        self.end = function () { return _endTime; }
        self.intersects = function (timeRange, tolerance) {
            if (!(timeRange instanceof module.TimeRange)) return;
            tolerance = tolerance || 0;

            if (_endTime < timeRange.start() - tolerance) return false;
            if (timeRange.end() < _startTime - tolerance) return false;

            return true;
        }

        self.merge = function (timeRange, tolerance) {
            if (!self.intersects(timeRange, tolerance)) return false;

            _startTime = Math.min(_startTime, timeRange.start());
            _endTime = Math.max(_endTime, timeRange.end());

            return true;
        }
    }

    return module;
})();