/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. 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.smackx.packet;

import org.jivesoftware.smack.packet.PacketExtension;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * Represents message events relating to the delivery, display, composition and cancellation of
 * messages.<p>
 * <p/>
 * There are four message events currently defined in this namespace:
 * <ol>
 * <li>Offline<br>
 * Indicates that the message has been stored offline by the intended recipient's server. This
 * event is triggered only if the intended recipient's server supports offline storage, has that
 * support enabled, and the recipient is offline when the server receives the message for delivery.</li>
 * <p/>
 * <li>Delivered<br>
 * Indicates that the message has been delivered to the recipient. This signifies that the message
 * has reached the recipient's XMPP client, but does not necessarily mean that the message has
 * been displayed. This event is to be raised by the XMPP client.</li>
 * <p/>
 * <li>Displayed<br>
 * Once the message has been received by the recipient's XMPP client, it may be displayed to the
 * user. This event indicates that the message has been displayed, and is to be raised by the
 * XMPP client. Even if a message is displayed multiple times, this event should be raised only
 * once.</li>
 * <p/>
 * <li>Composing<br>
 * In threaded chat conversations, this indicates that the recipient is composing a reply to a
 * message. The event is to be raised by the recipient's XMPP client. A XMPP client is allowed
 * to raise this event multiple times in response to the same request, providing the original
 * event is cancelled first.</li>
 * </ol>
 *
 * @author Gaston Dombiak
 */
public class MessageEvent implements PacketExtension {

    public static final String OFFLINE = "offline";
    public static final String COMPOSING = "composing";
    public static final String DISPLAYED = "displayed";
    public static final String DELIVERED = "delivered";
    public static final String CANCELLED = "cancelled";

    private boolean offline = false;
    private boolean delivered = false;
    private boolean displayed = false;
    private boolean composing = false;
    private boolean cancelled = true;

    private String packetID = null;

    /**
     * Returns the XML element name of the extension sub-packet root element.
     * Always returns "x"
     *
     * @return the XML element name of the packet extension.
     */
    public String getElementName() {
        return "x";
    }

    /**
     * Returns the XML namespace of the extension sub-packet root element.
     * According the specification the namespace is always "jabber:x:event"
     *
     * @return the XML namespace of the packet extension.
     */
    public String getNamespace() {
        return "jabber:x:event";
    }

    /**
     * When the message is a request returns if the sender of the message requests to be notified
     * when the receiver is composing a reply.
     * When the message is a notification returns if the receiver of the message is composing a
     * reply.
     *
     * @return true if the sender is requesting to be notified when composing or when notifying
     * that the receiver of the message is composing a reply
     */
    public boolean isComposing() {
        return composing;
    }

    /**
     * When the message is a request returns if the sender of the message requests to be notified
     * when the message is delivered.
     * When the message is a notification returns if the message was delivered or not.
     *
     * @return true if the sender is requesting to be notified when delivered or when notifying
     * that the message was delivered
     */
    public boolean isDelivered() {
        return delivered;
    }

    /**
     * When the message is a request returns if the sender of the message requests to be notified
     * when the message is displayed.
     * When the message is a notification returns if the message was displayed or not.
     *
     * @return true if the sender is requesting to be notified when displayed or when notifying
     * that the message was displayed
     */
    public boolean isDisplayed() {
        return displayed;
    }

    /**
     * When the message is a request returns if the sender of the message requests to be notified
     * when the receiver of the message is offline.
     * When the message is a notification returns if the receiver of the message was offline.
     *
     * @return true if the sender is requesting to be notified when offline or when notifying
     * that the receiver of the message is offline
     */
    public boolean isOffline() {
        return offline;
    }

    /**
     * When the message is a notification returns if the receiver of the message cancelled
     * composing a reply.
     *
     * @return true if the receiver of the message cancelled composing a reply
     */
    public boolean isCancelled() {
        return cancelled;
    }

    /**
     * Returns the unique ID of the message that requested to be notified of the event.
     * The packet id is not used when the message is a request for notifications
     *
     * @return the message id that requested to be notified of the event.
     */
    public String getPacketID() {
        return packetID;
    }

    /**
     * Returns the types of events. The type of event could be:
     * "offline", "composing","delivered","displayed", "offline"
     *
     * @return an iterator over all the types of events of the MessageEvent.
     */
    public Iterator getEventTypes() {
        ArrayList allEvents = new ArrayList();
        if (isDelivered()) {
            allEvents.add(MessageEvent.DELIVERED);
        }
        if (!isMessageEventRequest() && isCancelled()) {
            allEvents.add(MessageEvent.CANCELLED);
        }
        if (isComposing()) {
            allEvents.add(MessageEvent.COMPOSING);
        }
        if (isDisplayed()) {
            allEvents.add(MessageEvent.DISPLAYED);
        }
        if (isOffline()) {
            allEvents.add(MessageEvent.OFFLINE);
        }
        return allEvents.iterator();
    }

