/*
 * Copyright 2021 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.source.ads;

import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getAdCountInGroup;
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getMediaPeriodPositionUsForAd;
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getMediaPeriodPositionUsForContent;
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getStreamPositionUsForAd;
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getStreamPositionUsForContent;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.stream;

import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.C;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Unit tests for {@link ServerSideAdInsertionUtil}. */
@RunWith(AndroidJUnit4.class)
public final class ServerSideAdInsertionUtilTest {

  private static final Object ADS_ID = new Object();

  @Test
  public void addAdGroupToAdPlaybackState_insertsCorrectAdGroupData() {
    AdPlaybackState state =
        new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 1, C.TIME_END_OF_SOURCE)
            .withRemovedAdGroupCount(2);

    // stream: 0-- content --4300-- ad1 --4500-- content
    // content timeline: 0-4300 - [ad1] - 4700-end
    state =
        addAdGroupToAdPlaybackState(
            state,
            /* fromPositionUs= */ 4300,
            /* contentResumeOffsetUs= */ 400,
            /* adDurationsUs...= */ 200);

    assertThat(state)
        .isEqualTo(
            new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 0, 4300, C.TIME_END_OF_SOURCE)
                .withRemovedAdGroupCount(2)
                .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
                .withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
                .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 400)
                .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 200));

    // stream: 0-- content --2100-- ad1 --2400-- content --4300-- ad2 --4500-- content
    // content timeline: 0-2100 - [ad1] - 2100-4000 - [ad2] - 4400-end
    state =
        addAdGroupToAdPlaybackState(
            state,
            /* fromPositionUs= */ 2100,
            /* contentResumeOffsetUs= */ 0,
            /* adDurationsUs...= */ 300);

    assertThat(state)
        .isEqualTo(
            new AdPlaybackState(
                    ADS_ID, /* adGroupTimesUs...= */ 0, 0, 2100, 4000, C.TIME_END_OF_SOURCE)
                .withRemovedAdGroupCount(2)
                .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
                .withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
                .withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
                .withIsServerSideInserted(/* adGroupIndex= */ 3, /* isServerSideInserted= */ true)
                .withContentResumeOffsetUs(/* adGroupIndex= */ 3, /* contentResumeOffsetUs= */ 400)
                .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 300)
                .withAdDurationsUs(/* adGroupIndex= */ 3, /* adDurationsUs...= */ 200));

    // stream: 0-- ad1 --100-- content --2100-- ad2 --2400-- content --4300-- ad3 --4500-- content
    // content timeline: 0 - [ad1] - 50-2050 -[ad2] - 2050-3950 - [ad3] - 4350-end
    state =
        addAdGroupToAdPlaybackState(
            state,
            /* fromPositionUs= */ 0,
            /* contentResumeOffsetUs= */ 50,
            /* adDurationsUs...= */ 100);

    assertThat(state)
        .isEqualTo(
            new AdPlaybackState(
                    ADS_ID, /* adGroupTimesUs...= */ 0, 0, 0, 2050, 3950, C.TIME_END_OF_SOURCE)
                .withRemovedAdGroupCount(2)
                .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
                .withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
                .withAdCount(/* adGroupIndex= */ 4, /* adCount= */ 1)
                .withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
                .withIsServerSideInserted(/* adGroupIndex= */ 3, /* isServerSideInserted= */ true)
                .withIsServerSideInserted(/* adGroupIndex= */ 4, /* isServerSideInserted= */ true)
                .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 50)
                .withContentResumeOffsetUs(/* adGroupIndex= */ 4, /* contentResumeOffsetUs= */ 400)
                .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 100)
                .withAdDurationsUs(/* adGroupIndex= */ 3, /* adDurationsUs...= */ 300)
                .withAdDurationsUs(/* adGroupIndex= */ 4, /* adDurationsUs...= */ 200));

    // stream: 0-- ad1 --100-- c --2100-- ad2 --2400-- c --4300-- ad3 --4500-- c --5000-- ad4 --6000
    // content timeline: 0 - [ad1] - 50-2050 -[ad2] - 2050-3950 - [ad3] - 4350-4850 - [ad4] - 4850
    state =
        addAdGroupToAdPlaybackState(
            state,
            /* fromPositionUs= */ 5000,
            /* contentResumeOffsetUs= */ 0,
            /* adDurationsUs...= */ 1000);

    assertThat(state)
        .isEqualTo(
            new AdPlaybackState(
                    ADS_ID, /* adGroupTimesUs...= */
                    0,
                    0,
                    0,
                    2050,
                    3950,
                    4850,
                    C.TIME_END_OF_SOURCE)
                .withRemovedAdGroupCount(2)
                .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
                .withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
                .withAdCount(/* adGroupIndex= */ 4, /* adCount= */ 1)
                .withAdCount(/* adGroupIndex= */ 5, /* adCount= */ 1)
                .withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
                .withIsServerSideInserted(/* adGroupIndex= */ 3, /* isServerSideInserted= */ true)
                .withIsServerSideInserted(/* adGroupIndex= */ 4, /* isServerSideInserted= */ true)
                .withIsServerSideInserted(/* adGroupIndex= */ 5, /* isServerSideInserted= */ true)
                .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 50)
                .withContentResumeOffsetUs(/* adGroupIndex= */ 4, /* contentResumeOffsetUs= */ 400)
                .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 100)
                .withAdDurationsUs(/* adGroupIndex= */ 3, /* adDurationsUs...= */ 300)
                .withAdDurationsUs(/* adGroupIndex= */ 4, /* adDurationsUs...= */ 200)
                .withAdDurationsUs(/* adGroupIndex= */ 5, /* adDurationsUs...= */ 1000));
  }

  @Test
  public void addAdGroupToAdPlaybackState_emptyLeadingAds_markedAsSkipped() {
    AdPlaybackState state = new AdPlaybackState(ADS_ID);

    state =
        addAdGroupToAdPlaybackState(
            state,
            /* fromPositionUs= */ 0,
            /* contentResumeOffsetUs= */ 50_000,
            /* adDurationsUs...= */ 0,
            0,
            10_000,
            40_000,
            0);

    AdPlaybackState.AdGroup adGroup = state.getAdGroup(/* adGroupIndex= */ 0);
    assertThat(adGroup.durationsUs[0]).isEqualTo(0);
    assertThat(adGroup.states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
    assertThat(adGroup.durationsUs[1]).isEqualTo(0);
    assertThat(adGroup.states[1]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
    assertThat(adGroup.durationsUs[2]).isEqualTo(10_000);
    assertThat(adGroup.states[2]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
    assertThat(adGroup.durationsUs[4]).isEqualTo(0);
    assertThat(adGroup.states[4]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
    assertThat(stream(adGroup.durationsUs).sum()).isEqualTo(50_000);
  }

  @Test
  public void getStreamPositionUsForAd_returnsCorrectPositions() {
    // stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
    // content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
    AdPlaybackState state =
        new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 0, 0, 2000, 4000)
            .withRemovedAdGroupCount(2)
            .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 2)
            .withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
            .withAdCount(/* adGroupIndex= */ 4, /* adCount= */ 3)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 100)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 3, /* contentResumeOffsetUs= */ 0)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 4, /* contentResumeOffsetUs= */ 400)
            .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 150, 50)
            .withAdDurationsUs(/* adGroupIndex= */ 3, /* adDurationsUs...= */ 200)
            .withAdDurationsUs(/* adGroupIndex= */ 4, /* adDurationsUs...= */ 50, 50, 100);

    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 0, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(0);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 100, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(100);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 200, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(200);

    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ -50, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(100);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 0, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(150);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 100, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(250);

    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ -50, /* adGroupIndex= */ 3, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(2050);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 0, /* adGroupIndex= */ 3, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(2100);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 200, /* adGroupIndex= */ 3, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(2300);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 300, /* adGroupIndex= */ 3, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(2400);

    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ -50, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(4250);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 0, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(4300);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 100, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(4400);

    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ -50, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(4300);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 0, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(4350);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 100, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(4450);

    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ -50, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 2, state))
        .isEqualTo(4350);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 50, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 2, state))
        .isEqualTo(4450);
    assertThat(
            getStreamPositionUsForAd(
                /* positionUs= */ 150, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 2, state))
        .isEqualTo(4550);
  }

  @Test
  public void getMediaPeriodPositionUsForAd_returnsCorrectPositions() {
    // stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
    // content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
    AdPlaybackState state =
        new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 0, 0, 2000, 4000)
            .withRemovedAdGroupCount(2)
            .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 2)
            .withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
            .withAdCount(/* adGroupIndex= */ 4, /* adCount= */ 3)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 100)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 3, /* contentResumeOffsetUs= */ 0)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 4, /* contentResumeOffsetUs= */ 400)
            .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 150, 50)
            .withAdDurationsUs(/* adGroupIndex= */ 3, /* adDurationsUs...= */ 200)
            .withAdDurationsUs(/* adGroupIndex= */ 4, /* adDurationsUs...= */ 50, 50, 100);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 0, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(0);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 100, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(100);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 100, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(-50);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 200, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(200);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 200, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(50);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 300, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(150);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 2000, /* adGroupIndex= */ 3, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(-100);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 2100, /* adGroupIndex= */ 3, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(0);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 2300, /* adGroupIndex= */ 3, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(200);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4300, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(0);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4300, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(-50);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4300, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 2, state))
        .isEqualTo(-100);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4400, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 0, state))
        .isEqualTo(100);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4400, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(50);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4400, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 2, state))
        .isEqualTo(0);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4500, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(150);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4500, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 2, state))
        .isEqualTo(100);

    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4700, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 1, state))
        .isEqualTo(350);
    assertThat(
            getMediaPeriodPositionUsForAd(
                /* positionUs= */ 4700, /* adGroupIndex= */ 4, /* adIndexInAdGroup= */ 2, state))
        .isEqualTo(300);
  }

  @Test
  public void getStreamPositionUsForContent_returnsCorrectPositions() {
    // stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
    // content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
    AdPlaybackState state =
        new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 0, 0, 2000, 4000)
            .withRemovedAdGroupCount(2)
            .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 2)
            .withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
            .withAdCount(/* adGroupIndex= */ 4, /* adCount= */ 3)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 100)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 3, /* contentResumeOffsetUs= */ 0)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 4, /* contentResumeOffsetUs= */ 400)
            .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 150, 50)
            .withAdDurationsUs(/* adGroupIndex= */ 3, /* adDurationsUs...= */ 200)
            .withAdDurationsUs(/* adGroupIndex= */ 4, /* adDurationsUs...= */ 50, 50, 100);

    assertThat(getStreamPositionUsForContent(/* positionUs= */ 0, /* nextAdGroupIndex= */ 2, state))
        .isEqualTo(0);
    assertThat(getStreamPositionUsForContent(/* positionUs= */ 0, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(100);
    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(100);

    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 50, /* nextAdGroupIndex= */ 2, state))
        .isEqualTo(50);
    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 50, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(150);
    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 50, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(150);

    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 100, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(200);
    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 100, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(200);

    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 1999, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(2099);
    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 1999, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(2099);

    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 2000, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(2100);
    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 2000, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(2300);
    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 2000, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(2300);

    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 3999, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(4299);
    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 3999, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4299);

    assertThat(
            getStreamPositionUsForContent(/* positionUs= */ 4000, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(4300);
    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 4000, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4300);

    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 4200, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4300);

    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 4300, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4400);

    assertThat(
            getStreamPositionUsForContent(
                /* positionUs= */ 4400, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4500);
  }

  @Test
  public void getMediaPeriodPositionUsForContent_returnsCorrectPositions() {
    // stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
    // content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
    AdPlaybackState state =
        new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 0, 0, 2000, 4000)
            .withRemovedAdGroupCount(2)
            .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 2)
            .withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
            .withAdCount(/* adGroupIndex= */ 4, /* adCount= */ 3)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 100)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 3, /* contentResumeOffsetUs= */ 0)
            .withContentResumeOffsetUs(/* adGroupIndex= */ 4, /* contentResumeOffsetUs= */ 400)
            .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 150, 50)
            .withAdDurationsUs(/* adGroupIndex= */ 3, /* adDurationsUs...= */ 200)
            .withAdDurationsUs(/* adGroupIndex= */ 4, /* adDurationsUs...= */ 50, 50, 100);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 0, /* nextAdGroupIndex= */ 2, state))
        .isEqualTo(0);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 0, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(0);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(0);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 100, /* nextAdGroupIndex= */ 2, state))
        .isEqualTo(100);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 100, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(0);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 100, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(0);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 200, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(100);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 200, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(100);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2099, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(1999);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2099, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(1999);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2100, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(2000);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2100, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(2000);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2100, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(2000);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2300, /* nextAdGroupIndex= */ 3, state))
        .isEqualTo(2200);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2300, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(2000);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 2300, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(2000);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 4299, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(3999);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 4299, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(3999);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 4300, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(4000);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 4300, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4200);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 4500, /* nextAdGroupIndex= */ 4, state))
        .isEqualTo(4200);
    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 4500, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4400);

    assertThat(
            getMediaPeriodPositionUsForContent(
                /* positionUs= */ 4700, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
        .isEqualTo(4600);
  }

  @Test
  public void getAdCountInGroup_withUnsetCount_returnsZero() {
    AdPlaybackState state = new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000);

    assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 0)).isEqualTo(0);
    assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 1)).isEqualTo(0);
  }

  @Test
  public void getAdCountInGroup_withSetCount_returnsCount() {
    AdPlaybackState state =
        new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000)
            .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 4)
            .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 6);

    assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 0)).isEqualTo(4);
    assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 1)).isEqualTo(6);
  }
}
