/*
 * 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;

import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import android.content.Context;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import androidx.media3.common.C;
import androidx.media3.test.utils.DummyMainThread;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Unit tests for {@link StreamVolumeManager}. */
@RunWith(AndroidJUnit4.class)
public class StreamVolumeManagerTest {

  private static final long TIMEOUT_MS = 1_000;

  private AudioManager audioManager;
  private TestListener testListener;
  private DummyMainThread testThread;
  private StreamVolumeManager streamVolumeManager;

  @Before
  public void setUp() {
    Context context = ApplicationProvider.getApplicationContext();

    audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    testListener = new TestListener();

    testThread = new DummyMainThread();
    testThread.runOnMainThread(
        () ->
            streamVolumeManager =
                new StreamVolumeManager(context, new Handler(Looper.myLooper()), testListener));
  }

  @After
  public void tearDown() {
    testThread.runOnMainThread(() -> streamVolumeManager.release());
    testThread.release();
  }

  @Test
  @SdkSuppress(minSdkVersion = 28)
  public void getMinVolume_returnsStreamMinVolume() {
    testThread.runOnMainThread(
        () -> {
          int streamMinVolume = audioManager.getStreamMinVolume(C.STREAM_TYPE_DEFAULT);
          assertThat(streamVolumeManager.getMinVolume()).isEqualTo(streamMinVolume);
        });
  }

  @Test
  public void getMaxVolume_returnsStreamMaxVolume() {
    testThread.runOnMainThread(
        () -> {
          int streamMaxVolume = audioManager.getStreamMaxVolume(C.STREAM_TYPE_DEFAULT);
          assertThat(streamVolumeManager.getMaxVolume()).isEqualTo(streamMaxVolume);
        });
  }

