package com.bumptech.glide.request;

import static com.bumptech.glide.tests.Util.isADataSource;
import static com.bumptech.glide.tests.Util.mockResource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import com.bumptech.glide.GlideContext;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.request.target.SizeReadyCallback;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import com.bumptech.glide.request.transition.TransitionFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 18)
@SuppressWarnings("rawtypes")
public class SingleRequestTest {
  private RequestHarness harness = new RequestHarness();

  /**
   * {@link Number} and {@link List} are arbitrarily chosen types to test some type safety as well.
   * Both are in the middle of the hierarchy having multiple descendants and ancestors.
   */
  private static class RequestHarness {
    Engine engine = mock(Engine.class);
    Number model = 123456;
    @SuppressWarnings("unchecked")
    Target<List> target = mock(Target.class);
    Resource<List> resource = mockResource();
    RequestCoordinator requestCoordinator = mock(RequestCoordinator.class);
    Drawable placeholderDrawable = null;
    Drawable errorDrawable = null;
    Drawable fallbackDrawable = null;
    @SuppressWarnings("unchecked")
    RequestListener<List> requestListener = mock(RequestListener.class);
    @SuppressWarnings("unchecked")
    TransitionFactory<List> factory = mock(TransitionFactory.class);
    int overrideWidth = -1;
    int overrideHeight = -1;
    List<?> result = new ArrayList<>();
    GlideContext glideContext = mock(GlideContext.class);
    Key signature = mock(Key.class);
    Priority priority = Priority.HIGH;
    boolean useUnlimitedSourceGeneratorsPool = false;
    Class<List> transcodeClass = List.class;

    Map<Class<?>, Transformation<?>>  transformations = new HashMap<>();

    public RequestHarness() {
      when(requestCoordinator.canSetImage(any(Request.class))).thenReturn(true);
      when(requestCoordinator.canNotifyStatusChanged(any(Request.class))).thenReturn(true);
      when(resource.get()).thenReturn(result);
    }

    public SingleRequest<List> getRequest() {
       RequestOptions requestOptions = new RequestOptions()
        .error(errorDrawable)
        .placeholder(placeholderDrawable)
        .fallback(fallbackDrawable)
        .override(overrideWidth, overrideHeight)
        .priority(priority)
        .signature(signature)
        .useUnlimitedSourceGeneratorsPool(useUnlimitedSourceGeneratorsPool);
      return SingleRequest
          .obtain(glideContext, model, transcodeClass, requestOptions, overrideWidth,
              overrideHeight, priority, target, requestListener, requestCoordinator, engine,
              factory);
    }
  }

  @Before
  public void setUp() {
    harness = new RequestHarness();
  }

  @Test
  public void testIsNotCompleteBeforeReceivingResource() {
    SingleRequest<List> request = harness.getRequest();

    assertFalse(request.isComplete());
  }

  @Test
  public void testCanHandleNullResources() {
    SingleRequest<List> request = harness.getRequest();

    request.onResourceReady(null, DataSource.LOCAL);

    assertTrue(request.isFailed());
    verify(harness.requestListener).onLoadFailed(isAGlideException(), isA(Number.class),
        eq(harness.target), anyBoolean());
  }

  @Test
  public void testCanHandleEmptyResources() {
    SingleRequest<List> request = harness.getRequest();
    when(harness.resource.get()).thenReturn(null);

    request.onResourceReady(harness.resource, DataSource.REMOTE);

    assertTrue(request.isFailed());
    verify(harness.engine).release(eq(harness.resource));
    verify(harness.requestListener).onLoadFailed(isAGlideException(), any(Number.class),
        eq(harness.target), anyBoolean());
  }

  @Test
  public void testCanHandleNonConformingResources() {
    SingleRequest<List> request = harness.getRequest();
    when(((Resource) (harness.resource)).get())
        .thenReturn("Invalid mocked String, this should be a List");

    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    assertTrue(request.isFailed());
    verify(harness.engine).release(eq(harness.resource));
    verify(harness.requestListener).onLoadFailed(isAGlideException(), any(Number.class),
        eq(harness.target), anyBoolean());
  }

