/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.qpid.disttest.client;

import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

import junit.framework.TestCase;

import org.apache.qpid.disttest.DistributedTestException;
import org.apache.qpid.disttest.message.ParticipantResult;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;

public class ParticipantExecutorTest extends TestCase
{
    private static final ResultHasError HAS_ERROR = new ResultHasError();
    private static final String CLIENT_NAME = "CLIENT_NAME";
    private static final String PARTICIPANT_NAME = "PARTICIPANT_NAME";
    private ParticipantExecutor _participantExecutor = null;
    private Client _client = null;
    private Participant _participant = null;
    private ParticipantResult _mockResult;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();

        _client = mock(Client.class);
        when(_client.getClientName()).thenReturn(CLIENT_NAME);
        _participant = mock(Participant.class);

        _participantExecutor = new ParticipantExecutor(_participant);
        _participantExecutor.setExecutor(new SynchronousExecutor());

        _mockResult = mock(ParticipantResult.class);
    }

    public void testStart() throws Exception
    {
        when(_participant.doIt(CLIENT_NAME)).thenReturn(_mockResult);

        _participantExecutor.start(_client);

        InOrder inOrder = inOrder(_participant, _client);

        inOrder.verify(_participant).doIt(CLIENT_NAME);
        inOrder.verify(_participant).releaseResources();
        inOrder.verify(_client).sendResults(_mockResult);
    }

    public void testParticipantThrowsException() throws Exception
    {
        when(_participant.doIt(CLIENT_NAME)).thenThrow(DistributedTestException.class);

        _participantExecutor.start(_client);

        InOrder inOrder = inOrder(_participant, _client);

        inOrder.verify(_participant).doIt(CLIENT_NAME);
        inOrder.verify(_participant).releaseResources();
        inOrder.verify(_client).sendResults(argThat(HAS_ERROR));
    }

    public void testReleaseResourcesThrowsException() throws Exception
    {
        when(_participant.doIt(CLIENT_NAME)).thenReturn(_mockResult);
        doThrow(DistributedTestException.class).when(_participant).releaseResources();

        _participantExecutor.start(_client);

        InOrder inOrder = inOrder(_participant, _client);

        inOrder.verify(_participant).doIt(CLIENT_NAME);
        inOrder.verify(_participant).releaseResources();

        // check that sendResults is called even though releaseResources threw an exception
        inOrder.verify(_client).sendResults(_mockResult);
    }

    public void testThreadNameAndDaemonness() throws Exception
    {
        ThreadPropertyReportingParticipant participant = new ThreadPropertyReportingParticipant(PARTICIPANT_NAME);
        _participantExecutor = new ParticipantExecutor(participant);

        _participantExecutor.start(_client);
        participant.awaitExecution();

        assertTrue("Participant should be run in a thread named after it", participant.threadWasCalled().endsWith(PARTICIPANT_NAME));
        assertTrue("Executor should use daemon threads to avoid them preventing JVM termination", participant.wasDaemon());
    }

    private static final class ThreadPropertyReportingParticipant implements Participant
    {
        private final String _participantName;
        private final CountDownLatch _participantExecuted = new CountDownLatch(1);
        private String _threadName;
        private boolean _daemon;

        public ThreadPropertyReportingParticipant(String participantName)
        {
            _participantName = participantName;
        }

        public String threadWasCalled()
        {
            return _threadName;
        }

        public boolean wasDaemon()
        {
            return _daemon;
        }

        @Override
        public void releaseResources()
        {
        }

        @Override
        public String getName()
        {
            return _participantName;
        }

        @Override
        public ParticipantResult doIt(String registeredClientName) throws Exception
        {
            Thread currentThread = Thread.currentThread();
            _threadName = currentThread.getName();
            _daemon = currentThread.isDaemon();

            _participantExecuted.countDown();

            return null; // unused
        }

        public void awaitExecution()
        {
            boolean success = false;
            try
            {
                success = _participantExecuted.await(5, TimeUnit.SECONDS);
            }
            catch (InterruptedException e)
            {
                Thread.currentThread().interrupt();
            }

            assertTrue("Participant not executed", success);
        }
    }

    /** avoids our unit test needing to use multiple threads */
    private final class SynchronousExecutor implements Executor
    {
        @Override
        public void execute(Runnable command)
        {
            command.run();
        }
    }

    private static class ResultHasError extends ArgumentMatcher<ParticipantResult>
    {
        @Override
        public boolean matches(Object argument)
        {
            ParticipantResult result = (ParticipantResult) argument;
            return result.hasError();
        }
    }

}
