// TidyPutFilter.java
// $Id: TidyPutFilter.java,v 1.15 2003/01/27 16:14:54 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 2002.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigedit.filters;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Writer;
import java.io.PrintWriter;

import org.w3c.tools.resources.Attribute;
import org.w3c.tools.resources.AttributeRegistry;
import org.w3c.tools.resources.FramedResource;
import org.w3c.tools.resources.InvalidResourceException;
import org.w3c.tools.resources.ProtocolException;
import org.w3c.tools.resources.ReplyInterface;
import org.w3c.tools.resources.RequestInterface;
import org.w3c.tools.resources.Resource;
import org.w3c.tools.resources.ResourceFilter;
import org.w3c.tools.resources.ResourceReference;
import org.w3c.tools.resources.BooleanAttribute;

import org.w3c.www.mime.MimeType;

import org.w3c.www.http.HTTP;
import org.w3c.www.http.HttpEntityMessage;
import org.w3c.www.http.HttpReplyMessage;
import org.w3c.www.http.HttpRequestMessage;
import org.w3c.www.http.HttpWarning;
import org.w3c.www.http.HttpFactory;


import org.w3c.jigsaw.http.Client;
import org.w3c.jigsaw.http.Reply;
import org.w3c.jigsaw.http.Request;

import org.w3c.jigsaw.html.HtmlGenerator;

import org.w3c.jigsaw.frames.HTTPFrame;

import org.w3c.tidy.Tidy;
import org.w3c.tidy.Configuration;


public class TidyPutFilter extends ResourceFilter {

	
    /** attribute index */
    protected static int ATTR_VALID_STRICT = -1;
    
    static {
	Class     c = null;
	Attribute a = null;

	try {
	    c = Class.forName("org.w3c.jigedit.filters.TidyPutFilter");
	} catch (Exception ex) {
	    ex.printStackTrace();
	    System.exit(1);
	}

	a = new BooleanAttribute("valid-strict"
				 , Boolean.FALSE
				 , Attribute.EDITABLE); 
	ATTR_VALID_STRICT = AttributeRegistry.registerAttribute(c, a); 
    }

    HttpWarning hw = null;
    MimeType xhtml_mt = null;
   
   boolean isXhtml = false ;
   int tidyCharEncoding = Configuration.LATIN1;

    protected boolean getValidStrict(){
	return getBoolean(ATTR_VALID_STRICT, false);
    }
	

