/**
 * 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 type { ISegment } from "../../../../../manifest";
import type { IEMSG } from "../../../../containers/isobmff";
import type { IIndexSegment } from "../../../utils/index_helpers";
import { calculateRepeat, toIndexTime } from "../../../utils/index_helpers";
import type ManifestBoundsCalculator from "../manifest_bounds_calculator";
import { createDashUrlDetokenizer } from "./tokens";

/**
 * For the given start time and duration of a timeline element, calculate how
 * much this element should be repeated to contain the time given.
 * 0 being the same element, 1 being the next one etc.
 * @param {Number} segmentStartTime
 * @param {Number} segmentDuration
 * @param {Number} wantedTime
 * @returns {Number}
 */
function getWantedRepeatIndex(
  segmentStartTime: number,
  segmentDuration: number,
  wantedTime: number,
): number {
  const diff = wantedTime - segmentStartTime;
  return diff > 0 ? Math.floor(diff / segmentDuration) : 0;
}

/**
 * Get a list of Segments for the time range wanted.
 * @param {Object} index - index object, constructed by parsing the manifest.
 * @param {number} from - starting timestamp wanted, in seconds
 * @param {number} durationWanted - duration wanted, in seconds
 * @param {Object} manifestBoundsCalculator
 * @param {number|undefined} scaledPeriodEnd
 * @param {function} isEMSGWhitelisted
 * @returns {Array.<Object>}
 */
export default function getSegmentsFromTimeline(
  index: {
    availabilityTimeComplete?: boolean | undefined;
    availabilityTimeOffset?: number | undefined;
    segmentUrlTemplate: string | null;
    startNumber?: number | undefined;
    endNumber?: number | undefined;
    timeline: IIndexSegment[];
    timescale: number;
    indexTimeOffset: number;
  },
  from: number,
  durationWanted: number,
  manifestBoundsCalculator: ManifestBoundsCalculator,
  scaledPeriodEnd: number | undefined,
  isEMSGWhitelisted: (inbandEvent: IEMSG) => boolean,
): ISegment[] {
  const maximumTime = manifestBoundsCalculator.getEstimatedMaximumPosition(
    index.availabilityTimeOffset ?? 0,
  );
  const wantedMaximum = Math.min(from + durationWanted, maximumTime ?? Infinity);
  const scaledUp = toIndexTime(from, index);
  const scaledTo = toIndexTime(wantedMaximum, index);
  const { timeline, timescale, segmentUrlTemplate, startNumber, endNumber } = index;

  let currentNumber = startNumber ?? 1;
  const segments: ISegment[] = [];

  const timelineLength = timeline.length;

  for (let i = 0; i < timelineLength; i++) {
    const timelineItem = timeline[i];
    const { duration, start, range } = timelineItem;

    let maxRepeatTime;
    if (maximumTime === undefined) {
      maxRepeatTime = scaledPeriodEnd;
    } else {
      maxRepeatTime = Math.min(maximumTime * timescale, scaledPeriodEnd ?? Infinity);
    }
    const repeat = calculateRepeat(timelineItem, timeline[i + 1], maxRepeatTime);
    const complete =
      index.availabilityTimeComplete !== false ||
      (i !== timelineLength - 1 && repeat !== 0);
    let segmentNumberInCurrentRange = getWantedRepeatIndex(start, duration, scaledUp);
    let segmentTime = start + segmentNumberInCurrentRange * duration;
    while (segmentTime < scaledTo && segmentNumberInCurrentRange <= repeat) {
      const segmentNumber = currentNumber + segmentNumberInCurrentRange;
      if (endNumber !== undefined && segmentNumber > endNumber) {
        break;
      }

      const detokenizedURL =
        segmentUrlTemplate === null
          ? null
          : createDashUrlDetokenizer(segmentTime, segmentNumber)(segmentUrlTemplate);

      let time = segmentTime - index.indexTimeOffset;
      let realDuration = duration;
      if (time < 0) {
        realDuration = duration + time; // Remove from duration the part before `0`
        time = 0;
      }

      const segment = {
        id: String(segmentTime),
        time: time / timescale,
        end: (time + realDuration) / timescale,
        duration: realDuration / timescale,
        isInit: false,
        range,
        timescale: 1 as const,
        url: detokenizedURL,
        number: segmentNumber,
        timestampOffset: -(index.indexTimeOffset / timescale),
        complete,
        privateInfos: { isEMSGWhitelisted },
      };
      segments.push(segment);

      // update segment number and segment time for the next segment
      segmentNumberInCurrentRange++;
      segmentTime = start + segmentNumberInCurrentRange * duration;
    }

    if (segmentTime >= scaledTo) {
      // we reached ``scaledTo``, we're done
      return segments;
    }

    currentNumber += repeat + 1;
    if (endNumber !== undefined && currentNumber > endNumber) {
      return segments;
    }
  }

  return segments;
}
