/**
 * Copyright 2015 CANAL+ Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import log from "../../../../log";
/**
 * Check if there is a soon-to-be-encountered discontinuity in the buffer that
 * won't be filled by any future segment.
 * This function will only check discontinuities for the given `checkedRange`.
 *
 * @param {Object} content - The content we are currently loading.
 * @param {Object} checkedRange - The time range that will be checked for
 * discontinuities.
 * Both `nextSegmentStart` and `bufferedSegments` arguments can only refer to
 * that range.
 * @param {number|null} nextSegmentStart - The start time in seconds of the next
 * not-yet-pushed segment that can be pushed, in the limits of `checkedRange`.
 * This includes segments which have not been loaded or pushed yet, but also
 * segments which might be re-downloaded because currently incomplete in the
 * buffer, the point being to know what is the earliest time in the buffer where
 * a segment might be pushed in the future.
 * `null` if no segment in `checkedRange` will be pushed under current buffer's
 * conditions.
 * @param {boolean} hasFinishedLoading - if `true`, all segments for the current
 * Period have been loaded and none will be loaded in the future under the
 * current buffer's state.
 * @param {Array.<Object>} bufferedSegments - Information about every segments
 * currently in the buffer, in chronological order.
 * Only segments overlapping with the given `checkedRange` will be looked at,
 * though the array given can be larger.
 */
export default function checkForDiscontinuity(content, checkedRange, nextSegmentStart, hasFinishedLoading, bufferedSegments) {
    var period = content.period, adaptation = content.adaptation, representation = content.representation;
    // `bufferedSegments` might also contains segments which are before
    // `checkedRange`.
    // Here we want the first one that goes over `checkedRange.start`, to  see
    // if there's a discontinuity at the beginning in the buffer
    var nextBufferedInRangeIdx = getIndexOfFirstChunkInRange(bufferedSegments, checkedRange);
    if (nextBufferedInRangeIdx === null) {
        // There's no segment currently buffered for the current range.
        if (nextSegmentStart === null) { // No segment to load in that range
            // Check if we are in a discontinuity at the end of the current Period
            if (hasFinishedLoading &&
                period.end !== undefined &&
                checkedRange.end >= period.end) {
                return { start: undefined, end: null }; // discontinuity to Period's end
            }
            // Check that there is a discontinuity announced in the Manifest there
            var discontinuityEnd = representation.index
                .checkDiscontinuity(checkedRange.start);
            if (discontinuityEnd !== null) {
                return { start: undefined,
                    end: discontinuityEnd };
            }
        }
        return null;
    }
    var nextBufferedSegment = bufferedSegments[nextBufferedInRangeIdx];
    // Check if there is a hole that won't be filled before `nextSegmentStart`
    if (
    // Next buffered segment starts after the start of the current range
    nextBufferedSegment.bufferedStart !== undefined &&
        nextBufferedSegment.bufferedStart > checkedRange.start &&
        // and no segment will fill in that hole
        (nextSegmentStart === null ||
            nextBufferedSegment.infos.segment.end <= nextSegmentStart)) {
        var discontinuityEnd = nextBufferedSegment.bufferedStart;
        if (!hasFinishedLoading &&
            representation.index.awaitSegmentBetween(checkedRange.start, discontinuityEnd) !== false) {
            return null;
        }
        log.debug("RS: current discontinuity encountered", adaptation.type, nextBufferedSegment.bufferedStart);
        return { start: undefined,
            end: discontinuityEnd };
    }
    // Check if there's a discontinuity BETWEEN segments of the current range
    var nextHoleIdx = getIndexOfFirstDiscontinuityBetweenChunks(bufferedSegments, checkedRange, nextBufferedInRangeIdx + 1);
    // If there was a hole between two consecutives segments, and if this hole
    // comes before the next segment to load, there is a discontinuity (that hole!)
    if (nextHoleIdx !== null) {
        var segmentInfoBeforeHole = bufferedSegments[nextHoleIdx - 1];
        var segmentInfoAfterHole = bufferedSegments[nextHoleIdx];
        if (nextSegmentStart === null ||
            segmentInfoAfterHole.infos.segment.end <= nextSegmentStart) {
            if (!hasFinishedLoading && representation.index
                .awaitSegmentBetween(segmentInfoBeforeHole.infos.segment.end, segmentInfoAfterHole.infos.segment.time) !== false) {
                return null;
            }
            var start = segmentInfoBeforeHole.bufferedEnd;
            var end = segmentInfoAfterHole.bufferedStart;
            log.debug("RS: future discontinuity encountered", adaptation.type, start, end);
            return { start: start, end: end };
        }
    }
    if (nextSegmentStart === null) {
        // If no hole between segments and no segment to load, check for a
        // discontinuity at the end of the Period
        if (hasFinishedLoading && period.end !== undefined) { // Period is finished
            if (checkedRange.end < period.end) { // We've not reached the Period's end yet
                return null;
            }
            // Check if the last buffered segment ends before this Period's end
            // In which case there is a discontinuity between those
            var lastBufferedInPeriodIdx = getIndexOfLastChunkInPeriod(bufferedSegments, period.end);
            if (lastBufferedInPeriodIdx !== null) {
                var lastSegment = bufferedSegments[lastBufferedInPeriodIdx];
                if (lastSegment.bufferedEnd !== undefined &&
                    lastSegment.bufferedEnd < period.end) {
                    log.debug("RS: discontinuity encountered at the end of the current period", adaptation.type, lastSegment.bufferedEnd, period.end);
                    return { start: lastSegment.bufferedEnd,
                        end: null };
                }
            }
        }
        // At last, check if we don't have a discontinuity at the end of the current
        // range, announced in the Manifest, that is too big to be detected through
        // the previous checks.
        if (period.end !== undefined && checkedRange.end >= period.end) {
            return null; // The previous checks should have taken care of those
        }
        for (var bufIdx = bufferedSegments.length - 1; bufIdx >= 0; bufIdx--) {
            var bufSeg = bufferedSegments[bufIdx];
            if (bufSeg.bufferedStart === undefined) {
                break;
            }
            if (bufSeg.bufferedStart < checkedRange.end) {
                if (bufSeg.bufferedEnd !== undefined && bufSeg.bufferedEnd < checkedRange.end) {
                    var discontinuityEnd = representation.index
                        .checkDiscontinuity(checkedRange.end);
                    if (discontinuityEnd !== null) {
                        return { start: bufSeg.bufferedEnd,
                            end: discontinuityEnd };
                    }
                }
                return null;
            }
        }
    }
    return null;
}
/**
 * Returns the index of the first element in `bufferedChunks` that is part of
 * `range` (starts before it ends and ends after it starts).
 *
 * Returns `null` if no element is found in that range or if we cannot know the
 * index of the first element in it.
 * @param {Array.<Object>} bufferedChunks
 * @param {Object} range
 * @returns {number|null}
 */
