/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * 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.jivesoftware.smack;

import java.util.Iterator;

import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.test.SmackTestCase;

/**
 * Ensure that the server is delivering messages to the correct client based on the client's
 * presence priority.
 *
 * @author Gaston Dombiak
 */
public class PresenceTest extends SmackTestCase {

    public PresenceTest(String arg0) {
        super(arg0);
    }

    /**
     * XMPPConnection(0) will send messages to the bareJID of XMPPConnection(1) where the user of
     * XMPPConnection(1) has logged from two different places with different presence priorities.
     */
    public void testMessageToHighestPriority() {
        XMPPTCPConnection conn = null;
        try {
            // User_1 will log in again using another resource
            conn = createConnection();
            conn.connect();
            conn.login(getUsername(1), getUsername(1), "OtherPlace");
            // Change the presence priorities of User_1
            getConnection(1).sendStanza(new Presence(Presence.Type.available, null, 1,
                    Presence.Mode.available));
            conn.sendStanza(new Presence(Presence.Type.available, null, 2,
                    Presence.Mode.available));
            Thread.sleep(150);
            // Create the chats between the participants
            Chat chat0 = getConnection(0).getChatManager().createChat(getBareJID(1), null);
            Chat chat1 = getConnection(1).getChatManager().createChat(getBareJID(0), chat0.getThreadID(), null);
            Chat chat2 = conn.getChatManager().createChat(getBareJID(0), chat0.getThreadID(), null);

            // Test delivery of message to the presence with highest priority
            chat0.sendMessage("Hello");
            /*assertNotNull("Resource with highest priority didn't receive the message",
                    chat2.nextMessage(2000));
            assertNull("Resource with lowest priority received the message",
                    chat1.nextMessage(1000));*/

            // Invert the presence priorities of User_1
            getConnection(1).sendStanza(new Presence(Presence.Type.available, null, 2,
                    Presence.Mode.available));
            conn.sendStanza(new Presence(Presence.Type.available, null, 1,
                    Presence.Mode.available));

            Thread.sleep(150);
            // Test delivery of message to the presence with highest priority
            chat0.sendMessage("Hello");
            /*assertNotNull("Resource with highest priority didn't receive the message",
                    chat1.nextMessage(2000));
            assertNull("Resource with lowest priority received the message",
                    chat2.nextMessage(1000));*/

            // User_1 closes his connection
            conn.disconnect();
            Thread.sleep(150);

            // Test delivery of message to the unique presence of the user_1
            chat0.sendMessage("Hello");
            /*assertNotNull("Resource with highest priority didn't receive the message",
                    chat1.nextMessage(2000));*/

            getConnection(1).sendStanza(new Presence(Presence.Type.available, null, 2,
                    Presence.Mode.available));

            // User_1 will log in again using another resource
            conn = createConnection();
            conn.connect();
            conn.login(getUsername(1), getPassword(1), "OtherPlace");
            conn.sendStanza(new Presence(Presence.Type.available, null, 1,
                    Presence.Mode.available));
            chat2 = conn.getChatManager().createChat(getBareJID(0), chat0.getThreadID(), null);

            Thread.sleep(150);
            // Test delivery of message to the presence with highest priority
            chat0.sendMessage("Hello");
            /*assertNotNull("Resource with highest priority didn't receive the message",
                    chat1.nextMessage(2000));
            assertNull("Resource with lowest priority received the message",
                    chat2.nextMessage(1000));*/

            // Invert the presence priorities of User_1
            getConnection(1).sendStanza(new Presence(Presence.Type.available, null, 1,
                    Presence.Mode.available));
            conn.sendStanza(new Presence(Presence.Type.available, null, 2,
                    Presence.Mode.available));

            Thread.sleep(150);
            // Test delivery of message to the presence with highest priority
            chat0.sendMessage("Hello");
            /*assertNotNull("Resource with highest priority didn't receive the message",
                    chat2.nextMessage(2000));
            assertNull("Resource with lowest priority received the message",
                    chat1.nextMessage(1000));*/

        }
        catch (Exception e) {
            e.printStackTrace();
            fail(e.getMessage());
        }
        finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    /**
     * User1 logs from 2 resources but only one is available. User0 sends a message
     * to the full JID of the unavailable resource. User1 in the not available resource
     * should receive the message.
     * TODO Fix this in Wildfire but before check if XMPP spec requests this feature
     */
    public void testNotAvailablePresence() throws XMPPException {
        // Change the presence to unavailable of User_1
        getConnection(1).sendStanza(new Presence(Presence.Type.unavailable));

        // User_1 will log in again using another resource (that is going to be available)
        XMPPTCPConnection conn = createConnection();
        conn.connect();
        conn.login(getUsername(1), getPassword(1), "OtherPlace");

        // Create chats between participants
        Chat chat0 = getConnection(0).getChatManager().createChat(getFullJID(1), null);
        Chat chat1 = getConnection(1).getChatManager().createChat(getBareJID(0), chat0.getThreadID(), null);

        // Test delivery of message to the presence with highest priority
        chat0.sendMessage("Hello");
        /*assertNotNull("Not available connection didn't receive message sent to full JID",
                chat1.nextMessage(2000));
        assertNull("Not available connection received an unknown message",
                chat1.nextMessage(1000));*/
        conn.disconnect();

    }

    /**
     * User1 is connected from 2 resources. User1 adds User0 to his roster. Ensure
     * that presences are correctly retrieved for User1. User1 logs off from one resource
     * and ensure that presences are still correct for User1.
     */
    public void testMultipleResources() throws Exception {
        // Create another connection for the same user of connection 1
        ConnectionConfiguration connectionConfiguration =
                new ConnectionConfiguration(getHost(), getPort(), getXMPPServiceDomain());
        XMPPTCPConnection conn4 = new XMPPConnection(connectionConfiguration);
        conn4.connect();
        conn4.login(getUsername(1), getPassword(1), "Home");

        // Add a new roster entry
        Roster roster = getConnection(0).getRoster();
        roster.createEntry(getBareJID(1), "gato1", null);

        // Wait up to 2 seconds
        long initial = System.currentTimeMillis();
        while (System.currentTimeMillis() - initial < 2000 && (
                !roster.getPresence(getBareJID(1)).isAvailable()))
        {
            Thread.sleep(100);
        }

        // Check that a presence is returned for the new contact
        Presence presence = roster.getPresence(getBareJID(1));
        assertTrue("Returned an offline Presence for an existing user", presence.isAvailable());

        presence = roster.getPresenceResource(getBareJID(1) + "/Home");
        assertTrue("Returned an offline Presence for Home resource", presence.isAvailable());

        presence = roster.getPresenceResource(getFullJID(1));
        assertTrue("Returned an offline Presence for Smack resource", presence.isAvailable());

        Iterator<Presence> presences = roster.getPresences(getBareJID(1));
        assertTrue("Returned an offline Presence for an existing user", presence.isAvailable());
        assertNotNull("No presence was found for user1", presences);
        assertTrue("No presence was found for user1", presences.hasNext());
        presences.next();
        assertTrue("Only one presence was found for user1", presences.hasNext());

        // User1 logs out from one resource
        conn4.disconnect();

        // Wait up to 1 second
        Thread.sleep(700);

        // Check that a presence is returned for the new contact
        presence = roster.getPresence(getBareJID(1));
        assertTrue("Returned a null Presence for an existing user", presence.isAvailable());

        presence = roster.getPresenceResource(getFullJID(1));
        assertTrue("Returned a null Presence for Smack resource", presence.isAvailable());

        presence = roster.getPresenceResource(getBareJID(1) + "/Home");
        assertTrue("Returned a Presence for no longer connected resource", !presence.isAvailable());

        presences = roster.getPresences(getBareJID(1));
        assertNotNull("No presence was found for user1", presences);
        Presence value = presences.next();
        assertTrue("No presence was found for user1", value.isAvailable());
        assertFalse("More than one presence was found for user1", presences.hasNext());
    }

    /**
     * User1 logs in, then sets offline presence information (presence with status text). User2
     * logs in and checks to see if offline presence is returned.
     *
     * @throws Exception if an exception occurs.
     */
    public void testOfflineStatusPresence() throws Exception {
        // Add a new roster entry for other user.
        Roster roster = getConnection(0).getRoster();
        roster.createEntry(getBareJID(1), "gato1", null);

        // Wait up to 2 seconds
        long initial = System.currentTimeMillis();
        while (System.currentTimeMillis() - initial < 2000 && (
                roster.getPresence(getBareJID(1)).getType().equals(Presence.Type.unavailable))) {
            Thread.sleep(100);
        }

        // Sign out of conn1 with status
        Presence offlinePresence = new Presence(Presence.Type.unavailable);
        offlinePresence.setStatus("Offline test");
        getConnection(1).disconnect(offlinePresence);

        // Wait 500 ms
        Thread.sleep(500);
        Presence presence = getConnection(0).getRoster().getPresence(getBareJID(1));
        assertEquals("Offline presence status not received.", "Offline test", presence.getStatus());

        // Sign out of conn0.
        getConnection(0).disconnect();

        // See if conneciton 0 can get offline status.
        XMPPTCPConnection con0 = getConnection(0);
        con0.connect();
        con0.login(getUsername(0), getUsername(0));

        // Wait 500 ms
        Thread.sleep(500);
        presence = con0.getRoster().getPresence(getBareJID(1));
        assertTrue("Offline presence status not received after logout.",
                "Offline test".equals(presence.getStatus()));
    }

    protected int getMaxConnections() {
        return 2;
    }
}