    /**
     * When the message is a request sets if the sender of the message requests to be notified
     * when the receiver is composing a reply.
     * When the message is a notification sets if the receiver of the message is composing a
     * reply.
     *
     * @param composing sets if the sender is requesting to be notified when composing or when
     *                  notifying that the receiver of the message is composing a reply
     */
    public void setComposing(boolean composing) {
        this.composing = composing;
        setCancelled(false);
    }

    /**
     * When the message is a request sets if the sender of the message requests to be notified
     * when the message is delivered.
     * When the message is a notification sets if the message was delivered or not.
     *
     * @param delivered sets if the sender is requesting to be notified when delivered or when
     *                  notifying that the message was delivered
     */
    public void setDelivered(boolean delivered) {
        this.delivered = delivered;
        setCancelled(false);
    }

    /**
     * When the message is a request sets if the sender of the message requests to be notified
     * when the message is displayed.
     * When the message is a notification sets if the message was displayed or not.
     *
     * @param displayed sets if the sender is requesting to be notified when displayed or when
     *                  notifying that the message was displayed
     */
    public void setDisplayed(boolean displayed) {
        this.displayed = displayed;
        setCancelled(false);
    }

    /**
     * When the message is a request sets if the sender of the message requests to be notified
     * when the receiver of the message is offline.
     * When the message is a notification sets if the receiver of the message was offline.
     *
     * @param offline sets if the sender is requesting to be notified when offline or when
     *                notifying that the receiver of the message is offline
     */
    public void setOffline(boolean offline) {
        this.offline = offline;
        setCancelled(false);
    }

    /**
     * When the message is a notification sets if the receiver of the message cancelled
     * composing a reply.
     * The Cancelled event is never requested explicitly. It is requested implicitly when
     * requesting to be notified of the Composing event.
     *
     * @param cancelled sets if the receiver of the message cancelled composing a reply
     */
    public void setCancelled(boolean cancelled) {
        this.cancelled = cancelled;
    }

    /**
     * Sets the unique ID of the message that requested to be notified of the event.
     * The packet id is not used when the message is a request for notifications
     *
     * @param packetID the message id that requested to be notified of the event.
     */
    public void setPacketID(String packetID) {
        this.packetID = packetID;
    }

    /**
     * Returns true if this MessageEvent is a request for notifications.
     * Returns false if this MessageEvent is a notification of an event.
     *
     * @return true if this message is a request for notifications.
     */
    public boolean isMessageEventRequest() {
        return this.packetID == null;
    }

    /**
     * Returns the XML representation of a Message Event according the specification.
     * <p/>
     * Usually the XML representation will be inside of a Message XML representation like
     * in the following examples:<p>
     * <p/>
     * Request to be notified when displayed:
     * <pre>
     * &lt;message
     *    to='romeo@montague.net/orchard'
     *    from='juliet@capulet.com/balcony'
     *    id='message22'&gt;
     * &lt;x xmlns='jabber:x:event'&gt;
     *   &lt;displayed/&gt;
     * &lt;/x&gt;
     * &lt;/message&gt;
     * </pre>
     * <p/>
     * Notification of displayed:
     * <pre>
     * &lt;message
     *    from='romeo@montague.net/orchard'
     *    to='juliet@capulet.com/balcony'&gt;
     * &lt;x xmlns='jabber:x:event'&gt;
     *   &lt;displayed/&gt;
     *   &lt;id&gt;message22&lt;/id&gt;
     * &lt;/x&gt;
     * &lt;/message&gt;
     * </pre>
     */
    public String toXML() {
        StringBuilder buf = new StringBuilder();
        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
                "\">");
        // Note: Cancellation events don't specify any tag. They just send the packetID

        // Add the offline tag if the sender requests to be notified of offline events or if 
        // the target is offline
        if (isOffline())
            buf.append("<").append(MessageEvent.OFFLINE).append("/>");
        // Add the delivered tag if the sender requests to be notified when the message is 
        // delivered or if the target notifies that the message has been delivered
        if (isDelivered())
            buf.append("<").append(MessageEvent.DELIVERED).append("/>");
        // Add the displayed tag if the sender requests to be notified when the message is 
        // displayed or if the target notifies that the message has been displayed
        if (isDisplayed())
            buf.append("<").append(MessageEvent.DISPLAYED).append("/>");
        // Add the composing tag if the sender requests to be notified when the target is 
        // composing a reply or if the target notifies that he/she is composing a reply
        if (isComposing())
            buf.append("<").append(MessageEvent.COMPOSING).append("/>");
        // Add the id tag only if the MessageEvent is a notification message (not a request)
        if (getPacketID() != null)
            buf.append("<id>").append(getPacketID()).append("</id>");
        buf.append("</").append(getElementName()).append(">");
        return buf.toString();
    }

}
