/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.http;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * This class is for unit testing {@link EasyHttpClient}.
 * 
 * @author Elias Ross
 * @version 1.0
 */
public class EasyHttpClientTest extends junit.framework.TestCase {
	
	public EasyHttpClientTest(String name) {
		super(name);
	}

	public static class MyHttpServer extends ThreadedHttpServer {
		boolean doRead = true;

		boolean doPost = true;

		boolean doClose = false;

		boolean fail = false;

		boolean failAlways = false;

		int sleep = 1;

		volatile int requestCount = 0;

		StatusLine statusLine = StatusLine.HTTP11_200_OK;

		ServerRequest request;

		public MyHttpServer() throws IOException {
			super();
		}

		public void handleRequest(Request req) throws IOException {
			HttpServer s = req.getServer();
			// System.out.println("handleRequest " + s);
			requestCount++;
			request = s.readRequest();
			// System.out.println("handleRequest " + request);
			InputStream is = request.getInputStream();
			is = HttpUtil.wrapInputStream(is, request.getHeaders());
			String result = "got ";
			if (doRead) {
				BufferedReader in = new BufferedReader(
						new InputStreamReader(is));
				String line = in.readLine();
				result += line;
			}
			if (fail) {
				fail = failAlways;
				throw new IOException("Told to fail");
			}
			try {
				Thread.sleep(sleep);
			} catch (InterruptedException e) {
				System.out.println("Interrupted");
				throw new IOException("Wakeup");
			}
			if (doPost) {
				MessageHeader mh;
				mh = new MessageHeader(MessageHeader.FN_CONTENT_LENGTH, ""
						+ result.length());
				s.writeResponse(new ServerResponse(statusLine,
						new MessageHeader[] { mh }));
				PrintWriter out = new PrintWriter(new OutputStreamWriter(s
						.getOutputStream()));
				out.println(result);
				out.flush();
			} else {
				if (doClose) {
					MessageHeader mh;
					mh = new MessageHeader(MessageHeader.FN_CONNECTION, "close");
					s.writeResponse(new ServerResponse(statusLine,
							new MessageHeader[] { mh }));
					s.getOutputStream().close();
				} else {
					MessageHeader mh;
					mh = new MessageHeader(MessageHeader.FN_CONTENT_LENGTH, "0");
					s.writeResponse(new ServerResponse(statusLine,
							new MessageHeader[] { mh }));
				}
			}
		}
	}

	private class EasyHttpClientFactory2 {
		HttpClient c;

		EasyHttpClientFactory2(Socket s) throws IOException {
			c = new BasicHttpClient(s.getOutputStream(), s.getInputStream());
		}

		public EasyHttpClient makeGetClient(URL url) {
			return new EasyHttpClient(c, url, Method.GET);
		}

		public EasyHttpClient makePostClient(URL url) {
			return new EasyHttpClient(c, url, Method.POST);
		}

		public EasyHttpClient makeClient(URL url, Method method) {
			return new EasyHttpClient(c, url, method);
		}
	}

