package com.bumptech.glide.load.engine;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.bumptech.glide.load.Key;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, emulateSdk = 18)
public class EngineResourceTest {
    private EngineResource<Object> engineResource;
    private EngineResource.ResourceListener listener;
    private Key cacheKey = mock(Key.class);
    private Resource<Object> resource;

    @SuppressWarnings("unchecked")
    @Before
    public void setUp() {
        resource = mock(Resource.class);
        engineResource = new EngineResource<Object>(resource, true /*isCacheable*/);
        listener = mock(EngineResource.ResourceListener.class);
        engineResource.setResourceListener(cacheKey, listener);
    }

    @Test
    public void testCanAcquireAndRelease() {
        engineResource.acquire();
        engineResource.release();

        verify(listener).onResourceReleased(cacheKey, engineResource);
    }

    @Test
    public void testCanAcquireMultipleTimesAndRelease() {
        engineResource.acquire();
        engineResource.acquire();
        engineResource.release();
        engineResource.release();

        verify(listener).onResourceReleased(eq(cacheKey), eq(engineResource));
    }

    @Test
    public void testDelegatesGetToWrappedResource() {
        Object expected = new Object();
        when(resource.get()).thenReturn(expected);
        assertEquals(expected, engineResource.get());
    }

    @Test
    public void testDelegatesGetSizeToWrappedResource() {
        int expectedSize = 1234;
        when(resource.getSize()).thenReturn(expectedSize);
        assertEquals(expectedSize, engineResource.getSize());
    }

    @Test
    public void testRecyclesWrappedResourceWhenRecycled() {
        engineResource.acquire();
        engineResource.release();
        engineResource.recycle();
        verify(resource).recycle();
    }

    @Test(expected = IllegalStateException.class)
    public void testThrowsIfRecycledTwice() {
        engineResource.recycle();
        engineResource.recycle();
    }

    @Test(expected = IllegalStateException.class)
    public void testThrowsIfReleasedBeforeAcquired() {
        engineResource.release();
    }

    @Test(expected = IllegalStateException.class)
    public void testThrowsIfRecycledWhileAcquired() {
        engineResource.acquire();
        engineResource.recycle();
    }

    @Test(expected = IllegalStateException.class)
    public void testThrowsIfAcquiredAfterRecycled() {
        engineResource.recycle();
        engineResource.acquire();
    }

    @Test
    public void testThrowsIfAcquiredOnBackgroundThread() throws InterruptedException {
        Thread otherThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    engineResource.acquire();
                } catch (IllegalThreadStateException e) {
                    return;
                }
                fail("Failed to receive expected IllegalThreadStateException");
            }
        });
        otherThread.start();
        otherThread.join();
    }

    @Test
    public void testThrowsIfReleasedOnBackgroundThread() throws InterruptedException {
        engineResource.acquire();
        Thread otherThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                     engineResource.release();
                } catch (IllegalThreadStateException e) {
                    return;
                }
                fail("Failed to receive expected IllegalThreadStateException");
            }
        });
        otherThread.start();
        otherThread.join();
    }

    @Test(expected = IllegalStateException.class)
    public void testThrowsIfReleasedMoreThanAcquired() {
        engineResource.acquire();
        engineResource.release();
        engineResource.release();
    }

    @Test(expected = NullPointerException.class)
    public void testThrowsIfWrappedResourceIsNull() {
        new EngineResource<Object>(null, false);
    }

    @Test
    public void testCanSetAndGetIsCacheable() {
        engineResource = new EngineResource<Object>(mock(Resource.class), true);
        assertTrue(engineResource.isCacheable());
        engineResource = new EngineResource<Object>(mock(Resource.class), false);
        assertFalse(engineResource.isCacheable());
    }
}
