package org.mortbay.cometd;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Random;

import junit.framework.TestCase;
import org.cometd.Client;
import org.cometd.ClientListener;
import org.cometd.DeliverListener;
import org.cometd.Message;
import org.cometd.MessageListener;
import org.cometd.QueueListener;
import org.cometd.RemoveListener;

/**
 * @author athena
 *
 */
public class ClientImplTest extends TestCase
{
    protected StandaloneClient _client;
    protected Message _message;


    protected void setUp() throws Exception
    {
        _client = new StandaloneClient();
        _message = new MessageImpl();
    }

    public void testThrowingListeners() throws Exception
    {
        Message message = m("count", "1");
        ClientListener listener1 = new ThrowingMultiListener();
        MultiListener multiListener = new MultiListener();
        _client.setMaxQueue(1);
        _client.addListener(listener1);
        _client.addListener(multiListener);

        _client.deliver(message);
        assertTrue(multiListener.messageListenerCalled());
        multiListener.reset();

        _client.deliver(message);
        assertTrue(multiListener.queueListenerCalled());
        assertTrue(multiListener.messageListenerCalled());
        multiListener.reset();

        _client.doDeliverListeners();
        assertTrue(multiListener.deliverListenerCalled());
        multiListener.reset();

        _client.remove(false);
        assertTrue(multiListener.removeListenerCalled());
        multiListener.reset();
    }

    /*
     * QueueListener examples
     */
    public void testDeleteWhenFullQueue() throws Exception
    {
        Message delete =  m("delete", "a");
        Message keep = m("keep", "b");
        Message add = m("add", "c");

        _client.setMaxQueue(2);
        _client.addListener(new DeleteWhenFullQueueListener());

        _client.deliver(delete);
        _client.deliver(keep);
        _client.deliver(add);

        assertEquals(resultsList(keep,add), _client.takeMessages());
    }

    public void testDiscardNewMessageQueue() throws Exception
    {
        Message keep1 = m("keep1", "a");
        Message keep2 = m("keep2", "b");
        Message discard = m("discard", "c");

        _client.setMaxQueue(2);
        _client.addListener(new DiscardNewMessageQueueListener());

        _client.deliver(keep1);
        _client.deliver(keep2);
        _client.deliver(discard);

        assertEquals(resultsList(keep1, keep2), _client.takeMessages());
    }

    public void testModifyExistingMessagesQueue() throws Exception
    {
        Message keep = m("keep", "a");
        Message delete = m("delete", "b");
        Message add = m("add", "c");

        _client.setMaxQueue(2);
        _client.addListener(new ModifyExistingMessagesQueueListener());

        _client.deliver(keep);
        _client.deliver(delete);
        _client.deliver(add);

        assertEquals(resultsList(keep, add), _client.takeMessages());
    }

    // TODO: improve this test?
    public void testTakeWhileQueueing() throws Exception
    {
        _client.setMaxQueue(2);
        _client.addListener(new DeleteWhenFullQueueListener());

        Message[] m = new Message[5];
        for (int i = 0; i < m.length; ++i)
        {
            m[i] = m(i + "", i+"");
        }

        (new Thread() {
            public void run()
            {
                while (true ) {
                    _client.getQueue().poll();
                    try
                    {
                        Thread.sleep(100);
                    }
                    catch (Exception e) {}
                }
            }
        }).start();

        for (Message message : m)
        {
            _client.deliver(message);
        }

        assertEquals(resultsList(m[m.length-2], m[m.length-1]),_client.takeMessages());
    }

    public void testId() throws Exception
    {
        AbstractBayeux bayeux = new BayeuxStub();
        //bayeux.setNodeId("nodeid");
        _client = new StandaloneClient(bayeux);
        bayeux.addClient(_client,null);

        // TODO
    }

    public void testMessageListener() throws Exception
    {
        _message.put("key","value");

        _client.addListener(new CheckMessageListener(_message));
        _client.addListener(new CheckMessageListener(_message));

        _client.deliver(_message);
    }

    public void testRemoveListener() throws Exception
    {
        // TODO
    }

    public void testDeliverListener() throws Exception
    {
        final boolean[] called = {false};
        _client.addListener(new DeliverListener()
        {
            public void deliver(Client client, Queue<Message> queue)
            {
                called[0]=true;
            }
        });

        Message ping = m("ping", "hello");
        _client.deliver(ping);
        assertFalse(called[0]);

        _client.doDeliverListeners();

        assertTrue(called[0]);
    }

/*  // TODO: make sure this works properly. I think there's a chance of a deadlock
    public void testTakeMessage() throws Exception
    {
        Message[] messages = {
                m("a", "apple"),
                m("b", "bapple"),
                m("c", "capple"),
                m("d", "dapple"),
                m("e", "eapple"),
                m("f", "fapple")
        };

        final CountDownLatch todo = new CountDownLatch(messages.length);

        // temporary variable to avoid error with abstract lists not implementing remove
        List<Message> messageList = new ArrayList<Message>();
        messageList.addAll(Arrays.asList(messages));
        _client.returnMessages(messageList);

        final ClientImpl threadClient = _client;
        for(int i = 0; i < _client.getMessages(); ++i)
        {
            final int x = i;
            (new Thread() {
                public void run()
                {
                    try
                    {
                        int sleep = 10;
                        Thread.sleep(sleep);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }

                    Message message = threadClient.takeMessage();
                    todo.countDown();
                    // System.out.print(todo.getCount() + " -- ");
                    // System.out.println(x + " in thread client: " + message + "; "+threadClient.getMessages() + " left");


                }
            }).start();
        }

        todo.await(5, TimeUnit.SECONDS);
    }
*/

