/******************************************************************
*
*	CyberHTTP for Java
*
*	Copyright (C) Satoshi Konno 2002-2003
*
*	File: HTTPServer.java
*
*	Revision;
*
*	12/12/02
*		- first revision.
*	10/20/03
*		- Improved the HTTP server using multithreading.
*	08/27/04
*		- Changed accept() to set a default timeout, HTTP.DEFAULT_TIMEOUT, to the socket.
*	
******************************************************************/

package org.cybergarage.http;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

import org.cybergarage.util.Debug;
import org.cybergarage.util.ListenerList;

/**
 * 
 * This class identifies an HTTP over TCP server<br>
 * The server must be initialized iether by the {@link HTTPServer#open(InetAddress, int)} or the {@link HTTPServer#open(String, int)} method.<br>
 * Optionally a set of {@link HTTPRequestListener} may be set<br>
 * The server then can be started or stopped by the method {@link HTTPServer#start()} and {@link HTTPServer#stop()}
 * 
 * @author Satoshi "skonno" Konno
 * @author Stefano "Kismet" Lenzi
 * @version 1.8
 *
 */
public class HTTPServer implements Runnable
{
	////////////////////////////////////////////////
	//	Constants
	////////////////////////////////////////////////

	public final static String NAME = "CyberHTTP";
	public final static String VERSION = "1.0";

	public final static int DEFAULT_PORT = 80;
	
	/**
	 * Default timeout connection for HTTP comunication
	 * @since 1.8
	 */
        // I2P fix
	//public final static int DEFAULT_TIMEOUT = DEFAULT_PORT * 1000;
	public final static int DEFAULT_TIMEOUT = 10 * 1000;
	
	public static String getName()
	{
		String osName = System.getProperty("os.name");
		String osVer = System.getProperty("os.version");
		return osName + "/"  + osVer + " " + NAME + "/" + VERSION;
	}
	
	////////////////////////////////////////////////
	//	Constructor
	////////////////////////////////////////////////
	
	public HTTPServer()
	{
		serverSock = null;
		
	}

	////////////////////////////////////////////////
	//	ServerSocket
	////////////////////////////////////////////////

	private ServerSocket serverSock = null;
	private InetAddress bindAddr = null;
	private int bindPort = 0;
	/**
	 * Store the current TCP timeout value
	 * The variable should be accessed by getter and setter metho
	 */
	protected int timeout = DEFAULT_TIMEOUT;
	
	public ServerSocket getServerSock()
	{
		return serverSock;
	}

	public String getBindAddress()
	{
			if (bindAddr == null)
				return "";
			return bindAddr.toString();
	}

	public int getBindPort()
	{
		return bindPort;
	}
	
	
	
	////////////////////////////////////////////////
	//	open/close
	////////////////////////////////////////////////
	
	/**
	 * Get the current socket timeout
	 * @since 1.8
	 */
	public synchronized int getTimeout() {
		return timeout;
	}

	/**
	 * Set the current socket timeout
	 * @param timeout
	 * @since 1.8
	 */
	public synchronized void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	public boolean open(InetAddress addr,int port){
		if (serverSock != null)
			return true;
		try {
			serverSock = new ServerSocket(bindPort, 0, bindAddr);
		}catch (IOException e) {
			return false;
		}
		return true;
	}
	
	public boolean open(String addr, int port)
	{
		if (serverSock != null)
			return true;
		try {
			bindAddr = InetAddress.getByName(addr);
			bindPort = port;
			serverSock = new ServerSocket(bindPort, 0, bindAddr);
		}
		catch (IOException e) {
			Debug.warning("HTTP server open failed " + addr + " " + port, e);
			return false;
		}
		return true;
	}

	public boolean close()
	{
		if (serverSock == null)
			return true;
		try {
			serverSock.close();
			serverSock = null;
			bindAddr = null;
			bindPort = 0;
		}
		catch (Exception e) {
			Debug.warning(e);
			return false;
		}
		return true;
	}

	public Socket accept()
	{
		if (serverSock == null)
			return null;
		try {
			Socket sock = serverSock.accept();
			sock.setSoTimeout(getTimeout());
			return sock;
		}
		catch (Exception e) {
			return null;
		}
	}

	public boolean isOpened()
	{
		return (serverSock != null) ? true : false;
	}

	////////////////////////////////////////////////
	//	httpRequest
	////////////////////////////////////////////////

	private ListenerList httpRequestListenerList = new ListenerList();
	 	
	public void addRequestListener(HTTPRequestListener listener)
	{
		httpRequestListenerList.add(listener);
	}		

	public void removeRequestListener(HTTPRequestListener listener)
	{
		httpRequestListenerList.remove(listener);
	}		

	public void performRequestListener(HTTPRequest httpReq)
	{
		int listenerSize = httpRequestListenerList.size();
		for (int n=0; n<listenerSize; n++) {
			HTTPRequestListener listener = (HTTPRequestListener)httpRequestListenerList.get(n);
			listener.httpRequestRecieved(httpReq);
		}
	}		
	
	////////////////////////////////////////////////
	//	run	
	////////////////////////////////////////////////

	private Thread httpServerThread = null;
		
	public void run()
	{
		if (isOpened() == false)
			return;
			
		Thread thisThread = Thread.currentThread();
		
		while (httpServerThread == thisThread) {
			Thread.yield();
			Socket sock;
			try {
				Debug.message("accept ...");
				sock = accept();
				if (sock != null)
					Debug.message("sock = " + sock.getRemoteSocketAddress());
			}
			catch (Exception e){
				Debug.warning(e);
				break;
			}
			HTTPServerThread httpServThread = new HTTPServerThread(this, sock);
			httpServThread.start(); 
			Debug.message("httpServThread ...");
		}
	}
	
	public boolean start(){
		StringBuffer name = new StringBuffer("Cyber.HTTPServer/");
		// I2P hide address from thread dumps
		//name.append(serverSock.getLocalSocketAddress());
		httpServerThread = new Thread(this,name.toString());
		httpServerThread.start();
		return true;
	}
	
	public boolean stop()
	{
		httpServerThread = null;
		return true;
	}

	/** I2P */
	@Override
	public String toString() { return getBindAddress(); }
}
