/**
 * $RCSfile:  $
 * $Revision:  $
 * $Date:  $
 *
 * Copyright (C) 2006 Jive Software. All rights reserved.
 * This software is the proprietary information of Jive Software. Use is subject to license terms.
 */
package org.jivesoftware.wildfire.http;

import org.jivesoftware.wildfire.Connection;
import org.mortbay.util.ajax.Continuation;


/**
 * A connection to a client. The client will wait on getDeliverable() until the server forwards a
 * message to it or the wait time on the session timesout.
 *
 * @author Alexander Wenckus
 */
public class HttpConnection {
    private Connection.CompressionPolicy compressionPolicy;
    private long requestId;
    private String body;
    private HttpSession session;
    private Continuation continuation;
    private boolean isClosed;
    private boolean isSecure = false;

    public HttpConnection(long requestId, boolean isSecure) {
        this.requestId = requestId;
        this.isSecure = isSecure;
    }

    public boolean validate() {
        return false;
    }

    /**
     * The connection should be closed without delivering a stanza to the requestor.
     */
    public void close() {
        if (isClosed) {
            return;
        }

        try {
            deliverBody(null);
        }
        catch (HttpConnectionClosedException e) {
            /* Shouldn't happen */
        }
    }

    public boolean isClosed() {
        return isClosed;
    }

    public boolean isSecure() {
        return isSecure;
    }

    /**
     * Delivers content to the client. The content should be valid XMPP wrapped inside of a body.
     * A <i>null</i> value for body indicates that the connection should be closed and the client
     * sent an empty body.
     *
     * @param body the XMPP content to be forwarded to the client inside of a body tag.
     *
     * @throws HttpConnectionClosedException when this connection to the client has already recieved
     * a deliverable to forward to the client
     */
    public void deliverBody(String body) throws HttpConnectionClosedException {
        // We only want to use this function once so we will close it when the body is delivered.
        if (isClosed) {
            throw new HttpConnectionClosedException("The http connection is no longer " +
                    "available to deliver content");
        }
        else {
            isClosed = true;
        }

        if (continuation != null) {
            continuation.setObject(body);
            continuation.resume();
        }
        else {
            this.body = body;
        }
    }

    /**
     * A call that will cause a wait, or in the case of Jetty the thread to be freed, if there is no
     * deliverable currently available. Once the deliverable becomes available it is returned.
     *
     * @return the deliverable to send to the client
     * @throws HttpBindTimeoutException to indicate that the maximum wait time requested by the
     * client has been surpassed and an empty response should be returned.
     */
    public String getDeliverable() throws HttpBindTimeoutException {
        if (body == null && continuation != null) {
            try {
                body = waitForDeliverable();
            }
            catch (HttpBindTimeoutException e) {
                this.isClosed = true;
                throw e;
            }
        }
        else if (body == null) {
            throw new IllegalStateException("Continuation not set, cannot wait for deliverable.");
        }
        return body;
    }

    private String waitForDeliverable() throws HttpBindTimeoutException {
        if (continuation.suspend(session.getWait() * 1000)) {
            String deliverable = (String) continuation.getObject();
            // This will occur when the hold attribute of a session has been exceded.
            if (deliverable == null) {
                throw new HttpBindTimeoutException();
            }
            return deliverable;
        }
        throw new HttpBindTimeoutException("Request " + requestId + " exceded response time from " +
                "server of " + session.getWait() + " seconds.");
    }

    public boolean isCompressed() {
        return false;
    }

    public Connection.CompressionPolicy getCompressionPolicy() {
        return compressionPolicy;
    }

    public void setCompressionPolicy(Connection.CompressionPolicy compressionPolicy) {
        this.compressionPolicy = compressionPolicy;
    }

    public long getRequestId() {
        return requestId;
    }

    /**
     * Set the session that this connection belongs to.
     *
     * @param session the session that this connection belongs to.
     */
    void setSession(HttpSession session) {
        this.session = session;
    }

    /**
     * Returns the session that this connection belongs to.
     *
     * @return the session that this connection belongs to.
     */
    public HttpSession getSession() {
        return session;
    }

    void setContinuation(Continuation continuation) {
        this.continuation = continuation;
    }
}