// Reply.java
// $Id: Reply.java,v 1.38 2003/02/25 17:52:01 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.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import org.w3c.www.mime.MimeType;

import org.w3c.www.http.HTTP;
import org.w3c.www.http.HeaderValue;
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.HttpTokenList;

import org.w3c.tools.resources.ReplyInterface;
import org.w3c.tools.resources.RequestInterface;
import org.w3c.tools.resources.ResourceFilter;

public class Reply extends HttpReplyMessage implements ReplyInterface {
    protected static HttpMimeType  DEFAULT_TYPE = null;
    protected static HttpTokenList CONNECTION   = null;
    
    private static String ka = "Keep-Alive";
    private static String pc = "Proxy-Connection";
    private static String cl = "close";

    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;
    private boolean dynamic = false;

    /**
     * set the reply to be a reply for dynamic content
     * @param a boolean, true if the reply is generated by a dynamic
     */
    public void setDynamic(boolean dyn) {
	dynamic = dyn;
    }

    /**
     * is is dynamic or not?
     */
    public boolean isDynamic() {
	return dynamic;
    }
    
    public void setStatus(Integer status) {
	setStatus(status.intValue());
    }

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

    public boolean hasContentType() {
	return  ( hasHeader(H_CONTENT_TYPE) && 
		  ( getHeaderValue(H_CONTENT_TYPE).getValue() != null));
    }

    public void setKeepAlive(String value) {
	setValue(ka, value);
    }

    public void setProxyConnection(String value) {
	setValue(pc, 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(cl);
	    return false;
	} else if ( major >= 1 ) {
	    if ( minor >= 1 )
		return true;
	    if ( hasContentLength() || (is == null)) {
		if ( is_proxy )
		    addProxyConnection(ka);
		else
		    addConnection(ka);
		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(), true) ;
    }

    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) {
	setStream(is, false);
    }

    public synchronized void setStream(InputStream is, boolean closeOld) {
	if (sendBody) {
	    if (closeOld && (this.is != null)) {
		try {
		    this.is.close();
		} catch (IOException ioex) {};
	    }
	    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) {
	    ByteArrayInputStream bis = new ByteArrayInputStream (byteBuffer) ;
	    setStream(bis, true);
	}
    }

    // FIXME the bug fix should be in HttpMessage.hasHeader, but it would
    // be more time consuming, as it would add checks for all headers
    // and only Content-Type seems to be problematic

    /**
     * @param out The output stream to emit the message to. 
     * @param what (fixme doc)
     * @exception IOException If the message couldn't be emited to the 
     * given stream, due to IO errors. 
     */ 
    public void emit(OutputStream out, int what) 
	throws IOException
    {
	int status = getStatus();
	if (!hasContentType() && ((status != HTTP.CONTINUE) ||
				  (status != HTTP.SWITCHING)))  {
	    setHeaderValue(H_CONTENT_TYPE, DEFAULT_TYPE) ;
	}
	super.emit(out, what);
    }
    
    public void dump(OutputStream out) {
	if (!hasContentType()) {
	    setHeaderValue(H_CONTENT_TYPE, DEFAULT_TYPE) ;
	}
	super.dump(out);
	}   

    /**
     * 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");
    }

    /**
     * Get the entity MIME type.
     * @return An HttpMimeType object describing the entity's type, or 
     * <strong>null</strong> if udefined.
     */
    public MimeType getContentType() {
	MimeType mt = super.getContentType();
	if (mt == null) {
	    return MimeType.APPLICATION_OCTET_STREAM;
	} else {
	    return mt;
	}
    }

    /**
     * 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(HTTP.HEAD)) 
		sendBody = false;
    }
}
