/* 
 * 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.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * This class is for unit testing HttpClient and HttpServer.
 *
 * @author Elias Ross
 * @version 1.0
 */
public class ClientServerTest
	extends junit.framework.TestCase
{
	
	MessageHeaders mh = new MessageHeaders();
	
	public ClientServerTest(String name) {
		super(name);
	}

	/*
	public static junit.framework.TestSuite suite() {
		return new org.hansel.CoverageDecorator(ClientServerTest.class,
			new Class[] { RetryHttpClient.class });
	} 
	public static junit.framework.TestSuite suite() {
		TestSuite suite = new TestSuite();
		suite.addTest(new ClientServerTest("testRedirectBadUrl"));
		return suite;
	} 
	*/

	private static final byte stuff[] = { 1, 2, 3, 4 };
	private static final RequestLine requestLine =
			new RequestLine(Method.GET, "/", HttpVersion.HTTP11);
	private static final RequestLine requestPost =
			new RequestLine(Method.POST, "/", HttpVersion.HTTP11);
	private static final MessageHeaders messageHeaders = new MessageHeaders();
	static {
		messageHeaders.add(MessageHeader.MH_TRANSFER_ENCODING_CHUNKED);
	}
	private static final InputStream junkInput = new ByteArrayInputStream(stuff);
	private static final OutputStream junkOutput = new ByteArrayOutputStream();
	private static URL exampleURL = null;
	static {
		try {
			exampleURL = new URL("http://example.com");
		} catch (IOException e) {
		}
	}

	public void testServerParam()
		throws IOException
	{
		try {
			new BasicHttpServer(null, null);
			fail("null input");
		} catch (IllegalArgumentException e) {
		}
		try {
			new BasicHttpServer(new ByteArrayOutputStream(), null);
			fail("null input");
		} catch (IllegalArgumentException e) {
		}
		ByteArrayInputStream bais = new ByteArrayInputStream("GET /\r\n\r\n".getBytes());
		HttpServer s = new BasicHttpServer(new ByteArrayOutputStream(), bais);
		s.toString();
		s.readRequest();
		try {
			s.writeResponse(null);
			fail("null ServerResponse");
		} catch (IllegalArgumentException e) {
		}
		ServerResponse sr = new ServerResponse(
				StatusLine.HTTP11_200_OK,
				new MessageHeaders(),
				new ByteArrayDataPoster("ahou!".getBytes()));
		s.writeResponse(sr);
	}

	public void testServerClose()
		throws IOException
	{
		ByteArrayInputStream bais = new ByteArrayInputStream("1".getBytes());
		HttpServer s = new BasicHttpServer(new ByteArrayOutputStream(), bais);
		s.close();
		// to get more code coverage
		s.close();

		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		Socket sock = new Socket("127.0.0.1", server.getPort());
		s = new BasicHttpServer(sock);
		s.close();
		// Java 1.4-ism
		// assertTrue("closed", sock.isClosed());
		server.close();
	}

	public void testClientParam()
		throws IOException
	{
		try {
			new RetryHttpClient(null, 1);
			fail("null input");
		} catch (IllegalArgumentException e) {
		}
		try {
			new RetryHttpClient(exampleURL, -1);
			fail("bad tries");
		} catch (IllegalArgumentException e) {
		}
		try {
			new BasicHttpClient(null, junkInput);
			fail("null output");
		} catch (IllegalArgumentException e) {
		}
		try {
			new BasicHttpClient(junkOutput, null);
			fail("null input");
		} catch (IllegalArgumentException e) {
		}
		HttpClient c = new BasicHttpClient(junkOutput, junkInput);
		c.toString();
		try {
			c.writeRequest(null);
			fail("null input");
		} catch (IllegalArgumentException e) {
		}
	}

	public void testServerState()
		throws IOException
	{
		StatusLine statusLine = StatusLine.HTTP11_200_OK;
		String head = "GET / HTTP/1.1\r\n\r\n";
		InputStream junkInput = new ByteArrayInputStream((head + head + head + head + head + head).getBytes());
		try {
			HttpServer s = new BasicHttpServer(junkOutput, junkInput);
			s.readRequest();
			s.readRequest();
			fail("cannot read twice");
		} catch (IllegalStateException e) {
		}
		try {
			HttpServer s = new BasicHttpServer(junkOutput, junkInput);
			s.getOutputStream();
			fail("cannot getOutputStream now");
		} catch (IllegalStateException e) {
		}
		try {
			HttpServer s = new BasicHttpServer(junkOutput, junkInput);
			s.readRequest();
			s.writeResponse(new ServerResponse(statusLine, messageHeaders));
			s.getOutputStream();
			s.getOutputStream();
			fail("cannot getOutputStream twice");
		} catch (IllegalStateException e) {
		}
		try {
			HttpServer s = new BasicHttpServer(junkOutput, junkInput);
			s.readRequest();
			s.writeResponse(new ServerResponse(statusLine, messageHeaders));
			s.writeResponse(new ServerResponse(statusLine, messageHeaders));
			fail("cannot writeResponse twice");
		} catch (IllegalStateException e) {
		}
		{
			HttpServer s = new BasicHttpServer(junkOutput, junkInput);
			s.readRequest();
			s.writeResponse(new ServerResponse(statusLine, messageHeaders));
			s.readRequest();
			s.writeResponse(new ServerResponse(statusLine, messageHeaders));
			// shouldn't fail
		}
	}

	public void testClientState()
		throws IOException
	{
		try {
			HttpClient c = new BasicHttpClient(junkOutput, junkInput);
			c.writeRequest(new ClientRequest(requestLine, messageHeaders));
			c.writeRequest(new ClientRequest(requestLine, messageHeaders));
			fail("cannot write twice");
		} catch (IllegalStateException e) {
		}
		try {
			HttpClient c = new BasicHttpClient(junkOutput, junkInput);
			c.getOutputStream();
			fail("cannot getOutputStream now");
		} catch (IllegalStateException e) {
		}
		try {
			HttpClient c = new BasicHttpClient(junkOutput, junkInput);
			c.writeRequest(new ClientRequest(requestLine, messageHeaders));
			c.getOutputStream();
			c.getOutputStream();
			fail("cannot getOutputStream twice");
		} catch (IllegalStateException e) {
		}
		try {
			HttpClient c = new BasicHttpClient(junkOutput, junkInput);
			c.readResponse();
			fail("cannot readResponse now");
		} catch (IllegalStateException e) {
		}
	}

	public void testClientIn()
		throws IOException
	{
		ByteArrayOutputStream testout = new ByteArrayOutputStream();
		String resp = 
			"HTTP/1.1 200 OK" + HttpUtil.CRLF + 
			"Content-Length: 0" + HttpUtil.CRLF +
			HttpUtil.CRLF;
		ByteArrayInputStream testin = new ByteArrayInputStream(resp.getBytes());
		HttpClient c = new BasicHttpClient(testout, testin);
		c.writeRequest(new ClientRequest(requestLine, messageHeaders));

		ClientResponse r = c.readResponse();
		assertEquals("HTTP/1.1", HttpVersion.HTTP11, r.getStatusLine().getHttpVersion());
		assertEquals("200", 200, r.getStatusLine().getStatusCode());
		assertEquals("OK", "OK", r.getStatusLine().getReasonPhrase());
		MessageHeaders headers = r.getHeaders();
		assertEquals("One header", 1, headers.count());
		assertEquals("0 length", "0", headers.asList().get(0).getFieldContent());
		assertEquals("0 remain", 0, testin.available());

		InputStream in = r.getInputStream();
		assertEquals("empty ", -1, in.read());
		String s = new String(testout.toByteArray());
		assertTrue("Starts with", s.startsWith("GET / HTTP/1.1"));
	}

	private static class MyHttpServer extends ThreadedHttpServer {
		boolean doRead = true;
		boolean failException = false;
		ServerRequest r;
		public MyHttpServer() throws IOException {
			super();
		}
		
		@Override
		protected void exception(Exception e) {
			e.printStackTrace();
		}
		
		/**
		 * Reads one line and then says "got [line]"
		 */
		@Override
		public void handleRequest(Request request)
			throws IOException
		{
			if (failException)
			       	throw new RuntimeException("fail");
			HttpServer server = request.getServer();
			r = server.readRequest();
			String result = "got ";
			InputStream is = r.getInputStream();
			is = HttpUtil.wrapInputStream(is, r.getHeaders());
			if (doRead) {
				BufferedReader in = new BufferedReader(new InputStreamReader(is));
				String line = in.readLine();
				result += line;
			}
			MessageHeader mh; 
			mh = new MessageHeader(MessageHeader.FN_CONTENT_LENGTH, "" + result.length());
			server.writeResponse(new ServerResponse(StatusLine.HTTP11_200_OK, 
				new MessageHeaders(new MessageHeader[]{ mh })));
			PrintWriter out = new PrintWriter(new OutputStreamWriter(server.getOutputStream()));
			out.println(result);
			out.flush();
		}
	}

	public void testEasyGet()
		throws Exception
	{
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		URL url = new URL("http://localhost:" + server.getPort());
		EasyHttpClient ec = new EasyHttpClient(url);
		BufferedReader br = ec.doGet();
		String got = HttpUtil.read(br).trim();
		assertEquals("got", got); 
		server.close();
	}

	private class EasyHttpClientFactory2 {
		EasyHttpClientFactory2()
			throws IOException
		{
		}
		public EasyHttpClient makePostClient(URL url) {
			return new EasyHttpClient(url, Method.POST);
		}
	}

	public void testEasyPost()
		throws Exception
	{
		ThreadedHttpServer server = new MyHttpServer();
		server.start();
		URL url = new URL("http://localhost:" + server.getPort());
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2();
		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();
		server.close();
	}

	public void testClientClose()
		throws IOException
	{
		ThreadedHttpServer server = new MyHttpServer();
		Socket s = new Socket("127.0.0.1", server.getPort());
		BasicHttpClient client = new BasicHttpClient(s.getOutputStream(), s.getInputStream());
		client.close();
		try {
			client.writeRequest(new ClientRequest(requestLine, messageHeaders));
			fail("should be closed");
		} catch (IllegalStateException e) { }
	}

	public void testClientServer()
		throws IOException
	{
		ThreadedHttpServer server = new MyHttpServer();
		server.start();
		Socket s = new Socket("127.0.0.1", server.getPort());
		BasicHttpClient client = new BasicHttpClient(s.getOutputStream(), s.getInputStream());
		client.writeRequest(new ClientRequest(requestLine, messageHeaders));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(new ChunkedOutputStream(s.getOutputStream())));
		String orig = "hello HttpServer";
		out.println(orig);
		out.flush();
		ClientResponse r = client.readResponse();
		BufferedReader in = new BufferedReader(new InputStreamReader(r.getInputStream()));
		String line = in.readLine();
		assertEquals("we got something", "got " + orig, line);
		server.close();
	}

	private static class ContinueHttpServer extends ThreadedHttpServer {
		public ContinueHttpServer() throws IOException {
			super();
		}
		@Override
		public void handleRequest(Request request)
			throws IOException
		{
			HttpServer server = request.getServer();
			server.readRequest();
			MessageHeader mh = new MessageHeader("whatever", "something");
			MessageHeaders mhs = new MessageHeaders();
			mhs.add(mh);
			server.writeResponse(new ServerResponse(StatusLine.HTTP11_100, mhs));
			StatusLine st = new StatusLine(199);
			server.writeResponse(new ServerResponse(st, mhs));
			server.writeResponse(new ServerResponse(StatusLine.HTTP11_200_OK, mhs));
		}
	}

	private static class RedirHttpServer extends ThreadedHttpServer {
		String url;
		ServerRequest r;
		boolean sendLocation = true;
		public RedirHttpServer(String url) throws IOException {
			this.url = url;
		}
		public RedirHttpServer(URL url) throws IOException {
			super();
			this.url = url.toString();
		}
		/**
		 * Reads one line and then redirects to a different location.
		 */
		public void handleRequest(Request request)
			throws IOException
		{
			HttpServer server = request.getServer();
			// System.out.println("ClientServerTest Reading request " + this);
			r = server.readRequest();
			// System.out.println("ClientServerTest Read request " + r);
			MessageHeader mh; 
			MessageHeader mh2; 
			r.getInputStream();

			String uri = r.getRequestLine().getRequestURI();
			if (uri.equals("/")) {
				mh = new MessageHeader(MessageHeader.FN_LOCATION, url);
				mh2 = new MessageHeader(MessageHeader.FN_CONTENT_LENGTH, "0");
				if (!sendLocation)
					mh = mh2;
				server.writeResponse(new ServerResponse(StatusLine.HTTP11_301, new MessageHeader[] { mh, mh2 }));
			} else {
				mh = new MessageHeader(MessageHeader.FN_CONTENT_LENGTH, "0");
				server.writeResponse(new ServerResponse(StatusLine.HTTP11_200_OK, new MessageHeader[] { mh }));
			}
			// System.out.println("ClientServerTest Wrote response " + StatusLine.HTTP11_301);
		}
	}

	private class MyRetryHttpClient extends RetryHttpClient
	{
		HttpClient c;
		URL url;
		MyRetryHttpClient(HttpClient c)
			throws MalformedURLException
		{
			super(new URL("http://localhost"));
			this.c = c;
		}
		protected HttpClient makeHttpClient(URL url) {
			this.url = url;
			return c;
		}
	}

	public void testRedirect()
		throws Exception
	{
		URL url = new URL("http://localhost/someplace");
		RedirHttpServer server = new RedirHttpServer(url);
		server.start();
		Socket s = new Socket("localhost", server.getPort());
		BasicHttpClient bclient = new BasicHttpClient(s);
		MyRetryHttpClient client = new MyRetryHttpClient(bclient);

		client.writeRequest(new ClientRequest(requestLine, new MessageHeaders()));
		client.readResponse();
		String URI = server.r.getRequestLine().getRequestURI();
		assertEquals("redir'ed did not change initial url", "http://localhost", client.url.toString());
		assertEquals("redir'ed url", "/someplace", URI);
	}

	public void testRedirect2()
		throws Exception
	{
		URL url = new URL("http://otherhost/someplace");
		RedirHttpServer server = new RedirHttpServer(url);
		server.start();
		MyHttpServer server2 = new MyHttpServer();
		server2.start();

		Socket s = new Socket("localhost", server.getPort());
		BasicHttpClient bclient = new BasicHttpClient(s.getOutputStream(), s.getInputStream());
		Socket s2 = new Socket("localhost", server2.getPort());
		server2.doRead = false;
		BasicHttpClient bclient2 = new BasicHttpClient(s2);

		MyRetryHttpClient client = new MyRetryHttpClient(bclient);
		client.writeRequest(new ClientRequest(requestLine, messageHeaders));
		client.c = bclient2;
		client.readResponse();
		assertTrue("make a request", server.r != null);
		String URI = server2.r.getRequestLine().getRequestURI();
		assertEquals("redir'ed url", "/someplace", URI);
		server.close();
		server2.close();
	}

	public void testRedirectLoop()
		throws Exception
	{
		System.out.println("testRedirectLoop");
		URL url = new URL("http://localhost/");
		RedirHttpServer server = new RedirHttpServer(url);
		server.start();
		Socket s = new Socket("localhost", server.getPort());
		BasicHttpClient bclient = new BasicHttpClient(s);
		MyRetryHttpClient client = new MyRetryHttpClient(bclient);

		client.writeRequest(new ClientRequest(requestLine, new MessageHeaders()));
		try {
			client.readResponse();
			fail("redirect loop");
		} catch (HttpException e) {
			// e.printStackTrace();
		}
		server.close();
	}

	public void testRedirectNoThanks()
		throws Exception
	{
		System.out.println("testRedirectNoThanks");
		URL url = new URL("http://localhost/");
		RedirHttpServer server = new RedirHttpServer(url);
		server.start();
		URL url2 = new URL("http://localhost:" + server.getPort());
		RetryHttpClient client = new RetryHttpClient(url2);
		client.setFollowRedirects(false);

		client.writeRequest(new ClientRequest(requestLine, new MessageHeaders()));
		Response r = client.readResponse();
		assertEquals(301, r.getStatusLine().getStatusCode());
		server.close();
	}

	public void testRedirectNoLocation()
		throws Exception
	{
		System.out.println("testRedirectNoThanks");
		URL url = new URL("http://localhost/");
		RedirHttpServer server = new RedirHttpServer(url);
		server.start();
		server.sendLocation = false;
		URL url2 = new URL("http://localhost:" + server.getPort());
		RetryHttpClient client = new RetryHttpClient(url2);
		client.setFollowRedirects(false);

		client.writeRequest(new ClientRequest(requestLine, new MessageHeaders()));
		Response r = client.readResponse();
		assertEquals(301, r.getStatusLine().getStatusCode());
		server.close();
	}

	public void testRedirectBadUrl()
		throws Exception
	{
		System.out.println("testRedirectBadUrl");
		RedirHttpServer server = new RedirHttpServer("bad://URL_here");
		server.start();
		URL url = new URL("http://localhost:" + server.getPort());
		RetryHttpClient client = new RetryHttpClient(url); 

		client.writeRequest(new ClientRequest(requestLine, new MessageHeaders()));
		try {
			client.readResponse();
			fail("redirect bad URL");
		} catch (HttpException e) {
			System.out.println("HTTP " + e + " " + client);
		} catch (RuntimeException e) {
			throw e;
		}

		System.out.println(" ETC " + client);
		client.writeRequest(new ClientRequest(requestLine, new MessageHeaders()));
		try {
			client.readResponse();
			fail("redirect loop");
		} catch (HttpException e) {
		}
	}

	public void testContinue()
		throws Exception
	{
		ContinueHttpServer server = new ContinueHttpServer();
		server.start();
		Socket s = new Socket("localhost", server.getPort());
		BasicHttpClient bclient = new BasicHttpClient(s);
		MyRetryHttpClient client = new MyRetryHttpClient(bclient);

		Response r;
		client.writeRequest(new ClientRequest(requestLine, mh));
		r = client.readResponse();
		assertEquals(200, r.getStatusLine().getStatusCode());

		client.writeRequest(new ClientRequest(requestPost, mh));
		r = client.readResponse();
		assertEquals(200, r.getStatusLine().getStatusCode());

		client.setSkipContinues(false);
		client.writeRequest(new ClientRequest(requestLine, mh));
		r = client.readResponse();
		assertEquals(100, r.getStatusLine().getStatusCode());
	}

	public void testClose()
		throws Exception
	{
		MyHttpServer server = new MyHttpServer();
		server.start();
		server.doRead = false;
		Socket s;
		
		s = new Socket("localhost", server.getPort());
		BasicHttpClient bclient;
		bclient = new BasicHttpClient(s);
		bclient.writeRequest(new ClientRequest(requestLine, mh));
		bclient.readResponse();
		try {
			bclient.readResponse();
			fail("closed, cannot write");
		} catch (IllegalStateException e) { }
		bclient.close();

		s = new Socket("localhost", server.getPort());
		bclient = new BasicHttpClient(s);
		MyRetryHttpClient rclient = new MyRetryHttpClient(bclient);
		rclient.writeRequest(new ClientRequest(requestLine, mh));
		rclient.close();
		try {
			rclient.readResponse();
			fail("closed, cannot read2");
		} catch (IllegalStateException e) { }
		rclient.close();

		URL url = new URL("http://localhost:" + server.getPort());
		EasyHttpClientFactory2 factory = new EasyHttpClientFactory2();
		EasyHttpClient ec = factory.makePostClient(url);
		ec.close();

		server.close();
	}

	public void testFailToConnect()
		throws Exception
	{
		MyHttpServer server = new MyHttpServer();
		URL http = new URL("http://localhost:" + (server.getPort() + 10));
	        RetryHttpClient rhc = new RetryHttpClient(http);
		try {
			rhc.writeRequest(new ClientRequest(requestLine, messageHeaders));
			fail("protocol");
		} catch (IOException e) {}
	}

	public void testVariousConstructor()
		throws Exception
	{
		RetryHttpClient rhc;

		URL ftp = new URL("file:yo");
	        rhc = new RetryHttpClient(ftp);
		try {
			rhc.writeRequest(new ClientRequest(requestLine, messageHeaders));
			fail("protocol");
		} catch (IOException e) {}
		rhc.close();

		URL https = new URL("https://sourceforge.net");
	        rhc = new RetryHttpClient(https);
		rhc.writeRequest(new ClientRequest(requestLine, messageHeaders));

		URL http = new URL("http://sourceforge.net");
	        rhc = new RetryHttpClient(http);
		rhc.writeRequest(new ClientRequest(requestLine, messageHeaders));
		rhc.close();
	}

	public static void main(String s[]) throws Exception {
		new ClientServerTest("testRedirectBadUrl").testRedirectBadUrl();
	}

}
