package zutil.network.http;

import java.io.OutputStream;
import java.io.PrintStream;
import java.util.HashMap;

/**
 * This PrintStream is written for HTTP use
 * It has buffer capabilities and cookie management.
 * 
 * @author Ziver
 *
 */
public class HttpPrintStream extends PrintStream{
	// Defines the type of message
	public enum HTTPMessageType{
		REQUEST,
		RESPONSE
	}

	// This defines the type of message that will be generated
	private HTTPMessageType message_type;
	// The status code of the message, ONLY for response
	private Integer res_status_code;
	// The request type of the message ONLY for request
	private String req_type;
	// The requesting url ONLY for request
	private String req_url;
	// An Map of all the header values
	private HashMap<String, String> header;
	// An Map of all the cookies
	private HashMap<String, String> cookie;
	// The buffered header
	private StringBuffer buffer;
	// If the header buffering is enabled
	private boolean buffer_enabled;	

	/**
	 * Creates an new instance of HttpPrintStream with
	 * message type of RESPONSE and buffering disabled.
	 * 
	 * @param out is the OutputStream to send the message
	 */
	public HttpPrintStream(OutputStream out) {
		this( out, HTTPMessageType.RESPONSE );
	}
	/**
	 * Creates an new instance of HttpPrintStream with
	 * message type buffering disabled.
	 * 
	 * @param out is the OutputStream to send the message
	 * @param type is the type of message
	 */
	public HttpPrintStream(OutputStream out, HTTPMessageType type) {
		super(out);

		this.message_type = type;
		res_status_code = 0;
		header = new HashMap<String, String>();
		cookie = new HashMap<String, String>();
		buffer = new StringBuffer();
		buffer_enabled = false;
	}

	/**
	 * Enable the buffering capability of the PrintStream.
	 * Nothing will be sent to the client when buffering
	 * is enabled until you close or flush the stream. 
	 * This function will flush the stream if buffering is
	 * disabled.
	 * 
	 * @param b
	 */
	public void enableBuffering(boolean b){
		buffer_enabled = b;
		if(!buffer_enabled) flush();
	}

	/**
	 * Adds a cookie that will be sent to the client
	 * 
	 * @param key is the name of the cookie
	 * @param value is the value of the cookie
	 * @throws Exception Throws exception if the header has already been sent
	 */
	public void setCookie(String key, String value) throws RuntimeException{
		if(cookie == null)
			throw new RuntimeException("Header already sent!!!");
		cookie.put(key, value);
	}

	/**
	 * Adds an header value
	 * 
	 * @param key is the header name
	 * @param value is the value of the header
	 * @throws Exception Throws exception if the header has already been sent
	 */
	public void setHeader(String key, String value) throws RuntimeException{
		if(header == null)
			throw new RuntimeException("Header already sent!!!");
		header.put(key, value);
	}

	/**
	 * Sets the status code of the message, ONLY available in HTTP RESPONSE
	 * 
	 * @param code the code from 100 up to 599
	 * @throws RuntimeException if the header has already been sent or the message type is wrong
	 */
	public void setStatusCode(int code) throws RuntimeException{
		if( res_status_code == null )
			throw new RuntimeException("Header already sent!!!");
		if( message_type != HTTPMessageType.RESPONSE )
			throw new RuntimeException("Status Code is only available in HTTP RESPONSE!!!");
		res_status_code = code;
	}

	/**
	 * Sets the request type of the message, ONLY available in HTTP REQUEST
	 * 
	 * @param req_type is the type of the message, e.g. GET, POST...
	 * @throws RuntimeException if the header has already been sent or the message type is wrong
	 */
	public void setRequestType(String req_type) throws RuntimeException{
		if( req_type == null )
			throw new RuntimeException("Header already sent!!!");
		if( message_type != HTTPMessageType.REQUEST )
			throw new RuntimeException("Request Message Type is only available in HTTP REQUEST!!!");
		this.req_type = req_type;
	}
	/**
	 * Sets the requesting URL of the message, ONLY available in HTTP REQUEST
	 * 
	 * @param req_url is the URL
	 * @throws RuntimeException if the header has already been sent or the message type is wrong
	 */
	public void setRequestURL(String req_url) throws RuntimeException{
		if( req_url == null )
			throw new RuntimeException("Header already sent!!!");
		if( message_type != HTTPMessageType.REQUEST )
			throw new RuntimeException("Request URL is only available in HTTP REQUEST!!!");
		this.req_url = req_url;
	}

