// Reply.java
// $Id: Reply.java,v 1.18 2003/01/06 17:11:20 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.www.protocol.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringBufferInputStream;

import org.w3c.www.mime.MimeParser;
import org.w3c.www.mime.MimeType;

import org.w3c.www.http.ChunkedInputStream;
import org.w3c.www.http.ContentLengthInputStream;
import org.w3c.www.http.HTTP;
import org.w3c.www.http.HttpEntityMessage;
import org.w3c.www.http.HttpFactory;
import org.w3c.www.http.HttpMessage;
import org.w3c.www.http.HttpMimeType;
import org.w3c.www.http.HttpReplyMessage;
import org.w3c.www.http.HttpRequestMessage;
import org.w3c.www.http.HttpStreamObserver;

public class Reply extends HttpReplyMessage {
    protected static HttpMimeType  DEFAULT_TYPE = null;

    static {
	DEFAULT_TYPE = HttpFactory.makeMimeType(MimeType.TEXT_HTML);
    }

    MimeParser parser = null;
    HttpStreamObserver observer = null;

    public boolean keepsAlive() {
	if ( major >= 1 ) {
	    // HTTP/1.1 is quite cool, in some sense
  	    if ( minor >= 1 ) 
	        return ((getContentLength() >= 0) 
                         || hasTransferEncoding("chunked"));
	    // HTTP/1.0 tries to be as cool, with no success
	    return (hasConnection("keep-alive") 
		    || hasProxyConnection("keep-alive"));
	}
	return false;
    }

    /**
     * Set an stream observer on the reply entity stream.
     * This method should be called <em>before</em> any caller gets
     * a chance to execute <code>getInputStream</code>. It is needed
     * for HttpServer instances to be notified when the stream becomes
     * available for the next request.
     */

    protected void setStreamObserver(HttpStreamObserver observer) {
	this.observer = observer;
    }

    /**
     * Notify this reply that is has been built to answer given request.
     * Perform has many ugly hack HTTP/1.1 requires. 
     * @param request The request that is answered by this reply.
     */

    protected void matchesRequest(Request request) {
	String mth = request.getMethod();
	if ( mth.equals("HEAD") /* || mth.equals("OPTIONS") */)
	    setStream(null);
    }

    protected InputStream input        = null;
    protected boolean     definesInput = false;

    /**
     * Set this reply's input stream.
     * @param input The stream to read the reply's entity from.
     */

    public void setStream(InputStream input) {
	this.input        = input;
	this.definesInput = true;
    }

    /**
     * Get this reply entity body.
     * The reply entity body is returned as an InputStream, that the caller
     * has to read to actually get the bytes of the content.
     * @return An InputStream instance. If the reply has no body, the 
     * returned input stream will just return <strong>-1</strong> on
     * first read.
     */

    public InputStream getInputStream()
	throws IOException
    {
	if ( definesInput )
	    return input;
	// Check the status code, there are some special cases floating around
	switch(getStatus()) {
	  case HTTP.NOT_MODIFIED:
	  case HTTP.NO_CONTENT:
	      return null;
	}
	// First, do we have chunked encoding:
	if ( hasTransferEncoding("chunked") ) {
	    definesInput = true;
	    input = new ChunkedInputStream(observer, parser.getInputStream());
	    return input;
	}
	// Find out if there is a content length
	int len = getContentLength();
	if ( len >= 0 ) {
	    input = new ContentLengthInputStream(observer
						 , parser.getInputStream()
						 , len);
	    definesInput = true;
	    return input;
	}
	// Everything has failed, we assume the connection will close:
	if ( observer != null )
	    observer.notifyFailure(parser.getInputStream());
	return parser.getInputStream();
    }

    /**
     * Does this reply has an associated entity stream ?
     * @return A boolean, <strong>true</strong> if the reply has an entity
     * stream, <strong>false</strong> otherwise.
     */

    public boolean hasInputStream() 
	throws IOException
    {
	return getInputStream() != null;
    }

    /**
     * Set this reply content.
     * This method allows to set the reply content to a simple String instance.
     * @param msg The reply content.
     */

    public void setContent (String msg) {
	if ( ! hasHeader(H_CONTENT_TYPE) )
	    setHeaderValue(H_CONTENT_TYPE, DEFAULT_TYPE) ;
	setContentLength (msg.length()) ;
	setStream(new StringBufferInputStream(msg));
    }

    protected Reply(short major, short minor, int status) {
	this.major = major;
	this.minor = minor;
	this.setStatus(status);
    }

    protected Reply(MimeParser parser) {
	super(parser);
	this.parser = parser;
    }

}
