package rabbit.proxy;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import rabbit.http.HttpDateParser;
import rabbit.http.HttpHeader;
import rabbit.util.Config;

/** A class that can create standard response headers. 
 *
 * @author <a href="mailto:robo@khelekore.org">Robert Olofsson</a>
 */
class StandardResponseHeaders implements HttpGenerator {
    /** The identity of the server. */
    private String serverIdentity;
    /** The connection handling the response. */
    private Connection con;

    public StandardResponseHeaders (String serverIdentity, Connection con) {
	this.serverIdentity = serverIdentity;
	this.con = con;
    }

    /** Get a new HttpHeader. This is the same as 
     * getHeader ("HTTP/1.0 200 OK");
     * @return a new HttpHeader.
     */
    public HttpHeader getHeader () {
	return getHeader ("HTTP/1.1 200 OK");
    }
    
    /** Get a new HttpHeader initialized with some data.
     * @param statusLine the statusline of the response.
     * @return a new HttpHeader.
     */
    public HttpHeader getHeader (String statusLine) {
	HttpHeader ret = new HttpHeader ();
	ret.setStatusLine (statusLine);
	ret.setHeader ("Server", serverIdentity);
	ret.setHeader ("Content-type", "text/html");
	ret.setHeader ("Pragma", "no-cache");
	ret.setHeader ("Date", HttpDateParser.getDateString (new Date ()));
	return ret;    
    }

    /** Get a 200 Ok header
     * @return a 200 HttpHeader .
     */
    public HttpHeader get200 () {
	HttpHeader header = getHeader ("HTTP/1.1 200 Ok");  
	return header;
    }

    private void copyHeaderIfExists (String type, 
				     HttpHeader from, HttpHeader to) {
	String d = from.getHeader (type);
	if (d != null)
	    to.setHeader (type, d);	
    }

    public HttpHeader get206 (String ifRange, HttpHeader header) {
	HttpHeader ret = new HttpHeader ();
	ret.setStatusLine ("HTTP/1.1 206 Partial Content");
	boolean tiny = ifRange != null;
	if (tiny) {
	    String etag = header.getHeader ("ETag");
	    if (etag != null && con.checkStrongEtag (ifRange, etag))
		tiny = false;
	}
	if (tiny) {
	    copyHeaderIfExists ("Date", header, ret);
	    copyHeaderIfExists ("ETag", header, ret);
	    copyHeaderIfExists ("Content-Location", header, ret);	    
	    //copyHeaderIfExists ("Expires", header, ret);
	    /* should do this also in certain conditions...
	       copyHeadersIfExists ("Cache-Control", header, ret);
	       copyHeadersIfExists ("Vary", header, ret);
	    */
	} else {
	    header.copyHeader (ret);
	}
	return ret;
    }    

    /** Get a 304 Not Modified header for the given old header
     * @param oldresp the cached header.
     * @return a 304 HttpHeader .
     */
    public HttpHeader get304 (HttpHeader oldresp) {
	HttpHeader header = getHeader ("HTTP/1.1 304 Not Modified");  
	copyHeaderIfExists ("Date", oldresp, header);
	copyHeaderIfExists ("Content-Location", oldresp, header);
	copyHeaderIfExists ("ETag", oldresp, header);
	String etag = header.getHeader ("Etag");
	if (etag != null && !con.isWeak (etag))
	    copyHeaderIfExists ("Expires", oldresp, header);
	List<String> ccs = oldresp.getHeaders ("Cache-Control");
	for (int i = 0, s = ccs.size (); i < s; i++) 
	    header.addHeader ("Cache-Control", ccs.get (i));
	ccs = oldresp.getHeaders ("Vary");
	for (int i = 0, s = ccs.size (); i < s; i++) 
	    header.addHeader ("Vary", ccs.get (i));	
	return header;
    }