	public void testEasyGet() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		Socket s = new Socket("127.0.0.1", server.getPort());
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2(s);
		URL url = new URL("http://localhost");
		EasyHttpClient ec;
		ec = factory.makeGetClient(url);
		assertEquals(null, ec.getLastResponse());
		ec = factory.makeGetClient(url);
		BufferedReader br;
		br = ec.doGet();
		String got = HttpUtil.read(br).trim();
		assertEquals("got", got);
		assertEquals("/", server.request.getRequestLine().getRequestURI());
		// try again
		ec.setFile("/otherfile");
		HttpUtil.discard(ec.doGet());
		assertEquals("/otherfile", server.request.getRequestLine()
				.getRequestURI());
		// try 404
		ec.setFile("/otherfile2");
		server.statusLine = StatusLine.HTTP11_404;
		try {
			HttpUtil.read(ec.doGet());
			fail("Bad HTTP status" + ec.getLastResponse());
		} catch (HttpException e) {
		}
		assertEquals(404, ec.getLastResponse().getStatusLine().getStatusCode());
		// ignore 404
		ec.setCheckStatus(false);
		br = ec.doGet();
		assertEquals(404, ec.getLastResponse().getStatusLine().getStatusCode());
		got = HttpUtil.read(br).trim();
		assertEquals("got", got);
	}

	public void testEasyPostRetry() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.fail = true;
		server.failAlways = false;
		URL url = new URL("http://localhost:" + server.getPort());
		EasyHttpClient ec = new EasyHttpClient(url, Method.POST);
		String body = "abc=def";
		BufferedReader br = ec.doPostUrlEncoded(body.getBytes());
		String got = HttpUtil.read(br).trim();
		assertEquals("got " + body, got);
		assertTrue("we tossed", server.requestCount >= 2);
	}

	public void testEasyPostRetry2() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.fail = true;
		server.failAlways = true;
		URL url = new URL("http://localhost:" + server.getPort());
		EasyHttpClient ec = new EasyHttpClient(url, Method.POST);
		try {
			ec.doPostUrlEncoded("who cares\n".getBytes());
			fail("should have failed");
		} catch (IOException e) {
		}
		assertTrue("failed four times", server.requestCount >= 3);
	}

	/**
	 * Doesn't allow marking.
	 */
	class ByteArrayInputStream2 extends ByteArrayInputStream {
		boolean tested = false;

		ByteArrayInputStream2(byte[] buf) {
			super(buf);
		}

		public boolean markSupported() {
			tested = true;
			return false;
		}

		public String toString() {
			return super.toString() + " tested=" + tested;
		}
	}

	public void testEasyPostRetry3() throws IOException {
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.fail = true;
		server.failAlways = true;
		URL url = new URL("http://localhost:" + server.getPort());
		EasyHttpClient ec = new EasyHttpClient(url, Method.POST);
		String body = "something\n";
		ByteArrayInputStream2 is = new ByteArrayInputStream2(body.getBytes());
		try {
			ec.doOperation(is, -1, "text/plain");
			fail("cannot re-post " + is.tested + " " + ec.getLastResponse());
		} catch (HttpException e) {
		}
	}

	public void testEasyPost() throws Exception {
		ThreadedHttpServer server = new MyHttpServer();
		server.start();
		Socket s = new Socket("127.0.0.1", server.getPort());
		URL url = new URL("http://localhost/");
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2(s);
		EasyHttpClient ec = factory.makePostClient(url);
		Map<String, String> map = new HashMap<String, String>();
		map.put("a", "b");
		byte[] body = HttpUtil.urlEncode(map);
		BufferedReader br = ec.doPostUrlEncoded(body);
		String got = HttpUtil.read(br).trim();
		assertEquals("got " + new String(body), got);

		ec.close();
		try {
			ec.doPostUrlEncoded(body);
			fail("already closed");
		} catch (IllegalStateException e) {
		}
	}

	public void testEasyException() throws Exception {
		HttpClient c = new BasicHttpClient(new ByteArrayOutputStream(),
				new ByteArrayInputStream(new byte[0]));
		MessageHeaders mh = new MessageHeaders();
		RequestLine rl = new RequestLine(Method.DELETE, "/", HttpVersion.HTTP11);
		try {
			new EasyHttpClient(null, rl, mh);
		} catch (IllegalArgumentException e) {
		}
		try {
			new EasyHttpClient(c, null, mh);
		} catch (IllegalArgumentException e) {
		}
		try {
			new EasyHttpClient(c, rl, null);
		} catch (IllegalArgumentException e) {
		}
	}

	public void testEasyOperation() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.start();
		Socket s = new Socket("127.0.0.1", server.getPort());
		URL url = new URL("http://localhost/");
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2(s);
		EasyHttpClient ec = factory.makePostClient(url);
		String str = "yo!\n";
		ByteArrayInputStream bais = new ByteArrayInputStream(str.getBytes());
		InputStream is = ec.doOperation(bais, str.length(), "text/plain");
		BufferedReader in = new BufferedReader(new InputStreamReader(is));
		String got = HttpUtil.read(in);
		String fct = server.request.getHeaders().getFieldContent(
				MessageHeader.FN_CONTENT_TYPE);
		String fcl = server.request.getHeaders().getFieldContent(
				MessageHeader.FN_CONTENT_LENGTH);
		assertEquals("text/plain", fct);
		assertEquals("4", fcl);
		assertEquals(("got " + str).trim(), got.trim());
		// no length set
		try {
			ec.doOperation(null, 10, null);
			fail("need input stream with length");
		} catch (IllegalArgumentException e) {
		}
	}

	public void testGrabBag() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.doRead = false;
		server.doPost = false;
		server.fail = true;
		server.failAlways = true;
		Socket s = new Socket("127.0.0.1", server.getPort());
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2(s);
		URL url = new URL("http://localhost");
		EasyHttpClient ec = factory.makeGetClient(url);

		try {
			ec.doPostUrlEncoded(null);
			fail("null urlEncodedData");
		} catch (IllegalArgumentException e) {
		}
		ec.toString();
		try {
			ec.doPostUrlEncoded(null);
			fail("null urlEncodedData");
		} catch (IllegalArgumentException e) {
		}
	}

	/*
	 * public void testMain() throws Exception { EasyHttpClient.main(new
	 * String[] { }); EasyHttpClient.main(new String[] {
	 * "http://www.example.net" }); EasyHttpClient.main(new String[] {
	 * "http://www.example.net", "a=b" }); }
	 */

	public void testGetNoResponse() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		server.doPost = false;
		server.statusLine = StatusLine.HTTP11_404;
		Socket s = new Socket("127.0.0.1", server.getPort());
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2(s);
		URL url = new URL("http://localhost");
		EasyHttpClient ec = factory.makeGetClient(url);

		ec.setCheckStatus(false);
		BufferedReader br = ec.doGet();
		assertTrue("got some sort of reply", br != null);
	}

	public void testEasyOperation2() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		server.doPost = false;
		Socket s = new Socket("127.0.0.1", server.getPort());
		URL url = new URL("http://localhost/");
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2(s);
		EasyHttpClient ec = factory.makeClient(url, Method.DELETE);
		ec.doOperation();
		assertEquals(Method.DELETE, server.request.getRequestLine().getMethod());

		// try again, use keep-alive
		server.doPost = true;
		ec.doOperation();
		ec.close();
		server.doPost = false;

		// try again, this time the server returns empty document
		s = new Socket("127.0.0.1", server.getPort());
		factory = new EasyHttpClientFactory2(s);
		ec = factory.makeClient(url, Method.DELETE);
		ec.doOperation();

		// server closes connection
		server.doClose = true;
		ec.doOperation();
	}

	public void testRetryClientTimeoutBug() throws Exception {
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		server.doPost = true;
		server.sleep = 240 * 1000 * 3; // should cause timeout
		URL url = new URL("http://localhost:" + server.getPort());
		RetryHttpClient client = new RetryHttpClient(url, 3) {
			protected void setSocketOptions(Socket socket) throws IOException {
				socket.setSoTimeout(1 * 1000);
			}

			protected void retrySendRequest(IOException e, int failures) {
				super.retrySendRequest(e, failures);
				System.out.println("Retry " + e + " " + failures);
			}
		};
		EasyHttpClient ec = new EasyHttpClient(client, url, Method.POST);
		try {
			ec.doOperation();
			fail("need to time out!");
		} catch (SocketTimeoutException e) {
		}
		server.interrupt();
		System.out.println("should not timeout");
		server.sleep = 1; // should not cause timeout
		ec.doOperation();
		server.close();
	}
	
	public void testMain() throws Exception {
		EasyHttpClient.main(new String[0]);
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		EasyHttpClient.main(new String[] { "http://localhost:" + server.getPort() });
		server.close();
	}

}
