/*
 * -\-\-
 * Mobius
 * --
 * Copyright (c) 2017-2020 Spotify AB
 * --
 * 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 com.spotify.mobius.android;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import androidx.lifecycle.Lifecycle;
import com.spotify.mobius.runners.WorkRunners;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import org.junit.Before;
import org.junit.Test;

public class MutableLiveQueueTest {

  public static final int QUEUE_CAPACITY = 4;
  private MutableLiveQueue<String> mutableLiveQueue;

  private FakeLifecycleOwner fakeLifecycleOwner1;
  private FakeLifecycleOwner fakeLifecycleOwner2;
  private RecordingObserver<String> liveObserver;
  private RecordingObserver<Iterable<String>> pausedObserver;

  @Before
  public void setup() {
    mutableLiveQueue = new MutableLiveQueue<>(WorkRunners.immediate(), QUEUE_CAPACITY);
    fakeLifecycleOwner1 = new FakeLifecycleOwner();
    fakeLifecycleOwner2 = new FakeLifecycleOwner();
    liveObserver = new RecordingObserver<>();
    pausedObserver = new RecordingObserver<>();
  }

  @Test
  public void shouldIgnoreDestroyedLifecycleOwner() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver);

    assertThat(mutableLiveQueue.hasObserver(), equalTo(false));
  }

  @Test
  public void shouldSendDataToResumedObserver() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver);
    mutableLiveQueue.post("one");
    mutableLiveQueue.post("two");

    assertThat(mutableLiveQueue.hasActiveObserver(), equalTo(true));
    liveObserver.assertValues("one", "two");
  }

  @Test
  public void shouldNotQueueEventsWithNoObserver() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

    mutableLiveQueue.post("one");
    mutableLiveQueue.post("two");
    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver, pausedObserver);

    assertThat(liveObserver.valueCount(), equalTo(0));
    assertThat(pausedObserver.valueCount(), equalTo(0));
  }

  @Test
  public void shouldSendQueuedEventsWithValidPausedObserver() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver, pausedObserver);
    mutableLiveQueue.post("one");
    mutableLiveQueue.post("two");
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

    assertThat(liveObserver.valueCount(), equalTo(0));
    pausedObserver.assertValues(queueOf("one", "two"));
  }

  @Test
  public void shouldSendLiveAndQueuedEventsWhenRunningAndThenPausedObserver() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver, pausedObserver);
    mutableLiveQueue.post("one");
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
    mutableLiveQueue.post("two");
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

    liveObserver.assertValues("one");
    pausedObserver.assertValues(queueOf("two"));
  }

  @Test
  public void shouldSendQueuedEffectsIfObserverSwappedToResumedOneClearing() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, s -> {}, s -> {});
    mutableLiveQueue.post("one");
    mutableLiveQueue.post("two");
    fakeLifecycleOwner2.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
    mutableLiveQueue.setObserver(fakeLifecycleOwner2, liveObserver, pausedObserver);

    assertThat(liveObserver.valueCount(), equalTo(0));
    pausedObserver.assertValues(queueOf("one", "two"));
  }

  @Test
  public void shouldSendQueuedEffectsIfObserverSwappedWithoutClearing() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, s -> {}, s -> {});
    mutableLiveQueue.post("one");
    mutableLiveQueue.post("two");
    mutableLiveQueue.setObserver(fakeLifecycleOwner2, liveObserver, pausedObserver);
    fakeLifecycleOwner2.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

    assertThat(liveObserver.valueCount(), equalTo(0));
    pausedObserver.assertValues(queueOf("one", "two"));
  }

  @Test
  public void shouldClearQueueIfObserverCleared() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, s -> {}, s -> {});
    mutableLiveQueue.post("one");
    mutableLiveQueue.post("two");
    mutableLiveQueue.clearObserver();
    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver, pausedObserver);
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

    assertThat(liveObserver.valueCount(), equalTo(0));
    assertThat(pausedObserver.valueCount(), equalTo(0));
  }

  @Test
  public void shouldClearQueueIfLifecycleDestroyed() {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver, pausedObserver);
    mutableLiveQueue.post("one");
    mutableLiveQueue.post("two");
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);

    assertThat(liveObserver.valueCount(), equalTo(0));
    assertThat(pausedObserver.valueCount(), equalTo(0));
  }

  @Test
  public void shouldThrowIllegalStateExceptionIfQueueFull() throws Exception {
    fakeLifecycleOwner1.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);

    mutableLiveQueue.setObserver(fakeLifecycleOwner1, liveObserver);

    mutableLiveQueue.post("1");
    mutableLiveQueue.post("2");
    mutableLiveQueue.post("3");
    mutableLiveQueue.post("4");

    assertThatThrownBy(() -> mutableLiveQueue.post("this one breaks"))
        .isInstanceOf(IllegalStateException.class)
        .hasMessageContaining("this one breaks")
        .hasMessageContaining(String.valueOf(QUEUE_CAPACITY));
  }

  private Queue<String> queueOf(String... args) {
    return new LinkedList<>(Arrays.asList(args));
  }
}