    private Message m(String key, String value)
    {
        Message message = new MessageImpl();
        message.put(key, value);
        return message;
    }

    private List<Message> resultsList(Message m1, Message m2)
    {
        return Arrays.asList(new Message[] {m1, m2});
    }

    class StandaloneClient extends ClientImpl
    {
        public StandaloneClient()
        {
            super(new BayeuxStub(), "standalone");
        }

        public StandaloneClient(AbstractBayeux bayeux)
        {
            super(bayeux, "standalone");
        }

        /*
         * Warn for methods that require a proper Bayeux object
         */
        @Override
        public void deliver(Client from, String toChannel, Object data, String id)
        {
            System.err.println("Method unsupported!");
        }

        public void deliver(Message message)
        {
            doDelivery(null, message);
        }

//        @Override
//        public void remove(boolean timeout)
//        {
//            System.err.println("Method unsupported!");
//        }

        @Override
        public void setBrowserId(String id)
        {
            System.err.println("Method unsupported!");
        }
    }

    /*
     * stub out and use to initialize a "standalone" client
     */
    static class BayeuxStub extends AbstractBayeux
    {
        public BayeuxStub()
        {
            try
            {
                _random=SecureRandom.getInstance("SHA1PRNG");
            }
            catch (Exception e)
            {
                _random=new Random();
            }
            // lacks the context hashcode from AbstractBayeux#initialize()
            _random.setSeed(_random.nextLong()^hashCode()^Runtime.getRuntime().freeMemory());
        }

        public ClientImpl newRemoteClient()
        {
            return null;
        }
    }

    static class DeleteWhenFullQueueListener implements QueueListener
    {
        public boolean queueMaxed(Client from, Client client, Message message)
        {
            client.getQueue().poll();
            return true;
        }

    }

    static class DiscardNewMessageQueueListener implements QueueListener
    {
        public boolean queueMaxed(Client from, Client client, Message message)
        {
            return false;
        }

    }

    static class ModifyExistingMessagesQueueListener implements QueueListener
    {
        public boolean queueMaxed(Client from, Client client, Message message)
        {
            Iterator<Message> queueIterator = client.getQueue().iterator();
            boolean removed = false;
            while(queueIterator.hasNext())
            {
                Message m = queueIterator.next();
                if(m.get("delete")!=null)
                {
                    queueIterator.remove();
                    removed=true;
                }
            }

            return removed;
        }
    }

    static class CheckMessageListener implements MessageListener
    {
        Message _message;
        public CheckMessageListener(Message message)
        {
            _message = message;
        }

        public void deliver(Client fromClient, Client toClient, Message msg)
        {
            assertEquals(_message, msg);
        }
    }

    private static class ThrowingMultiListener implements RemoveListener, QueueListener, MessageListener, DeliverListener
    {
        public void removed(String s, boolean b)
        {
            throw new RuntimeException();
        }

        public boolean queueMaxed(Client client, Client client1, Message message)
        {
            throw new RuntimeException();
        }

        public void deliver(Client client, Client client1, Message message)
        {
            throw new RuntimeException();
        }

        public void deliver(Client client, Queue<Message> messages)
        {
            throw new RuntimeException();
        }
    }

    private static class MultiListener implements RemoveListener, QueueListener, MessageListener, DeliverListener
    {
        private boolean removeCalled;
        private boolean queueCalled;
        private boolean messageCalled;
        private boolean deliverCalled;

        public void removed(String s, boolean b)
        {
            removeCalled = true;
        }

        public boolean queueMaxed(Client client, Client client1, Message message)
        {
            queueCalled = true;
            return true;
        }

        public void deliver(Client client, Client client1, Message message)
        {
            messageCalled = true;
        }

        public void deliver(Client client, Queue<Message> messages)
        {
            deliverCalled = true;
        }

        private boolean removeListenerCalled()
        {
            return removeCalled;
        }

        private boolean queueListenerCalled()
        {
            return queueCalled;
        }

        private boolean messageListenerCalled()
        {
            return messageCalled;
        }

        private boolean deliverListenerCalled()
        {
            return deliverCalled;
        }

        public void reset()
        {
            removeCalled = false;
            queueCalled = false;
            messageCalled = false;
            deliverCalled = false;
        }
    }
}