  @Test
  public void testIsNotFailedAfterClear() {
    SingleRequest<List> request = harness.getRequest();

    request.onResourceReady(null, DataSource.DATA_DISK_CACHE);
    request.clear();

    assertFalse(request.isFailed());
  }

  @Test
  public void testIsPausedAfterPause() {
    SingleRequest<List> request = harness.getRequest();
    request.pause();

    assertTrue(request.isPaused());
  }

  @Test
  public void testIsNotCancelledAfterPause() {
    SingleRequest<List> request = harness.getRequest();
    request.pause();

    assertFalse(request.isCancelled());
  }

  @Test
  public void testIsNotPausedAfterBeginningWhilePaused() {
    SingleRequest<List> request = harness.getRequest();
    request.pause();
    request.begin();

    assertFalse(request.isPaused());
    assertTrue(request.isRunning());
  }

  @Test
  public void testIsNotFailedAfterBegin() {
    SingleRequest<List> request = harness.getRequest();

    request.onResourceReady(null, DataSource.DATA_DISK_CACHE);
    request.begin();

    assertFalse(request.isFailed());
  }

  @Test
  public void testIsCompleteAfterReceivingResource() {
    SingleRequest<List> request = harness.getRequest();

    request.onResourceReady(harness.resource, DataSource.LOCAL);

    assertTrue(request.isComplete());
  }

  @Test
  public void testIsNotCompleteAfterClear() {
    SingleRequest<List> request = harness.getRequest();
    request.onResourceReady(harness.resource, DataSource.REMOTE);
    request.clear();

    assertFalse(request.isComplete());
  }

  @Test
  public void testIsCancelledAfterClear() {
    SingleRequest<List> request = harness.getRequest();
    request.clear();

    assertTrue(request.isCancelled());
  }

  @Test
  public void testDoesNotNotifyTargetTwiceIfClearedTwiceInARow() {
    SingleRequest<List> request = harness.getRequest();
    request.clear();
    request.clear();

    verify(harness.target, times(1)).onLoadCleared(any(Drawable.class));
  }