function getIndexOfFirstChunkInRange(bufferedChunks, range) {
    for (var bufIdx = 0; bufIdx < bufferedChunks.length; bufIdx++) {
        var bufSeg = bufferedChunks[bufIdx];
        if (bufSeg.bufferedStart === undefined ||
            bufSeg.bufferedEnd === undefined ||
            bufSeg.bufferedStart >= range.end) {
            return null;
        }
        if (bufSeg.bufferedEnd > range.start) {
            return bufIdx;
        }
    }
    return null;
}
/**
 * Returns the index of the first element in `bufferedChunks` which is not
 * immediately consecutive to the one before it.
 *
 * `startFromIndex` is the index of the first segment that will be checked with
 * the element coming before it. As such, it has to be superior to 0.
 *
 * If the element at `startFromIndex` comes immediately after the one before it,
 * the element at `startFromIndex + 1` will be checked instead and so on until a
 * segment completely out of `checkedRange` (which starts after it) is detected.
 *
 * If no hole between elements is found, `null` is returned.
 * @param {Array.<Object>} bufferedChunks
 * @param {Object} range
 * @param {number} startFromIndex
 * @returns {number|null}
 */
function getIndexOfFirstDiscontinuityBetweenChunks(bufferedChunks, range, startFromIndex) {
    if (startFromIndex <= 0) {
        log.error("RS: Asked to check a discontinuity before the first chunk.");
        return null;
    }
    for (var bufIdx = startFromIndex; bufIdx < bufferedChunks.length; bufIdx++) {
        var currSegment = bufferedChunks[bufIdx];
        var prevSegment = bufferedChunks[bufIdx - 1];
        // Exit as soon we miss information or when we go further than `checkedRange`
        if (currSegment.bufferedStart === undefined ||
            prevSegment.bufferedEnd === undefined ||
            currSegment.bufferedStart >= range.end) {
            return null;
        }
        // If there is a hole between two consecutive buffered segment
        if (currSegment.bufferedStart - prevSegment.bufferedEnd > 0) {
            return bufIdx;
        }
    }
    return null;
}
/**
 * Returns the index of the last element in `bufferedChunks` that is part of
 * `range` (starts before it ends and ends after it starts).
 *
 * Returns `null` if no element is found in that range or if we cannot know the
 * index of the last element in it.
 * @param {Array.<Object>} bufferedChunks
 * @param {number} periodEnd
 * @returns {number|null}
 */
function getIndexOfLastChunkInPeriod(bufferedChunks, periodEnd) {
    for (var bufIdx = bufferedChunks.length - 1; bufIdx >= 0; bufIdx--) {
        var bufSeg = bufferedChunks[bufIdx];
        if (bufSeg.bufferedStart === undefined) {
            return null;
        }
        if (bufSeg.bufferedStart < periodEnd) {
            return bufIdx;
        }
    }
    return null;
}
