/** 
 * 
 * Copyright 2004 Protique Ltd
 * 
 * Licensed 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.activemq.transport;

import EDU.oswego.cs.dl.util.concurrent.Slot;
import junit.framework.TestCase;
import org.activemq.io.WireFormat;
import org.activemq.io.impl.DefaultWireFormat;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.ActiveMQTopic;
import org.activemq.message.KeepAlive;
import org.activemq.message.Packet;
import org.activemq.message.PacketListener;
import org.activemq.message.Receipt;
import org.activemq.message.ReceiptHolder;
import org.activemq.util.IdGenerator;

import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

/**
 * @version $Revision: 1.1.1.1 $
 */
public class TransportChannelTestSupport extends TestCase implements PacketListener, TransportChannelListener {

    protected int TEST_SIZE = 100;
    protected Object mutex;
    protected TransportChannel sender;
    protected TransportChannel receiver;
    protected TransportServerChannel server;
    protected ArrayList packets;
    protected List exceptions = new Vector();
    protected boolean sendReceipts = false;
    private IdGenerator idGenerator = new IdGenerator();
    protected WireFormat wireFormat = new DefaultWireFormat();
    private boolean closeReceiver = true;

    public TransportChannelTestSupport() {
    }

    public TransportChannelTestSupport(String name) {
        super(name);
    }

    /*
     * test for Receipt send(Packet, int)
     */
    public void testSendPacket() throws Exception {
        System.out.println("Sending packets");

        List tmpList = (List) packets.clone();
        for (int i = 0; i < TEST_SIZE; i++) {
            Packet packet = (Packet) tmpList.get(i);
            sender.asyncSend(packet);
        }
        System.out.println("Sent: " + TEST_SIZE + " packets");

        for (int i = 0; i < 10; i++) {
            synchronized (mutex) {
                if (!packets.isEmpty()) {
                    mutex.wait(500);
                }
            }
        }
        assertTrue("Packets not consumed, still have: " + packets.size() + " packet(s) unconsumed", packets.isEmpty());
        assertTrue("Exceptions were thrown: " + exceptions, exceptions.size() == 0);
    }

    public void testAsyncSendWithReceipt() throws Exception {
        sendReceipts = true;
        Packet packet = new KeepAlive();
        packet.setId((short) 1);
        ReceiptHolder rh = sender.asyncSendWithReceipt(packet);
        Receipt result = rh.getReceipt(5000);
        if (result == null) {
            fail("Should have gotten receipt");
        }
    }

    public void testRpc() throws Exception {
        sendReceipts = true;

        List tmpList = (List) packets.clone();
        for (int i = 0; i < TEST_SIZE; i++) {
            Packet packet = (Packet) tmpList.get(i);
            Receipt receipt = sender.send(packet, 4000);
            assertTrue("Receipt should not be null!", receipt != null);
            System.out.println("Got receipt: " + receipt + " for packet: " + packet);
        }
    }

    public void testForceDisconnect() throws Exception {
// Exceptions following the disconnect are allowed to be either hidden
// (e.g by a reliable transport) or shown to the client. Accept either
// and swallow
// any exceptions throws by this particular test
        sender.setExceptionListener(new ExceptionListener() {

            public void onException(JMSException arg0) {
                ;
            }
        });

        if (receiver != null) {
            receiver.setExceptionListener(new ExceptionListener() {
                public void onException(JMSException e) {
                }
            });
        }

// Send some data down the channel.
        testAsyncSendWithReceipt();

// Test disconnect. This will be fatal for most channels, but reliable
// channels
// should be able to recover from it. In any case transportConnected
// should be false
// immediately after the disconnect
        final Slot disconnectEvent = new Slot();
        sender.addTransportStatusEventListener(new TransportStatusEventListener() {
            public void statusChanged(TransportStatusEvent e) {
                if (e.getChannelStatus() == TransportStatusEvent.DISCONNECTED) {
                    try {
                        disconnectEvent.offer(e, 1000);
                    }
                    catch (InterruptedException e1) {
                    }
                }
            }
        });
        sender.forceDisconnect();

        assertNotNull("Should have received state change notification", disconnectEvent.poll(1000 * 30));
        assertFalse("Should be disconnected", sender.isTransportConnected());
//there could ber exceptions thrown - which are valid for a force disconnect
//so clear them so tearDown() will pass
        exceptions.clear();
    }

