// Reply.java
// $Id: Reply.java,v 1.30 2000/06/08 16:33:52 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigsaw.http ;

import java.io.* ;

import org.w3c.www.mime.*;
import org.w3c.www.http.*;
import org.w3c.tools.resources.*;

public class Reply extends HttpReplyMessage implements ReplyInterface {
    protected static HttpMimeType  DEFAULT_TYPE = null;
    protected static HttpTokenList CONNECTION   = null;

    static {
	String connection[] = { "Keep-Alive" };
	CONNECTION   = HttpFactory.makeStringList(connection);
	DEFAULT_TYPE = HttpFactory.makeMimeType(MimeType.TEXT_HTML);
    }

    InputStream is      = null;
    Client      client  = null;
    boolean     keep    = true;
    Request     request = null;
    private boolean sendBody = true;

    public void setStatus(Integer status) {
	setStatus(status.intValue());
    }

    public boolean hasContentLength() {
	return hasHeader(H_CONTENT_LENGTH);
    }

    public boolean hasContentType() {
	return hasHeader(H_CONTENT_TYPE);
    }

    public void setKeepAlive(String value) {
	setValue("Keep-Alive", value);
    }

    public void setProxyConnection(String value) {
	setValue("Proxy-Connection", value);
    }

    public boolean keepProxyConnection() {
	throw new RuntimeException("keepProxyConnection: not implemented!");
    }

    /**
     * @deprecated
     */

    public FileDescriptor getInputFileDescriptor() {
	return null;
    }
    
    public void setKeepConnection(boolean onoff) {
	this.keep = onoff;
    }

    public boolean tryKeepConnection() {
	if ( ! keep ) {
	    addConnection("close");
	    return false;
	} else if ( major >= 1 ) {
	    if ( minor >= 1 )
		return true;
	    if ( hasContentLength() || (is == null)) {
		if ( is_proxy )
		    addProxyConnection("Keep-Alive");
		else
		    addConnection("Keep-Alive");
		return true;
	    }
	}
	return false;
    }

    /**
     * Is this reply a proxy reply.
     */
    protected boolean is_proxy = false;

    /**
     * Mark this reply as being a proxy reply.
     */

    public void setProxy (boolean onoff) {
	is_proxy = onoff;
    }

    /**
     * Sets the reply stream to the given HtmlGenerator stream.
     * @param g The HtmlGenerator whose output is to be used as the reply body.
     */

    public void setStream (org.w3c.jigsaw.html.HtmlGenerator g) {
	g.close() ;
	setContentLength (g.length()) ;
	setContentType (g.getMimeType()) ;
	if (sendBody)
	    setStream (g.getInputStream()) ;
    }

    public boolean hasStream() {
	return is != null;
    }

    /**
     * Open this reply body stream.
     * This is used to send the reply body back to the client.
     * @return An InputStream containing the reply body, which is dumped
     *    back to the client.
     */

    public InputStream openStream () {
	return is ;
    }

    public void setStream(InputStream is) {
	if (sendBody)
	    this.is = is;
    }

    protected ResourceFilter filters[] = null;
    protected int        infilters = -1;
    protected void setFilters(ResourceFilter filters[], int infilters) {
	this.filters   = filters;
	this.infilters = infilters;
    }

    
    protected OutputStream output = null;

    /**
     * Get the reply output stream.
     * @param doEmit Emit that reply before giving out the output stream.
     * @return An OutputStream instance.
     * @exception IOException If the output stream could not get opened.
     */

    public synchronized OutputStream getOutputStream(boolean doEmit) 
	throws IOException
    {
	if ( output != null )
	    return output;
	// Build the output stream:
	output = client.getOutputStream();
	// Call any filters:
	while ( --infilters >= 0 ) 
	    output = filters[infilters].outputFilter(request, this, output);
	if ( doEmit ) {
	    DataOutputStream dataOutput = new DataOutputStream(output);
	    emit(dataOutput);
	    dataOutput.flush();
	    setStatus(HTTP.DONE);
	}
	// Disable keep-connection:
	keep = false;
	return output;
    }

    /**
     * Get that reply output stream.
     * The reply is first emitted to the stream, and the opened stream 
     * is returned back to the caller.
     * @return An OutputStream instance.
     * @exception IOException If the output stream could not get opened.
     */

    public OutputStream getOutputStream() 
	throws IOException
    {
	return getOutputStream(true);
    }

   /**
     * Should this reply be chunked ?
     * @return If so, the reply should prepare itself to send back the
     *     appropriate transfer encoding header, and return 
     *     <strong>true</strong>, otherwise it should just return
     *     <strong>false</strong>.
     */

    protected Boolean chunkable = null ;
    // FIXME should be an HttpTokenList
    protected static String chunked = "chunked";

    public boolean canChunkTransfer() {
	// Have we already compute this ?
	if ( chunkable == null ) {
	    // Compute wether we can chunk the reply:
	    if ( hasContentLength() || (is == null)) {
		chunkable = Boolean.FALSE ;
	    } else if ((major >= 1) && (minor >= 1)) {
		String connections[] = getConnection();
		chunkable = Boolean.TRUE ;
		if (connections != null) {
		    for (int i = 0; i< connections.length; i++) {
			if (connections[i].equalsIgnoreCase("close")) {
			    chunkable = Boolean.FALSE;
			}
		    }
		}
		if (chunkable == Boolean.TRUE)
		    addTransferEncoding(chunked);
	    } else {
		chunkable = Boolean.FALSE ;
	    }
	}
	return (chunkable == Boolean.TRUE) ;
    }

    /**
     * Set this reply content.
     * This method allows to set the reply content to a simple String instance.
     * @param msg The reply content.
     * @param encoding, the encoding of the reply
     */
    public void setContent (String msg, String encoding) {
	if ( ! hasContentType() )
	    setHeaderValue(H_CONTENT_TYPE, DEFAULT_TYPE) ;
	byte byteBuffer[];
	try {
	    byteBuffer = msg.getBytes(encoding) ;
	} catch (UnsupportedEncodingException ex) {
	    throw new RuntimeException (this.getClass().getName() + 
					"[setContent] Unable to convert" +
					"properly char to bytes");
	}
	setContentLength (byteBuffer.length) ;
	if (sendBody) {
	    is = new ByteArrayInputStream (byteBuffer) ;
	}
    }

    /**
     * Set this reply content.
     * This method allows to set the reply content to a simple String instance.
     * encoding will be by default "ISO8859_1"
     * @param msg The reply content.
     */
    public void setContent (String msg) {
	setContent(msg, "ISO-8859-1");
    }
    
    /**
     * Create a new Reply instance for the given client.
     * @param client The client to who this reply  is directed.
     */

    public Reply (Client client) {
	this.client = client ;
    }

    /**
     * Create a new reply for the given client.
     * @param client The client ot who the reply is directed.
     * @reply status The reply status code.
     */

    public Reply(Client client, Request request, short major, short minor,
		 int status) {
	this (client) ;
	this.request = request;
	this.major   = major;
	this.minor   = minor;
	this.keep    = true;
	this.setServer((client != null) 
		       ? client.getServer().getSoftware()
		       : null);
	this.setStatus (status);
	if (request != null)
	    if (request.getMethod().endsWith("HEAD")) 
		sendBody = false;
    }
}