	/**
	 * Prints with a new line
	 */
	public void println(String s){
		printOrBuffer(s+"\n");
	}

	/**
	 * Prints an string
	 */
	public void print(String s){
		printOrBuffer(s);
	}

	/**
	 * prints to all
	 */
	private void printOrBuffer(String s){
		if(buffer_enabled){
			buffer.append(s);
		}
		else{
			if(res_status_code != null){
				if( message_type==HTTPMessageType.REQUEST ) 
					super.print(req_type+" "+req_url+" HTTP/1.1");	
				else 
					super.print("HTTP/1.1 "+res_status_code+" "+getStatusString(res_status_code));				
				super.println();
				res_status_code = null;
				req_type = null;
				req_url = null;
			}
			if(header != null){
				for(String key : header.keySet()){					
					super.print(key+": "+header.get(key));
					super.println();
				}
				header = null;
			}
			if(cookie != null){
				if( !cookie.isEmpty() ){
					if( message_type==HTTPMessageType.REQUEST ){
						super.print("Cookie: ");
						for(String key : cookie.keySet()){					
							super.print(key+"="+cookie.get(key)+"; ");
						}
						super.println();
					}
					else{
						for(String key : cookie.keySet()){					
							super.print("Set-Cookie: "+key+"="+cookie.get(key)+";");
							super.println();
						}
					}
				}
				super.println();
				cookie = null;
			}
			super.print(s);
		}
	}

	/**
	 * Sends out the buffer and clears it
	 */
	public void flush(){
		if(buffer_enabled){
			buffer_enabled = false;
			printOrBuffer(buffer.toString());
			buffer.delete(0, buffer.length());
			buffer_enabled = true;
		}
		else if(res_status_code != null || header != null || cookie != null){
			printOrBuffer("");
		}
		super.flush();
	}

	public void close(){
		flush();
		super.close();
	}

	public void println(){			println("");}
	public void println(boolean x){	println(String.valueOf(x));}
	public void println(char x){	println(String.valueOf(x));}
	public void println(char[] x){	println(new String(x));}
	public void println(double x){	println(String.valueOf(x));}
	public void println(float x){	println(String.valueOf(x));}
	public void println(int x){		println(String.valueOf(x));}
	public void println(long x){	println(String.valueOf(x));}
	public void println(Object x){	println(String.valueOf(x));}

	public void print(boolean x){	printOrBuffer(String.valueOf(x));}
	public void print(char x){		printOrBuffer(String.valueOf(x));}
	public void print(char[] x){	printOrBuffer(new String(x));}
	public void print(double x){	printOrBuffer(String.valueOf(x));}
	public void print(float x){		printOrBuffer(String.valueOf(x));}
	public void print(int x){		printOrBuffer(String.valueOf(x));}
	public void print(long x){		printOrBuffer(String.valueOf(x));}
	public void print(Object x){	printOrBuffer(String.valueOf(x));}

	/*
	public void write(int b) {		print((char)b);}	
	public void write(byte buf[], int off, int len){
									print(new String(buf, off, len));}
	 */
	private String getStatusString(int type){
		switch(type){
		case 100: return "Continue";
		case 200: return "OK";
		case 301: return "Moved Permanently";
		case 307: return "Temporary Redirect";
		case 400: return "Bad Request";
		case 401: return "Unauthorized";
		case 403: return "Forbidden";
		case 404: return "Not Found";
		case 500: return "Internal Server Error";
		case 501: return "Not Implemented";
		default:  return "";
		}
	}
}
