/*******************************************************************************
 * Copyright (c) 2006, 2008 Steffen Pingel and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Steffen Pingel - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.trac.tests.support;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

public class TestProxy implements Runnable {

	public static final String NOT_FOUND = "HTTP/1.1 404 Not Found";

	private int listenPort;

	private Message request;

	private Message response;

	private Thread runner;

	private IOException exception;

	private volatile boolean stopped = false;

	public TestProxy(int listenPort) {
		this.listenPort = listenPort;
	}

	public TestProxy() {
	}

	public synchronized int getListenPort() throws InterruptedException {
		while (listenPort == 0) {
			wait();
		}
		return listenPort;
	}

	public void start() {
		runner = new Thread(this, "TestProxy :" + listenPort);
		runner.start();
	}

	public int startAndWait() throws InterruptedException {
		start();
		int port = getListenPort();
		// wait for socket to enter accept call
		Thread.sleep(100);
		return port;
	}

	public void run() {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(listenPort);
			synchronized (this) {
				listenPort = serverSocket.getLocalPort();
				notifyAll();
			}
			while (!stopped) {
				Socket socket = serverSocket.accept();
				try {
					Message request = readMessage(socket.getInputStream());
					setRequest(request);

					Message response = waitForResponse();
					writeMessage(response, socket.getOutputStream());
				} finally {
					try {
						socket.close();
					} catch (IOException e1) {
					}
				}
			}
		} catch (InterruptedIOException e) {
		} catch (IOException e) {
			setException(e);
		} catch (InterruptedException e) {
		} finally {
			if (serverSocket != null) {
				try {
					serverSocket.close();
				} catch (IOException e) {
				}
			}
		}

	}

	private void writeMessage(Message message, OutputStream out) throws IOException {
		BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
		writer.write(message.toString());
	}

	private synchronized void setException(IOException exception) {
		this.exception = exception;
		notifyAll();
	}

	public synchronized void checkForException() throws IOException {
		if (exception != null) {
			throw exception;
		}
	}

	private Message readMessage(InputStream in) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));
		Message message = null;
		String line;
		while ((line = reader.readLine()) != null) {
			if (line.length() == 0) {
				if (message == null) {
					throw new IOException("Incomplete message");
				}
				return message;
			}

			if (message == null) {
				message = new Message(line);
			} else {
				StringTokenizer t = new StringTokenizer(line, ":");
				message.headers.put(t.nextToken(), t.nextToken().trim());
			}
		}
		throw new EOFException();
	}

	public void stop() {
		stopped = true;
		runner.interrupt();
		try {
			runner.join(500);
		} catch (InterruptedException e) {
		}
	}

	public Message getRequest() {
		return request;
	}

	public synchronized Message waitForRequest() throws InterruptedException {
		while (request == null) {
			wait();
		}
		return request;
	}

	public synchronized Message waitForResponse() throws InterruptedException {
		while (response == null) {
			wait();
		}
		return response;
	}

	public synchronized void setResponse(Message response) {
		this.response = response;
		notifyAll();
	}

	public synchronized void setResponse(String response) {
		this.response = new Message(response);
		notifyAll();
	}

	public synchronized void setRequest(Message request) {
		this.request = request;
		notifyAll();
	}

	public static class Message {

		public Message(String request) {
			this.request = request;
		}

		public String request;

		public Map<String, String> headers = new HashMap<String, String>();

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(request);
			sb.append("\n");
			if (headers != null) {
				for (String key : headers.keySet()) {
					sb.append(key + ": " + headers.get(key));
					sb.append("\n");
				}
			}
			sb.append("\n");
			return sb.toString();
		}

		public String getMethod() {
			int i = request.indexOf(" ");
			return (i != -1) ? request.substring(0, i) : request;
		}

	}

	public static void main(String[] args) {
		TestProxy proxy = new TestProxy(8080);
		proxy.start();
		try {
			proxy.setResponse(new Message("404 / HTTP 1.1"));
			try {
				System.out.println(proxy.waitForRequest());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} finally {
			proxy.stop();
		}

	}

}