  @Test
  public void testResourceIsNotCompleteWhenAskingCoordinatorIfCanSetImage() {
    RequestCoordinator requestCoordinator = mock(RequestCoordinator.class);
    doAnswer(new Answer() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        Request request = (Request) invocation.getArguments()[0];
        assertFalse(request.isComplete());
        return true;
      }
    }).when(requestCoordinator).canSetImage(any(Request.class));

    harness.requestCoordinator = requestCoordinator;
    SingleRequest<List> request = harness.getRequest();

    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(requestCoordinator).canSetImage(eq(request));
  }

  @Test
  public void testIsNotFailedWithoutException() {
    SingleRequest<List> request = harness.getRequest();

    assertFalse(request.isFailed());
  }

  @Test
  public void testIsFailedAfterException() {
    SingleRequest<List> request = harness.getRequest();

    request.onLoadFailed(new GlideException("test"));
    assertTrue(request.isFailed());
  }

  @Test
  public void testIgnoresOnSizeReadyIfNotWaitingForSize() {
    SingleRequest<List> request = harness.getRequest();
    request.begin();
    request.onSizeReady(100, 100);
    request.onSizeReady(100, 100);

    verify(harness.engine, times(1))
        .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), eq(100), eq(100),
            eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class),
            eq(harness.transformations), anyBoolean(), anyBoolean(), any(Options.class),
            anyBoolean(), anyBoolean(), anyBoolean(), any(ResourceCallback.class));
  }

  @Test
  public void testIsFailedAfterNoResultAndNullException() {
    SingleRequest<List> request = harness.getRequest();

    request.onLoadFailed(new GlideException("test"));
    assertTrue(request.isFailed());
  }

  @Test
  public void testEngineLoadCancelledOnCancel() {
    Engine.LoadStatus loadStatus = mock(Engine.LoadStatus.class);

    when(harness.engine
       .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), anyInt(), anyInt(),
          eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class),
          eq(harness.transformations), anyBoolean(), anyBoolean(), any(Options.class),
          anyBoolean(), anyBoolean(), anyBoolean(), any(ResourceCallback.class)))
        .thenReturn(loadStatus);

    SingleRequest<List> request = harness.getRequest();
    request.begin();

    request.onSizeReady(100, 100);
    request.cancel();

    verify(loadStatus).cancel();
  }

  @Test
  public void testResourceIsRecycledOnClear() {
    SingleRequest<List> request = harness.getRequest();

    request.onResourceReady(harness.resource, DataSource.REMOTE);
    request.clear();

    verify(harness.engine).release(eq(harness.resource));
  }

  @Test
  public void testPlaceholderDrawableIsSet() {
    Drawable expected = new ColorDrawable(Color.RED);

    MockTarget target = new MockTarget();

    harness.placeholderDrawable = expected;
    harness.target = target;
    SingleRequest<List> request = harness.getRequest();
    request.begin();

    assertEquals(expected, target.currentPlaceholder);
  }

  @Test
  public void testErrorDrawableIsSetOnLoadFailed() {
    Drawable expected = new ColorDrawable(Color.RED);

    MockTarget target = new MockTarget();

    harness.errorDrawable = expected;
    harness.target = target;
    SingleRequest<List> request = harness.getRequest();

    request.onLoadFailed(new GlideException("test"));

    assertEquals(expected, target.currentPlaceholder);
  }

  @Test
  public void testPlaceholderDrawableSetOnNullModelWithNoErrorDrawable() {
    Drawable placeholder = new ColorDrawable(Color.RED);

    MockTarget target = new MockTarget();

    harness.errorDrawable = placeholder;
    harness.target = target;
    harness.model = null;
    SingleRequest<List> request = harness.getRequest();

    request.begin();

    assertEquals(placeholder, target.currentPlaceholder);
  }

  @Test
  public void testErrorDrawableSetOnNullModelWithErrorDrawable() {
    Drawable placeholder = new ColorDrawable(Color.RED);
    Drawable errorPlaceholder = new ColorDrawable(Color.GREEN);

    MockTarget target = new MockTarget();

    harness.placeholderDrawable = placeholder;
    harness.errorDrawable = errorPlaceholder;
    harness.target = target;
    harness.model = null;
    SingleRequest<List> request = harness.getRequest();

    request.begin();

    assertEquals(errorPlaceholder, target.currentPlaceholder);
  }


  @Test
  public void testFallbackDrawableSetOnNullModelWithErrorAndFallbackDrawables() {
    Drawable placeholder = new ColorDrawable(Color.RED);
    Drawable errorPlaceholder = new ColorDrawable(Color.GREEN);
    Drawable fallback = new ColorDrawable(Color.BLUE);

    MockTarget target = new MockTarget();
    harness.placeholderDrawable = placeholder;
    harness.errorDrawable = errorPlaceholder;
    harness.fallbackDrawable = fallback;
    harness.target = target;
    harness.model = null;
    SingleRequest<List> request = harness.getRequest();
    request.begin();
    assertEquals(fallback, target.currentPlaceholder);
  }


  @Test
  public void testIsNotRunningBeforeRunCalled() {
    assertFalse(harness.getRequest().isRunning());
  }

  @Test
  public void testIsRunningAfterRunCalled() {
    Request request = harness.getRequest();
    request.begin();
    assertTrue(request.isRunning());
  }

  @Test
  public void testIsNotRunningAfterComplete() {
    SingleRequest<List> request = harness.getRequest();
    request.begin();
    request.onResourceReady(harness.resource, DataSource.REMOTE);

    assertFalse(request.isRunning());
  }

  @Test
  public void testIsNotRunningAfterFailing() {
    SingleRequest<List> request = harness.getRequest();
    request.begin();
    request.onLoadFailed(new GlideException("test"));

    assertFalse(request.isRunning());
  }

  @Test
  public void testIsNotRunningAfterClear() {
    SingleRequest<List> request = harness.getRequest();
    request.begin();
    request.clear();

    assertFalse(request.isRunning());
  }

  @Test
  public void testCallsTargetOnResourceReadyIfNoRequestListener() {
    harness.requestListener = null;
    SingleRequest<List> request = harness.getRequest();
    request.onResourceReady(harness.resource, DataSource.LOCAL);

    verify(harness.target).onResourceReady(eq(harness.result), anyTransition());
  }

  @Test
  public void testCallsTargetOnResourceReadyIfRequestListenerReturnsFalse() {
    SingleRequest<List> request = harness.getRequest();
    when(harness.requestListener
        .onResourceReady(any(List.class), any(Number.class), eq(harness.target), isADataSource(),
            anyBoolean())).thenReturn(false);
    request.onResourceReady(harness.resource, DataSource.LOCAL);

    verify(harness.target).onResourceReady(eq(harness.result), anyTransition());
  }

  @Test
  public void testDoesNotCallTargetOnResourceReadyIfRequestListenerReturnsTrue() {
    SingleRequest<List> request = harness.getRequest();
    when(harness.requestListener
        .onResourceReady(any(List.class), any(Number.class), eq(harness.target), isADataSource(),
            anyBoolean())).thenReturn(true);
    request.onResourceReady(harness.resource, DataSource.REMOTE);

    verify(harness.target, never()).onResourceReady(any(List.class), anyTransition());
  }

  @Test
  public void testCallsTargetOnExceptionIfNoRequestListener() {
    harness.requestListener = null;
    SingleRequest<List> request = harness.getRequest();
    request.onLoadFailed(new GlideException("test"));

    verify(harness.target).onLoadFailed(eq(harness.errorDrawable));
  }

  @Test
  public void testCallsTargetOnExceptionIfRequestListenerReturnsFalse() {
    SingleRequest<List> request = harness.getRequest();
    when(harness.requestListener.onLoadFailed(isAGlideException(), any(Number.class),
        eq(harness.target), anyBoolean()))
        .thenReturn(false);
    request.onLoadFailed(new GlideException("test"));

    verify(harness.target).onLoadFailed(eq(harness.errorDrawable));
  }

  @Test
  public void testDoesNotCallTargetOnExceptionIfRequestListenerReturnsTrue() {
    SingleRequest<List> request = harness.getRequest();
    when(harness.requestListener.onLoadFailed(isAGlideException(), any(Number.class),
        eq(harness.target), anyBoolean()))
        .thenReturn(true);

    request.onLoadFailed(new GlideException("test"));

    verify(harness.target, never()).onLoadFailed(any(Drawable.class));
  }

  @Test
  public void testRequestListenerIsCalledWithResourceResult() {
    SingleRequest<List> request = harness.getRequest();
    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(harness.requestListener)
        .onResourceReady(eq(harness.result), any(Number.class), isAListTarget(), isADataSource(),
            anyBoolean());
  }

  @Test
  public void testRequestListenerIsCalledWithModel() {
    SingleRequest<List> request = harness.getRequest();
    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(harness.requestListener)
        .onResourceReady(any(List.class), eq(harness.model), isAListTarget(), isADataSource(),
            anyBoolean());
  }

  @Test
  public void testRequestListenerIsCalledWithTarget() {
    SingleRequest<List> request = harness.getRequest();
    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(harness.requestListener)
        .onResourceReady(any(List.class), any(Number.class), eq(harness.target), isADataSource(),
            anyBoolean());
  }

  @Test
  public void testRequestListenerIsCalledWithLoadedFromMemoryIfLoadCompletesSynchronously() {
    final SingleRequest<List> request = harness.getRequest();

    when(harness.engine
        .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), anyInt(),
            anyInt(), eq(Object.class), eq(List.class), any(Priority.class),
            any(DiskCacheStrategy.class), eq(harness.transformations), anyBoolean(),
            anyBoolean(), any(Options.class), anyBoolean(), anyBoolean(), anyBoolean(),
            any(ResourceCallback.class)))
        .thenAnswer(new Answer<Object>() {
          @Override
          public Object answer(InvocationOnMock invocation) throws Throwable {
            request.onResourceReady(harness.resource, DataSource.MEMORY_CACHE);
            return null;
          }
        });

    request.begin();
    request.onSizeReady(100, 100);
    verify(harness.requestListener)
        .onResourceReady(eq(harness.result), any(Number.class), isAListTarget(),
            eq(DataSource.MEMORY_CACHE), anyBoolean());
  }

  @Test
  public void
  testRequestListenerIsCalledWithNotLoadedFromMemoryCacheIfLoadCompletesAsynchronously() {
    SingleRequest<List> request = harness.getRequest();
    request.onSizeReady(100, 100);
    request.onResourceReady(harness.resource, DataSource.LOCAL);

    verify(harness.requestListener)
        .onResourceReady(eq(harness.result), any(Number.class), isAListTarget(),
            eq(DataSource.LOCAL), anyBoolean());
  }

  @Test
  public void testRequestListenerIsCalledWithIsFirstResourceIfNoRequestCoordinator() {
    harness.requestCoordinator = null;
    SingleRequest<List> request = harness.getRequest();
    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(harness.requestListener)
        .onResourceReady(eq(harness.result), any(Number.class), isAListTarget(), isADataSource(),
            eq(true));
  }

  @Test
  public void testRequestListenerIsCalledWithFirstImageIfRequestCoordinatorReturnsNoResourceSet() {
    SingleRequest<List> request = harness.getRequest();
    when(harness.requestCoordinator.isAnyResourceSet()).thenReturn(false);
    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(harness.requestListener)
        .onResourceReady(eq(harness.result), any(Number.class), isAListTarget(), isADataSource(),
            eq(true));
  }

  @Test
  public void
  testRequestListenerIsCalledWithNotIsFirstRequestIfRequestCoordinatorReturnsResourceSet() {
    SingleRequest<List> request = harness.getRequest();
    when(harness.requestCoordinator.isAnyResourceSet()).thenReturn(true);
    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(harness.requestListener)
        .onResourceReady(eq(harness.result), any(Number.class), isAListTarget(),
            isADataSource(), eq(false));
  }

  @Test
  public void testTargetIsCalledWithAnimationFromFactory() {
    SingleRequest<List> request = harness.getRequest();
    Transition<List> transition = mockTransition();
    when(harness.factory.build(any(DataSource.class), anyBoolean())).thenReturn(transition);
    request.onResourceReady(harness.resource, DataSource.DATA_DISK_CACHE);

    verify(harness.target).onResourceReady(eq(harness.result), eq(transition));
  }

  @Test
  public void testCallsGetSizeIfOverrideWidthIsLessThanZero() {
    harness.overrideWidth = -1;
    harness.overrideHeight = 100;
    SingleRequest<List> request = harness.getRequest();
    request.begin();

    verify(harness.target).getSize(any(SizeReadyCallback.class));
  }

  @Test
  public void testCallsGetSizeIfOverrideHeightIsLessThanZero() {
    harness.overrideHeight = -1;
    harness.overrideWidth = 100;
    SingleRequest<List> request = harness.getRequest();
    request.begin();

    verify(harness.target).getSize(any(SizeReadyCallback.class));
  }

  @Test
  public void testDoesNotCallGetSizeIfOverrideWidthAndHeightAreSet() {
    harness.overrideWidth = 100;
    harness.overrideHeight = 100;
    SingleRequest<List> request = harness.getRequest();
    request.begin();

    verify(harness.target, never()).getSize(any(SizeReadyCallback.class));
  }

  @Test
  public void testCallsEngineWithOverrideWidthAndHeightIfSet() {
    harness.overrideWidth = 1;
    harness.overrideHeight = 2;

    SingleRequest<List> request = harness.getRequest();
    request.begin();

    verify(harness.engine)
        .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), anyInt(),
            anyInt(), eq(Object.class), eq(List.class), any(Priority.class),
            any(DiskCacheStrategy.class), eq(harness.transformations), anyBoolean(),
            anyBoolean(), any(Options.class), anyBoolean(), anyBoolean(), anyBoolean(),
            any(ResourceCallback.class));
  }

  @Test
  public void testDoesNotSetErrorDrawableIfRequestCoordinatorDoesntAllowIt() {
    harness.errorDrawable = new ColorDrawable(Color.RED);
    SingleRequest<List> request = harness.getRequest();
    when(harness.requestCoordinator.canNotifyStatusChanged(any(Request.class))).thenReturn(false);
    request.onLoadFailed(new GlideException("test"));

    verify(harness.target, never()).onLoadFailed(any(Drawable.class));
  }

  @Test
  public void testCanReRunCancelledRequests() {
    doAnswer(new CallSizeReady(100, 100)).when(harness.target)
        .getSize(any(SizeReadyCallback.class));

    when(harness.engine
        .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), eq(100), eq(100),
            eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class),
            eq(harness.transformations), anyBoolean(), anyBoolean(), any(Options.class),
            anyBoolean(), anyBoolean(), anyBoolean(), any(ResourceCallback.class)))
        .thenAnswer(new CallResourceCallback(harness.resource));
    SingleRequest<List> request = harness.getRequest();

    request.begin();
    request.cancel();
    request.begin();

    verify(harness.target, times(2)).onResourceReady(eq(harness.result), anyTransition());
  }

  @Test
  public void testResourceOnlyReceivesOneGetOnResourceReady() {
    SingleRequest<List> request = harness.getRequest();
    request.onResourceReady(harness.resource, DataSource.LOCAL);

    verify(harness.resource, times(1)).get();
  }

  @Test
  public void testDoesNotStartALoadIfOnSizeReadyIsCalledAfterCancel() {
    SingleRequest<List> request = harness.getRequest();
    request.cancel();
    request.onSizeReady(100, 100);

    verify(harness.engine, never())
        .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), anyInt(),
            anyInt(), eq(Object.class), eq(List.class), any(Priority.class),
            any(DiskCacheStrategy.class), eq(harness.transformations), anyBoolean(),
            anyBoolean(), any(Options.class), anyBoolean(), anyBoolean(), anyBoolean(),
            any(ResourceCallback.class));
  }


  @Test
  public void testCallsSourceUnlimitedExecutorEngineIfOptionsIsSet() {
    doAnswer(new CallSizeReady(100, 100)).when(harness.target)
        .getSize(any(SizeReadyCallback.class));

    harness.useUnlimitedSourceGeneratorsPool = true;

    SingleRequest<List> request = harness.getRequest();
    request.begin();

    verify(harness.engine)
        .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), anyInt(),
            anyInt(), eq(Object.class), eq(List.class), any(Priority.class),
            any(DiskCacheStrategy.class), eq(harness.transformations), anyBoolean(),
            anyBoolean(), any(Options.class), anyBoolean(), eq(Boolean.TRUE), anyBoolean(),
            any(ResourceCallback.class));
  }

  @Test
  public void testCallsSourceExecutorEngineIfOptionsIsSet() {
    doAnswer(new CallSizeReady(100, 100)).when(harness.target)
        .getSize(any(SizeReadyCallback.class));

    harness.useUnlimitedSourceGeneratorsPool = false;

    SingleRequest<List> request = harness.getRequest();
    request.begin();

    verify(harness.engine)
        .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), anyInt(),
            anyInt(), eq(Object.class), eq(List.class), any(Priority.class),
            any(DiskCacheStrategy.class), eq(harness.transformations), anyBoolean(),
            anyBoolean(), any(Options.class), anyBoolean(), eq(Boolean.FALSE), anyBoolean(),
            any(ResourceCallback.class));
  }

  @Test
  public void testIsEquivalentTo() {
    SingleRequest<List> originalRequest1 = harness.getRequest();
    SingleRequest<List> originalRequest2 = harness.getRequest();
    assertTrue(originalRequest1.isEquivalentTo(originalRequest2));

    harness = new RequestHarness();
    harness.overrideWidth = harness.overrideWidth * 2;
    SingleRequest<List> widthRequest = harness.getRequest();
    assertTrue(widthRequest.isEquivalentTo(widthRequest));
    assertFalse(widthRequest.isEquivalentTo(originalRequest1));
    assertFalse(originalRequest1.isEquivalentTo(widthRequest));

    harness = new RequestHarness();
    harness.overrideHeight = harness.overrideHeight * 2;
    SingleRequest<List> heightRequest = harness.getRequest();
    assertTrue(heightRequest.isEquivalentTo(heightRequest));
    assertFalse(heightRequest.isEquivalentTo(originalRequest1));
    assertFalse(originalRequest1.isEquivalentTo(heightRequest));

    harness = new RequestHarness();
    harness.model = 12345679;
    SingleRequest<List> modelRequest = harness.getRequest();
    assertTrue(modelRequest.isEquivalentTo(modelRequest));
    assertFalse(modelRequest.isEquivalentTo(originalRequest1));
    assertFalse(originalRequest1.isEquivalentTo(modelRequest));

    harness = new RequestHarness();
    harness.model = null;
    SingleRequest<List> nullModelRequest = harness.getRequest();
    assertTrue(nullModelRequest.isEquivalentTo(nullModelRequest));
    assertFalse(nullModelRequest.isEquivalentTo(originalRequest1));
    assertFalse(originalRequest1.isEquivalentTo(nullModelRequest));

    harness = new RequestHarness();
    harness.errorDrawable = new ColorDrawable(Color.GRAY);
    SingleRequest<List> errorRequest = harness.getRequest();
    assertTrue(errorRequest.isEquivalentTo(errorRequest));
    assertFalse(errorRequest.isEquivalentTo(originalRequest1));
    assertFalse(originalRequest1.isEquivalentTo(errorRequest));

    harness = new RequestHarness();
    harness.priority = Priority.LOW;
    SingleRequest<List> priorityRequest = harness.getRequest();
    assertTrue(priorityRequest.isEquivalentTo(priorityRequest));
    assertFalse(priorityRequest.isEquivalentTo(originalRequest1));
    assertFalse(originalRequest1.isEquivalentTo(priorityRequest));
  }

  // TODO do we want to move these to Util?
  @SuppressWarnings("unchecked")
  private static <T> Transition<T> mockTransition() {
    return mock(Transition.class);
  }

  @SuppressWarnings("unchecked")
  private static Target<List> isAListTarget() {
    return isA(Target.class);
  }

  private static GlideException isAGlideException() {
    return isA(GlideException.class);
  }

  @SuppressWarnings("unchecked")
  private static <T> Transition<T> anyTransition() {
    return any(Transition.class);
  }

  private static class CallResourceCallback implements Answer {

    private Resource resource;

    public CallResourceCallback(Resource resource) {
      this.resource = resource;
    }

    @Override
    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
      ResourceCallback cb =
          (ResourceCallback) invocationOnMock.getArguments()[invocationOnMock.getArguments().length
              - 1];
      cb.onResourceReady(resource, DataSource.REMOTE);
      return null;
    }
  }

  private static class CallSizeReady implements Answer {

    private int width;
    private int height;

    public CallSizeReady(int width, int height) {
      this.width = width;
      this.height = height;
    }

    @Override
    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
      SizeReadyCallback cb = (SizeReadyCallback) invocationOnMock.getArguments()[0];
      cb.onSizeReady(width, height);
      return null;
    }
  }

  private static class MockTarget implements Target<List> {
    private Drawable currentPlaceholder;

    @Override
    public void onLoadCleared(Drawable placeholder) {
      currentPlaceholder = placeholder;
    }

    @Override
    public void onLoadStarted(Drawable placeholder) {
      currentPlaceholder = placeholder;

    }

    @Override
    public void onLoadFailed(Drawable errorDrawable) {
      currentPlaceholder = errorDrawable;

    }

    @Override
    public void onResourceReady(List resource, Transition<? super List> transition) {
      currentPlaceholder = null;
    }


    @Override
    public void getSize(SizeReadyCallback cb) {
    }

    @Override
    public void removeCallback(SizeReadyCallback cb) {
      // Do nothing.
    }

    @Override
    public void setRequest(Request request) {
    }

    @Override
    public Request getRequest() {
      return null;
    }

    @Override
    public void onStart() {
    }

    @Override
    public void onStop() {

    }

    @Override
    public void onDestroy() {

    }
  }
}