    public void consume(Packet packet) {
        System.out.println("Received packet: " + packet);

        if (sendReceipts) {
            // lets send a receipt
            Receipt receipt = new Receipt();
            receipt.setId(idGenerator.getNextShortSequence());
            receipt.setCorrelationId(packet.getId());
            try {
                receiver.asyncSend(receipt);
            }
            catch (JMSException e) {
                logMessage("Sending receipt: " + receipt + " for packet: " + packet, e);
            }
        }
        else {
            packets.remove(packet);
            if (packets.isEmpty()) {
                synchronized (mutex) {
                    mutex.notify();
                }
            }
        }
    }

    /**
     * Assume that sender and receiver are created before we're invoked
     */
    protected void setUp() throws Exception {
        super.setUp();

        assertTrue("sender must be constructed in the TestCase before setUp() is invoked", sender != null);
        assertTrue("receiver or server must be constructed in the TestCase before setUp() is invoked", receiver != null
                || server != null);

        mutex = new Object();

        sender.setExceptionListener(new ExceptionListener() {

            public void onException(JMSException ex) {
                String message = "Sender got an exception:";
                logMessage(message, ex);
            }
        });

        sender.setPacketListener(new PacketListener() {

            public void consume(Packet packet) {
                System.err.println("Error - sender received a packet: " + packet);
                exceptions.add(packet);
            }

        });

        sender.setClientID("sender");
        sender.start();

        packets = new ArrayList(TEST_SIZE);
        for (int i = 0; i < TEST_SIZE; i++) {
            ActiveMQMessage test = new ActiveMQMessage();
            test.setJMSMessageID("test:" + i);
            test.setExternalMessageId(true);
            test.setJMSDestination(new ActiveMQTopic(getName()));

            packets.add(test);
        }
    }

    protected void tearDown() throws Exception {
        //getting exceptions when peers stop is acceptable
        if (receiver != null) {
            receiver.setExceptionListener(null);
        }
        super.tearDown();

        System.out.println("Stopping sender");
        sender.stop();
        if (receiver == null) {
            System.out.println("No receiver created!");
        }
        else {
            if (closeReceiver) {
                System.out.println("Stopping receiver");
                //assertTrue("No receiver created!", receiver != null);
                receiver.stop();
            }
            else {
                System.out.println("Receiver will be closed by the server");
            }
        }
        if (server != null) {
            System.out.println("Stopping server");
            server.stop();
        }
        assertTrue("Exceptions were thrown: " + exceptions, exceptions.size() == 0);
    }

    protected void configureServer() throws JMSException {
        if (server != null) {
            server.setTransportChannelListener(this);
            server.start();
            System.out.println("Server has started");

            // lets wait a little for the server to startup
            /*
             * try { Thread.sleep(500); } catch (InterruptedException e) {
             * System.out.println("Caught: " + e); e.printStackTrace(); }
             */
        }
    }

    protected void configureReceiver() {
        receiver.setPacketListener(this);

        receiver.setExceptionListener(new ExceptionListener() {

            public void onException(JMSException ex) {
                logMessage("Receiver got an exception:", ex);
            }

        });

        receiver.setClientID("receiver");

        try {
            receiver.start();
        }
        catch (JMSException e) {
            logMessage("Failure starting receiver: ", e);
        }
        System.out.println("Receiver has started");
    }


    protected void createSenderAndReceiver(String string) throws URISyntaxException, JMSException {
        URI uri = new URI(string);

        receiver = TransportChannelProvider.create(wireFormat, uri);
        if (receiver != null) {
            configureReceiver();
        }

        sender = TransportChannelProvider.create(wireFormat, uri);
    }

    protected void createSenderAndServer(String subject) throws URISyntaxException, JMSException {
        URI uri = new URI(subject);
        server = TransportServerChannelProvider.create(wireFormat, uri);
        configureServer();
        sender = TransportChannelProvider.create(wireFormat, uri);
    }

    protected void logMessage(String message, JMSException ex) {
        System.err.println(message);
        ex.printStackTrace();
        Throwable t = ex.getLinkedException();
        if (t != null && t != ex) {
            System.out.println("Reason: " + t);
            t.printStackTrace();
        }
        exceptions.add(ex);
    }

    public void addClient(TransportChannel channel) {
        this.receiver = channel;
        this.closeReceiver = false;

        System.out.println("addClient() with receiver: " + receiver);

        assertTrue("Should have received a receiver by now", receiver != null);

        configureReceiver();
    }

    public void removeClient(TransportChannel channel) {
    }
}