/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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.
 */
package androidx.media3.exoplayer.hls;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;

import android.net.Uri;
import androidx.media3.common.C;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist;
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser;
import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Unit test for {@link HlsChunkSource.HlsMediaPlaylistSegmentIterator}. */
@RunWith(AndroidJUnit4.class)
public class HlsMediaPlaylistSegmentIteratorTest {

  public static final String LOW_LATENCY_SEGMENTS_AND_PARTS =
      "media/m3u8/live_low_latency_segments_and_parts";
  public static final String SEGMENTS_ONLY = "media/m3u8/live_low_latency_segments_only";

  @Test
  public void create_withMediaSequenceBehindLiveWindow_isEmpty() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);

    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist, mediaPlaylist.mediaSequence - 1, /* partIndex= */ C.INDEX_UNSET));

    assertThat(hlsMediaPlaylistSegmentIterator.next()).isFalse();
  }

  @Test
  public void create_withMediaSequenceBeforeTrailingPartSegment_isEmpty() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);

    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist,
                mediaPlaylist.mediaSequence + mediaPlaylist.segments.size() + 1,
                /* partIndex= */ C.INDEX_UNSET));

    assertThat(hlsMediaPlaylistSegmentIterator.next()).isFalse();
  }

  @Test
  public void create_withPartIndexBeforeLastTrailingPartSegment_isEmpty() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);

    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist,
                mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(),
                /* partIndex= */ 3));

    assertThat(hlsMediaPlaylistSegmentIterator.next()).isFalse();
  }

  @Test
  public void next_conventionalLiveStartIteratorAtSecondSegment_correctElements() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(SEGMENTS_ONLY);
    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist, /* mediaSequence= */ 11, /* partIndex= */ C.INDEX_UNSET));

    List<DataSpec> datasSpecs = new ArrayList<>();
    while (hlsMediaPlaylistSegmentIterator.next()) {
      datasSpecs.add(hlsMediaPlaylistSegmentIterator.getDataSpec());
    }

    assertThat(datasSpecs).hasSize(5);
    assertThat(datasSpecs.get(0).uri.toString()).isEqualTo("fileSequence11.ts");
    assertThat(Iterables.getLast(datasSpecs).uri.toString()).isEqualTo("fileSequence15.ts");
  }

  @Test
  public void next_startIteratorAtFirstSegment_correctElements() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);
    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist, /* mediaSequence= */ 10, /* partIndex= */ C.INDEX_UNSET));

    List<DataSpec> datasSpecs = new ArrayList<>();
    while (hlsMediaPlaylistSegmentIterator.next()) {
      datasSpecs.add(hlsMediaPlaylistSegmentIterator.getDataSpec());
    }

    assertThat(datasSpecs).hasSize(9);
    // The iterator starts with 6 segments.
    assertThat(datasSpecs.get(0).uri.toString()).isEqualTo("fileSequence10.ts");
    // Followed by trailing parts.
    assertThat(datasSpecs.get(6).uri.toString()).isEqualTo("fileSequence16.0.ts");
    // The preload part is the last.
    assertThat(Iterables.getLast(datasSpecs).uri.toString()).isEqualTo("fileSequence16.2.ts");
  }

  @Test
  public void next_startIteratorAtFirstPartInaSegment_usesFullSegment() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);
    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist, /* mediaSequence= */ 14, /* partIndex= */ 0));

    List<DataSpec> datasSpecs = new ArrayList<>();
    while (hlsMediaPlaylistSegmentIterator.next()) {
      datasSpecs.add(hlsMediaPlaylistSegmentIterator.getDataSpec());
    }

    assertThat(datasSpecs).hasSize(5);
    // The iterator starts with 6 segments.
    assertThat(datasSpecs.get(0).uri.toString()).isEqualTo("fileSequence14.ts");
    assertThat(datasSpecs.get(1).uri.toString()).isEqualTo("fileSequence15.ts");
    // Followed by trailing parts.
    assertThat(datasSpecs.get(2).uri.toString()).isEqualTo("fileSequence16.0.ts");
    assertThat(datasSpecs.get(3).uri.toString()).isEqualTo("fileSequence16.1.ts");
    // The preload part is the last.
    assertThat(Iterables.getLast(datasSpecs).uri.toString()).isEqualTo("fileSequence16.2.ts");
  }

  @Test
  public void next_startIteratorAtTrailingPart_correctElements() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);
    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist, /* mediaSequence= */ 16, /* partIndex= */ 1));

    List<DataSpec> datasSpecs = new ArrayList<>();
    while (hlsMediaPlaylistSegmentIterator.next()) {
      datasSpecs.add(hlsMediaPlaylistSegmentIterator.getDataSpec());
    }

    assertThat(datasSpecs).hasSize(2);
    // The iterator starts with 2 parts.
    assertThat(datasSpecs.get(0).uri.toString()).isEqualTo("fileSequence16.1.ts");
    // The preload part is the last.
    assertThat(Iterables.getLast(datasSpecs).uri.toString()).isEqualTo("fileSequence16.2.ts");
  }

  @Test
  public void next_startIteratorAtPartWithinSegment_correctElements() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);
    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator =
        new HlsChunkSource.HlsMediaPlaylistSegmentIterator(
            mediaPlaylist.baseUri,
            /* startOfPlaylistInPeriodUs= */ 0,
            HlsChunkSource.getSegmentBaseList(
                mediaPlaylist, /* mediaSequence= */ 14, /* partIndex= */ 1));

    List<DataSpec> datasSpecs = new ArrayList<>();
    while (hlsMediaPlaylistSegmentIterator.next()) {
      datasSpecs.add(hlsMediaPlaylistSegmentIterator.getDataSpec());
    }

    assertThat(datasSpecs).hasSize(7);
    // The iterator starts with 11 parts.
    assertThat(datasSpecs.get(0).uri.toString()).isEqualTo("fileSequence14.1.ts");
    assertThat(datasSpecs.get(1).uri.toString()).isEqualTo("fileSequence14.2.ts");
    assertThat(datasSpecs.get(2).uri.toString()).isEqualTo("fileSequence14.3.ts");
    // Use a segment in between if possible.
    assertThat(datasSpecs.get(3).uri.toString()).isEqualTo("fileSequence15.ts");
    // Then parts again.
    assertThat(datasSpecs.get(4).uri.toString()).isEqualTo("fileSequence16.0.ts");
    assertThat(datasSpecs.get(5).uri.toString()).isEqualTo("fileSequence16.1.ts");
    assertThat(datasSpecs.get(6).uri.toString()).isEqualTo("fileSequence16.2.ts");
  }

  private static HlsMediaPlaylist getHlsMediaPlaylist(String file) {
    try {
      return (HlsMediaPlaylist)
          new HlsPlaylistParser()
              .parse(
                  Uri.EMPTY,
                  TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), file));
    } catch (IOException e) {
      fail(e.getMessage());
    }
    return null;
  }
}
