/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.commons.concurrent;

import static java.lang.System.identityHashCode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.lang.reflect.Proxy;
import java.util.NoSuchElementException;

import org.apache.commons.pool2.ObjectPool;
import org.junit.Before;
import org.junit.Test;

/**
 * Tests of {@link PooledProxiedServiceAPIInvocationHandler}.
 */
public class PooledProxiedServiceAPIInvocationHandlerTest {
    private static final String INPUT = "input";

    private static final String OUTPUT = "output";

    private TestService service;

    private ObjectPool<TestService> pool;

    private PooledProxiedServiceAPIInvocationHandler<TestService> handler;

    private TestService proxy;

    @Before
    @SuppressWarnings("unchecked")
    public void setUp() throws Exception {
        service = mock(TestService.class);
        when(service.call(INPUT)).thenReturn(OUTPUT);
        pool = mock(ObjectPool.class);
        when(pool.borrowObject()).thenReturn(service);
        handler = new PooledProxiedServiceAPIInvocationHandler<>(pool);
        ClassLoader loader = TestService.class.getClassLoader();
        Class<?>[] interfaces = new Class<?>[] {TestService.class };
        proxy = (TestService) Proxy.newProxyInstance(loader, interfaces,
            handler);
    }

    @Test
    public void testInvokeEquals() {
        assertEquals(proxy, proxy);
        ClassLoader loader = TestService.class.getClassLoader();
        Class<?>[] interfaces = new Class<?>[] {TestService.class };
        TestService otherProxy = (TestService) Proxy
            .newProxyInstance(loader, interfaces, handler);
        assertNotEquals(proxy, otherProxy);
    }

    @Test
    public void testInvokeHashCode() {
        assertEquals(identityHashCode(proxy), proxy.hashCode());
    }

    @Test
    public void testInvokeToString() {
        assertTrue(proxy.toString().contains(proxy.getClass().getName()));
    }

    @Test
    public void testInvokeCall() throws Exception {
        assertEquals(OUTPUT, proxy.call(INPUT));
        verify(pool).returnObject(service);
        verify(pool, never()).invalidateObject(service);
    }

    @Test(expected = IOException.class)
    public void testInvokeCallIOException() throws Exception {
        when(service.call(INPUT)).thenThrow(new IOException("test"));
        try {
            proxy.call(INPUT);
        } catch (IOException e) {
            verify(pool, never()).returnObject(service);
            verify(pool).invalidateObject(service);
            throw e;
        }
    }

    @Test(expected = SecurityException.class)
    public void testInvokeCallSecurityException() throws Exception {
        when(service.call(INPUT)).thenThrow(new SecurityException("test"));
        try {
            proxy.call(INPUT);
        } catch (SecurityException e) {
            verify(pool, never()).returnObject(service);
            verify(pool).invalidateObject(service);
            throw e;
        }
    }

    @Test(expected = IllegalStateException.class)
    public void testInvokeAquireFailed()
            throws NoSuchElementException, IllegalStateException,
            Exception {
        when(pool.borrowObject()).thenThrow(new IOException("test"));
        try {
            proxy.call(INPUT);
        } catch (IllegalStateException e) {
            verify(service, never()).call(INPUT);
            verify(pool, never()).returnObject(service);
            verify(pool, never()).invalidateObject(service);
            throw e;
        }
    }

    @Test(expected = IllegalStateException.class)
    public void testInvokeReleasePartiallyFailed() throws Exception {
        doThrow(new IOException("test")).when(pool).returnObject(service);
        try {
            proxy.call(INPUT);
        } catch (IllegalStateException e) {
            verify(pool).invalidateObject(service);
            throw e;
        }
    }

    @Test(expected = IllegalStateException.class)
    public void testInvokeReleaseFullyFailed() throws Exception {
        doThrow(new IOException("test")).when(pool).returnObject(service);
        doThrow(new IOException("test")).when(pool)
            .invalidateObject(service);
        proxy.call(INPUT);
    }

    @Test(expected = IllegalStateException.class)
    public void testInvokeDestroyFailed() throws Exception {
        when(service.call(INPUT)).thenThrow(new IOException("test"));
        doThrow(new IOException("test")).when(pool)
            .invalidateObject(service);
        proxy.call(INPUT);
    }

    public static interface TestService {
        String call(String input) throws IOException;

        @Override
        boolean equals(Object other);
    }
}
