/*
 *
 * 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.test.framework.distributedtesting;

import junit.framework.TestResult;
import org.apache.log4j.Logger;

import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
import org.apache.qpid.test.framework.FrameworkBaseCase;
import org.apache.qpid.test.framework.TestClientDetails;
import org.apache.qpid.test.framework.sequencers.CircuitFactory;
import org.apache.qpid.test.utils.ConversationFactory;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import java.util.Collection;
import java.util.Set;

/**
 * DistributedTestDecorator is a base class for writing test decorators that invite test clients to participate in
 * distributed test cases. It provides a helper method, {@link #signupClients}, that broadcasts an invitation and
 * returns the set of test clients that are available to particiapte in the test.
 *
 * <p/>When used to wrap a {@link FrameworkBaseCase} test, it replaces the default {@link CircuitFactory} implementations
 * with a suitable circuit factory for distributed tests. Concrete implementations can use this to configure the sending
 * and receiving roles on the test.
 *
 * <p><table id="crc"><caption>CRC Card</caption>
 * <tr><th> Responsibilities <th> Collaborations
 * <tr><td> Broadcast test invitations and collect enlists. <td> {@link ConversationFactory}.
 * </table>
 */
public abstract class DistributedTestDecorator extends WrappedSuiteTestDecorator
{
    /** Used for debugging. */
    private static final Logger log = Logger.getLogger(DistributedTestDecorator.class);

    /** Holds the contact information for all test clients that are available and that may take part in the test. */
    Set<TestClientDetails> allClients;

    /** Holds the conversation helper for the control level conversation for coordinating the test through. */
    ConversationFactory conversationFactory;

    /** Holds the connection that the control conversation is held over. */
    Connection connection;

    /** Holds the underlying test suite that this decorator wraps. */
    WrappedSuiteTestDecorator testSuite;

    /** Holds the control topic, on which test invitations are broadcast. */
    protected Destination controlTopic;

    /**
     * Creates a wrapped suite test decorator from another one.
     *
     * @param suite               The test suite.
     * @param availableClients    The list of all clients that responded to the compulsory invite.
     * @param controlConversation The conversation helper for the control level, test coordination conversation.
     * @param controlConnection   The connection that the coordination messages are sent over.
     */
    public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> availableClients,
        ConversationFactory controlConversation, Connection controlConnection)
    {
        super(suite);

        log.debug("public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> allClients = "
            + availableClients + ", ConversationHelper controlConversation = " + controlConversation + "): called");

        testSuite = suite;
        allClients = availableClients;
        conversationFactory = controlConversation;
        connection = controlConnection;

        // Set up the test control topic.
        try
        {
            controlTopic = conversationFactory.getSession().createTopic("iop.control");
        }
        catch (JMSException e)
        {
            throw new RuntimeException("Unable to create the coordinating control topic to broadcast test invites on.", e);
        }
    }

    /**
     * Should run all of the tests in the wrapped test suite.
     *
     * @param testResult The the results object to monitor the test results with.
     */
    public abstract void run(TestResult testResult);

    /**
     * Should provide the distributed test sequencer to pass to {@link org.apache.qpid.test.framework.FrameworkBaseCase}
     * tests.
     *
     * @return A distributed test sequencer.
     */
    public abstract CircuitFactory getTestSequencer();

    /**
     * Broadcasts an invitation to participate in a coordinating test case to find out what clients are available to
     * run the test case.
     *
     * @param coordTest The coordinating test case to broadcast an inviate for.
     *
     * @return A set of test clients that accepted the invitation.
     */
    protected Set<TestClientDetails> signupClients(FrameworkBaseCase coordTest)
    {
        // Broadcast the invitation to find out what clients are available to test.
        Set<TestClientDetails> enlists;
        try
        {
            Message invite = conversationFactory.getSession().createMessage();

            ConversationFactory.Conversation conversation = conversationFactory.startConversation();

            invite.setStringProperty("CONTROL_TYPE", "INVITE");
            invite.setStringProperty("TEST_NAME", coordTest.getTestCaseNameForTestMethod(coordTest.getName()));

            conversation.send(controlTopic, invite);

            // Wait for a short time, to give test clients an opportunity to reply to the invitation.
            Collection<Message> replies = conversation.receiveAll(allClients.size(), 500);
            enlists = Coordinator.extractEnlists(replies);
        }
        catch (JMSException e)
        {
            throw new RuntimeException("There was a JMSException during the invite/enlist conversation.", e);
        }

        return enlists;
    }

    /**
     * Prints a string summarizing this test decorator, mainly for debugging purposes.
     *
     * @return String representation for debugging purposes.
     */
    public String toString()
    {
        return "DistributedTestDecorator: [ testSuite = " + testSuite + " ]";
    }
}