    public ReplyInterface ingoingFilter(RequestInterface req) {
	Request request = (Request) req;

	if(request.getMethod().equals("PUT")) {
	    try {
		MimeType req_mt = request.getContentType();
		if (xhtml_mt == null){
		    xhtml_mt = new MimeType("application","xhtml+xml");
		}
		if (req_mt.match(xhtml_mt) != MimeType.NO_MATCH) {
		    isXhtml = true ;
		} else {
		    isXhtml = false ;
		}
		if (req_mt.match(MimeType.TEXT_HTML) == MimeType.NO_MATCH
		    && isXhtml == false){
		    return null;
		}
		if (req_mt.hasParameter("charset")){
		    String charset = req_mt.getParameterValue("charset"); 
		    if (charset.equalsIgnoreCase("iso-2022-jp")){
			tidyCharEncoding = Configuration.ISO2022 ;
		    }
		    if (charset.equalsIgnoreCase("us-ascii")){
			tidyCharEncoding = Configuration.ASCII ;
		    }
		    if (charset.equalsIgnoreCase("iso-8859-1")){
			tidyCharEncoding = Configuration.LATIN1 ;
		    }
		    if (charset.equalsIgnoreCase("utf8")){
			tidyCharEncoding = Configuration.UTF8 ;
		    }
		    if (charset.equalsIgnoreCase("macroman")){
			tidyCharEncoding = Configuration.MACROMAN ;
		    }
		}
	    } catch (NullPointerException ex) {
		// no Content-Type sent! check anyway
	    }
	    InputStream in = null;
	    try {
		in = request.getInputStream();
		if ( in == null ) {
		    return null;
		}
	    } catch (IOException ex) {
		return null;
	    }

	    // verify that the target resource is putable
	    ResourceReference rr = request.getTargetResource();
	    if (rr != null) {
		try {
		    FramedResource target = (FramedResource) rr.lock();
		    HTTPFrame frame = null;
		    try {
			frame = (HTTPFrame) target.getFrame( 
			    Class.forName("org.w3c.jigsaw.frames.HTTPFrame"));
		    } catch (ClassNotFoundException cex) {
			cex.printStackTrace();
				//big big problem ...
		    }
		    if (frame == null) // can't be putable
			return null;
		    // now we can verify if the target resource is putable
		    if (! frame.getPutableFlag()) {
			return null;
		    }
		    // and that the PUT can happen (taken from putFileResource
		    int cim = frame.checkIfMatch(request);
		    if ((cim == HTTPFrame.COND_FAILED)
			|| (cim == HTTPFrame.COND_WEAK)
			|| (frame.checkIfNoneMatch(request) == 
			    HTTPFrame.COND_FAILED)
			|| (frame.checkIfModifiedSince(request) == 
			    HTTPFrame.COND_FAILED)
			|| (frame.checkIfUnmodifiedSince(request) == 
			    HTTPFrame.COND_FAILED)) {
			
			Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
			r.setContent("Pre-condition failed.");
			return r;
		    }
		} catch (InvalidResourceException ex) {
		    ex.printStackTrace();
		    // problem ...
		} finally {
		    rr.unlock();
		}
	    }

	    String expect = request.getExpect();
	    if (expect != null) {
		if (expect.startsWith("100")) { // expect 100?
		    Client client = request.getClient();
		    if (client != null) {
			try {
			    client.sendContinue();
			} catch (java.io.IOException ex) {
			    return null;
			}
		    }
		}
	    }

	    try {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		Tidy tidy = new Tidy();
		if (isXhtml) {
		    tidy.setXHTML(true);
		} else {
		    tidy.setXHTML(false);
		}
		tidy.setErrout(new PrintWriter(System.err));
		in.mark(65536000);


//		System.out.println(tidyCharEncoding);
		tidy.setCharEncoding(tidyCharEncoding);
/*		tidy.setIndentContent(false);
		tidy.setIndentAttributes(false);
		tidy.setSmartIndent(false);
*/
		tidy.parse(in,bout);

		if (tidy.getParseErrors() != 0){
		    //System.out.println("too many errors, bailing out") ;
		    in.reset();
		}

		// Need to call tidy in other thread, because of Piped streams.
		
		byte[] bufout = bout.toByteArray();
	
		ByteArrayInputStream tmpbin = new ByteArrayInputStream(bufout);

		request.setContentLength(bufout.length);
		if (bufout.length != 0 ) {
		    // tidy is happy let's apply transformation
		    request.setStream(tmpbin);
		    // add state to set warnings on the way back
		    request.setState("tidy", "ok");
		} else {
		    if (getValidStrict() == true ){
			// tidy failed and a file MUST validate -> refuse put
			Reply reply = request.makeReply(HTTP.FORBIDDEN) ;
			HtmlGenerator g = new HtmlGenerator("Not Acceptable");
			g.append("<p>This HTML code does not validate. Valid"
				 + "code is required here</p>"
				 + "<p>Warnings: " + tidy.getParseWarnings() 
				   + " Errors: "+tidy.getParseErrors()+"</p>");

			reply.setStream(g);
			return reply;
		    } else {
			return null;
		    }
		}
	    } catch (Exception ex){
		ex.printStackTrace();
		// problem
	    }
	    return null;
	} else {
	    return null;
	}
    }

    /**
     * @param request The original request.
     * @param reply It's original reply. 
     * @return A Reply instance, or <strong>null</strong> if processing 
     * should continue normally. 
     * @exception ProtocolException If processing should be interrupted,  
     * because an abnormal situation occured. 
     */
    public ReplyInterface outgoingFilter(RequestInterface req,
					 ReplyInterface rep) 
	throws ProtocolException
    {
	Request request = (Request) req;
	Reply   reply   = (Reply) rep;
	
	if (request.hasState("tidy")) {
	    if (hw == null) {
		hw = 
		  HttpFactory.makeWarning(HttpWarning.TRANSFORMATION_APPLIED);
		hw.setAgent("Jigsaw");
		hw.setText("Body modified for HTML conformance using JTidy");
		reply.addWarning(hw);
	    }
	}
	return null;
    }
}