  @Test
  public void getVolume_returnsStreamVolume() {
    testThread.runOnMainThread(
        () -> {
          int streamVolume = audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT);
          assertThat(streamVolumeManager.getVolume()).isEqualTo(streamVolume);
        });
  }

  @Test
  public void setVolume_changesStreamVolume() {
    testThread.runOnMainThread(
        () -> {
          int minVolume = streamVolumeManager.getMinVolume();
          int maxVolume = streamVolumeManager.getMaxVolume();
          if (minVolume == maxVolume) {
            return;
          }

          int oldVolume = streamVolumeManager.getVolume();
          int targetVolume = oldVolume == maxVolume ? minVolume : maxVolume;

          streamVolumeManager.setVolume(targetVolume);

          assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
          assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
          assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
        });
  }

  @Test
  public void setVolume_withOutOfRange_isIgnored() {
    testThread.runOnMainThread(
        () -> {
          int maxVolume = streamVolumeManager.getMaxVolume();
          int minVolume = streamVolumeManager.getMinVolume();
          int oldVolume = streamVolumeManager.getVolume();

          streamVolumeManager.setVolume(maxVolume + 1);
          assertThat(streamVolumeManager.getVolume()).isEqualTo(oldVolume);

          streamVolumeManager.setVolume(minVolume - 1);
          assertThat(streamVolumeManager.getVolume()).isEqualTo(oldVolume);
        });
  }

  @Test
  public void increaseVolume_increasesStreamVolumeByOne() {
    testThread.runOnMainThread(
        () -> {
          int minVolume = streamVolumeManager.getMinVolume();
          int maxVolume = streamVolumeManager.getMaxVolume();
          if (minVolume == maxVolume) {
            return;
          }

          streamVolumeManager.setVolume(minVolume);
          int targetVolume = minVolume + 1;

          streamVolumeManager.increaseVolume();

          assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
          assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
          assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
        });
  }

  @Test
  public void increaseVolume_onMaxVolume_isIgnored() {
    testThread.runOnMainThread(
        () -> {
          int maxVolume = streamVolumeManager.getMaxVolume();

          streamVolumeManager.setVolume(maxVolume);
          streamVolumeManager.increaseVolume();

          assertThat(streamVolumeManager.getVolume()).isEqualTo(maxVolume);
        });
  }

  @Test
  public void decreaseVolume_decreasesStreamVolumeByOne() {
    testThread.runOnMainThread(
        () -> {
          int minVolume = streamVolumeManager.getMinVolume();
          int maxVolume = streamVolumeManager.getMaxVolume();
          if (minVolume == maxVolume) {
            return;
          }

          streamVolumeManager.setVolume(maxVolume);
          int targetVolume = maxVolume - 1;

          streamVolumeManager.decreaseVolume();

          assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
          assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
          assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
        });
  }

  @Test
  public void decreaseVolume_onMinVolume_isIgnored() {
    testThread.runOnMainThread(
        () -> {
          int minVolume = streamVolumeManager.getMinVolume();

          streamVolumeManager.setVolume(minVolume);
          streamVolumeManager.decreaseVolume();

          assertThat(streamVolumeManager.getVolume()).isEqualTo(minVolume);
        });
  }

  @Test
  public void setVolumeMuted_changesMuteState() {
    testThread.runOnMainThread(
        () -> {
          int minVolume = streamVolumeManager.getMinVolume();
          int maxVolume = streamVolumeManager.getMaxVolume();
          if (minVolume == maxVolume || minVolume > 0) {
            return;
          }

          streamVolumeManager.setVolume(maxVolume);
          assertThat(streamVolumeManager.isMuted()).isFalse();

          streamVolumeManager.setMuted(true);
          assertThat(streamVolumeManager.isMuted()).isTrue();
          assertThat(testListener.lastStreamVolumeMuted).isTrue();

          streamVolumeManager.setMuted(false);
          assertThat(streamVolumeManager.isMuted()).isFalse();
          assertThat(testListener.lastStreamVolumeMuted).isFalse();
          assertThat(testListener.lastStreamVolume).isEqualTo(maxVolume);
        });
  }

  @Test
  public void setStreamType_notifiesStreamTypeAndVolume() {
    testThread.runOnMainThread(
        () -> {
          int minVolume = streamVolumeManager.getMinVolume();
          int maxVolume = streamVolumeManager.getMaxVolume();
          if (minVolume == maxVolume) {
            return;
          }

          int testStreamType = C.STREAM_TYPE_ALARM;
          int testStreamVolume = audioManager.getStreamVolume(testStreamType);

          int oldVolume = streamVolumeManager.getVolume();
          if (oldVolume == testStreamVolume) {
            int differentVolume = oldVolume == minVolume ? maxVolume : minVolume;
            streamVolumeManager.setVolume(differentVolume);
          }

          streamVolumeManager.setStreamType(testStreamType);

          assertThat(testListener.lastStreamType).isEqualTo(testStreamType);
          assertThat(testListener.lastStreamVolume).isEqualTo(testStreamVolume);
          assertThat(streamVolumeManager.getVolume()).isEqualTo(testStreamVolume);
        });
  }

  @Test
  public void onStreamVolumeChanged_isCalled_whenAudioManagerChangesIt() throws Exception {
    AtomicInteger targetVolumeRef = new AtomicInteger();
    testThread.runOnMainThread(
        () -> {
          int minVolume = streamVolumeManager.getMinVolume();
          int maxVolume = streamVolumeManager.getMaxVolume();
          if (minVolume == maxVolume) {
            return;
          }

          int oldVolume = streamVolumeManager.getVolume();
          int targetVolume = oldVolume == maxVolume ? minVolume : maxVolume;
          targetVolumeRef.set(targetVolume);

          audioManager.setStreamVolume(C.STREAM_TYPE_DEFAULT, targetVolume, /* flags= */ 0);
        });

    testListener.onStreamVolumeChangedLatch.await(TIMEOUT_MS, MILLISECONDS);
    assertThat(testListener.lastStreamVolume).isEqualTo(targetVolumeRef.get());
  }

  private static class TestListener implements StreamVolumeManager.Listener {

    private @C.StreamType int lastStreamType;
    private int lastStreamVolume;
    private boolean lastStreamVolumeMuted;
    public final CountDownLatch onStreamVolumeChangedLatch;

    public TestListener() {
      onStreamVolumeChangedLatch = new CountDownLatch(1);
    }

    @Override
    public void onStreamTypeChanged(@C.StreamType int streamType) {
      lastStreamType = streamType;
    }

    @Override
    public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) {
      lastStreamVolume = streamVolume;
      lastStreamVolumeMuted = streamMuted;
      onStreamVolumeChangedLatch.countDown();
    }
  }
}