    /** Get a 400 Bad Request header for the given exception.
     * @param exception the Exception handled.
     * @return a HttpHeader for the exception.
     */
    public HttpHeader get400 (Exception exception) {
	// in most cases we should have a header out already, but to be sure...
	HttpHeader header = getHeader ("HTTP/1.1 400 Bad Request ");  
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, "400 Bad Request") +
			       "Unable to handle request:<br><b><XMP>\n" + 
			       exception +
			       "</XMP></b></body></html>\n");
	header.setContent (accreq.toString ());
	return header;
    }

    /** Get a 401 Authentication Required for the given realm and url.
     * @param realm the realm that requires auth.
     * @param url the URL of the request made.
     * @return a suitable HttpHeader.
     */
    public HttpHeader get401 (String realm, URL url) {
	return getAuthorizationHeader (realm, url, 401, 
				       "Authentication Required", "WWW");
    }

    private HttpHeader getAuthorizationHeader (String realm, URL url, int num,
					       String auth, String type) {
	String na = num + " " + auth;
	HttpHeader header = 
	    getHeader ("HTTP/1.1 " + na);
	header.setHeader (type + "-Authenticate", 
			  "Basic realm=\"" + realm + "\"");
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, na) +
			       "access to: <b>" + url + 
			       " </b><br>requires some authentication\n" +
			       "</body></html>\n");
	header.setContent (accreq.toString ());
	return header;
    }

    /** Get a 403 Forbidden header.
     * @return a HttpHeader.
     */
    public HttpHeader get403 () {
	// in most cases we should have a header out already, but to be sure...
	HttpHeader header = getHeader ("HTTP/1.1 403 Forbidden");  
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, "403 Forbidden") +
			       "That is forbidden</body></html>");
	header.setContent (accreq.toString ());
	return header;
    }

    /** Get a 404 File not found.
     * @return a HttpHeader.
     */
    public HttpHeader get404 (String file) {
	// in most cases we should have a header out already, but to be sure...
	HttpHeader header = getHeader ("HTTP/1.1 404 File not found");  
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, "404 File not found") +
			       "File '" + file + "' not found.</body></html>");
	header.setContent (accreq.toString ());
	return header;
    }

    /** Get a 407 Proxy Authentication Required for the given realm and url.
     * @param realm the realm that requires auth.
     * @param url the URL of the request made.
     * @return a suitable HttpHeader.
     */
    public HttpHeader get407 (String realm, URL url) {
	return getAuthorizationHeader (realm, url, 407, 
				       "Proxy Authentication Required", "Proxy");
    }

    /** Get a 412 Precondition Failed header.
     * @return a suitable HttpHeader.
     */
    public HttpHeader get412 () {
	HttpHeader header = getHeader ("HTTP/1.1 412 Precondition Failed");
	String sh = "412 Precondition Failed";
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, sh) + 
			       "</body></html>\n");
	header.setContent (accreq.toString ());
	return header;
    }

    /** Get a 414 Request-URI Too Long
     * @return a suitable HttpHeader.
     */
    public HttpHeader get414 () {
	HttpHeader header = getHeader ("HTTP/1.1 414 Request-URI Too Long");
	String sh = "414 Request-URI Too Long";
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, sh) + 
			       "</body></html>\n");
	header.setContent (accreq.toString ());
	return header;
    }

    /** Get a Requested Range Not Satisfiable for the given exception.
     * @param exception the Exception made.
     * @return a suitable HttpHeader.
     */
    public HttpHeader get416 (Throwable exception) {
	String sh = "HTTP/1.1 416 Requested Range Not Satisfiable ";
	HttpHeader header = getHeader (sh);
	String shh = "416 Requested Range Not Satisfiable";
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, shh) +
			       "Request out of range: " + exception + 
			       ".</b>\n</body></html>\n");
	header.setContent (accreq.toString ());
	return header;
    }
    
    /** Get a 417 Expectation Failed header.
     * @param expectation the expectation that failed.
     * @return a suitable HttpHeader.
     */
    public HttpHeader get417 (String expectation) {
	HttpHeader header = getHeader ("HTTP/1.1 417 Expectation failed");
	String sh = "417 Expectation failed";
	StringBuilder accreq = 
	    new StringBuilder (HtmlPage.getPageHeader (con, sh) +
			       "RabbIT does not handle the '" + expectation + 
			       "' kind of expectations yet.</b>\n" +
			       "</body></html>\n");
	header.setContent (accreq.toString ());
	return header;
    }


    /** Get a 500 Internal Server Error header for the given exception.
     * @param exception the Exception made.
     * @return a suitable HttpHeader.
     */
    public HttpHeader get500 (Throwable exception) {
	// in most cases we should have a header out already, but to be sure...
	// normally this only thrashes the page... Too bad.
	HttpHeader header = getHeader ("HTTP/1.1 500 Internal Server Error ");  
	StringWriter sw = new StringWriter ();
	PrintWriter sos = new PrintWriter (sw);
	exception.printStackTrace (sos);
	
	Properties sysprop = System.getProperties ();
	
	HttpProxy proxy = con.getProxy ();
	Config config = proxy.getConfig ();
	String sh = "500 Internal Server Error";
	StringBuilder sb = 
	    new StringBuilder (HtmlPage.getPageHeader (con, sh));
	sb.append ("You have found a bug in RabbIT please report this" + 
		   "(together with the URL you tried to visit) to the " +
		   "<a href=\"http://www.khelekore.org/rabbit/\" target =" + 
		   "\"_top\">RabbIT</a> crew.<br><br>\n" +
		   "<font size = 4>Connection status</font><br><hr noshade>\n" +
		   con.getDebugInfo ().replaceAll ("\n", "<br>\n") + 
		   "<br>\n" +
		   "<font size = 4>Proxy status</font><br>\n<hr noshade>\n" +
		   "proxy version: " + HttpProxy.VERSION + "<br>\n" + 
		   "proxy identity: " + proxy.getServerIdentity () + "<br>\n" +
		   "server host: " + proxy.getHost () + "<br>\n" +
		   "server port: " + proxy.getPort () + "<br>\n" +
		   "accessfilters: " + 
		   config.getProperty ("Filters", "accessfilters") + 
		   "<br>\n" + 
		   "httpinfilters: " + 
		   config.getProperty ("Filters", "httpinfilters") + 
		   "<br>\n" + 
		   "httpoutfilters:" + 
		   config.getProperty ("Filters", "httpoutfilters") +
		   "<br>\n<br>\n" + 					       
		   "<font size = 4>System properties</font><br>\n" + 
		   "<hr noshade>\n" + 
		   "java.version: " + 
		   sysprop.getProperty ("java.version") + "<br>\n" +
		   "java.vendor: " + 
		   sysprop.getProperty ("java.vendor") + "<br>\n" +
		   "os.name: " + 
		   sysprop.getProperty ("os.name") + "<br>\n" +
		   "os.version: " + 
		   sysprop.getProperty ("os.version") + "<br>\n" +
		   "os.arch: " + 
		   sysprop.getProperty ("os.arch") + "<br>\n" +
		   "error is:<BR><XMP>\n" + sw + 
		   "</XMP><br>" + 
		   "<hr noshade>\n" +
		   "</body></html>\n");
	header.setContent (sb.toString ());
	return header;
    }

    private static String[][] placeTransformers = {
	{"www.", ""}, 
	{"", ".com"}, 
	{"www.", ".com"}, 
	{"", ".org"}, 
	{"www.", ".org"}, 
	{"", ".net"},
	{"www.", ".net"}
    };

    /** Get a 504 Gateway Timeout for the given exception.
     * @param exception the Exception made.
     * @return a suitable HttpHeader.
     */
    public HttpHeader get504 (Throwable exception, String requestLine) {
	HttpHeader header = getHeader ("HTTP/1.1 504 Gateway Time-out ");
	
	HttpHeader hh = new HttpHeader ();
	hh.setRequestLine (requestLine);
	String uri = hh.getRequestURI ();
	try {
	    URL u = new URL (uri);
	    StringBuilder content = 
		new StringBuilder ("\n\n<br>Did you mean to go to: <ul>");
	    Set<String> places = new HashSet<String> ();
	    for (int i = 0; i < placeTransformers.length; i++) {
		String pre = placeTransformers[i][0];
		String suf = placeTransformers[i][1];
		String place = getPlace (u, pre, suf); 
		if (place != null && !places.contains (place)) {
		    content.append ("<li><a href=\"" + place + "\">" + 
				    place + "</a></li>\n");
		    places.add (place);
		}
	    }
	    content.append ("</ul>");
	    header.setContent (content.toString ());
	} catch (MalformedURLException e) {
	    throw new RuntimeException (e);
	}
	
	return header;
    }

    private String getPlace (URL u, String hostPrefix, String hostSuffix) {
	String host = u.getHost ();
	if (host.startsWith (hostPrefix))
	    hostPrefix = "";
	if (host.endsWith (hostSuffix))
	    hostSuffix = "";
	if (hostPrefix.equals ("") && hostSuffix.equals (""))
	    return null;
	return u.getProtocol () + "://" + hostPrefix + u.getHost () + 
	    hostSuffix + 
	    (u.getPort () == -1 ? "" : ":" + u.getPort ()) + 
	    u.getFile ();		
    }
}
