package net.sourceforge.cobertura.webapp.test;

import static org.junit.Assert.*;

import groovy.util.Node;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;

import net.sourceforge.cobertura.ant.InstrumentTask;
import net.sourceforge.cobertura.ant.ReportTask;
import net.sourceforge.cobertura.test.util.TestUtils;
import net.sourceforge.cobertura.webapp.FlushCoberturaServlet;

import org.apache.commons.io.FileUtils;

import org.apache.tools.ant.taskdefs.Javac;
import org.apache.tools.ant.taskdefs.War;
import org.apache.tools.ant.taskdefs.Zip;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ZipFileSet;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.mortbay.jetty.testing.HttpTester;
import org.mortbay.jetty.testing.ServletTester;

/**
 * 
 * This test tests the functional FlushCoberturaServlet used for jetty sevlets.
 * 
 * We use the ServletTester API to remove the sensitive tests that were in
 * the previous versions of cobertura.
 * 
 * @author schristou88
 *
 */
public class WebAppFunctionalTest {
	ServletTester tester;
	HttpTester request;
	HttpTester response;
	File tempDir = TestUtils.getTempDir();
	public static final String SIMPLE_SERVLET_CLASSNAME = "com.acme.servlet.SimpleServlet";
	private static final String SAVE_DATA_METHOD_NAME = "doGet";

	@Before
	public void setUp() throws Exception {
		FileUtils.deleteDirectory(tempDir);
		FileUtils.deleteQuietly(new File("cobertura.ser"));
		FileUtils.deleteQuietly(new File(tempDir.getAbsolutePath(),
				"coverage.xml"));
	}

	@After
	public void tearDown() throws Exception {
		if (tester != null)
			tester.stop();
	}

	@Test
	public void testServletCreation() throws Exception {
		startUpServlet();
		pingServer();
	}

	@Test
	public void testCoberturaServlet() throws Exception {
		startUpServlet();
		pingCoberturaServer();
	}

	@Test
	public void testFlushCoberturaData() throws Exception {
		createSimpleWar();

		createCoberturaServlet();

		createCoberturaJar();

		instrumentWar();

		startUpServlet();

		pingServer();

		generateReportFile();

		Node dom = TestUtils.getXMLReportDOM(new File(
				tempDir.getAbsolutePath(), "coverage.xml"));

		int hitCountBefore = TestUtils.getHitCount(dom,
				SIMPLE_SERVLET_CLASSNAME, SAVE_DATA_METHOD_NAME);

		assertEquals(0, hitCountBefore);

		pingCoberturaServer();

		generateReportFile();

		dom = TestUtils.getXMLReportDOM(new File(tempDir.getAbsolutePath(),
				"coverage.xml"));

		int hitCountAfter = TestUtils.getHitCount(dom,
				SIMPLE_SERVLET_CLASSNAME, SAVE_DATA_METHOD_NAME);

		tester.stop();

		assertEquals("Hit count should have increased by one",
				hitCountBefore + 1, hitCountAfter);
	}

	@Test
	public void testFlushCoberturaData2() throws Exception {
		createSimpleWar();

		createCoberturaServlet();

		createCoberturaJar();

		instrumentWar();

		startUpServlet();

		pingServer();

		generateReportFile();

		Node dom = TestUtils.getXMLReportDOM(new File(
				tempDir.getAbsolutePath(), "coverage.xml"));

		int hitCountBefore = TestUtils.getHitCount(dom,
				SIMPLE_SERVLET_CLASSNAME, SAVE_DATA_METHOD_NAME);

		assertEquals(0, hitCountBefore);

		pingCoberturaServer();

		generateReportFile();

		dom = TestUtils.getXMLReportDOM(new File(tempDir.getAbsolutePath(),
				"coverage.xml"));

		int hitCountAfter = TestUtils.getHitCount(dom,
				SIMPLE_SERVLET_CLASSNAME, SAVE_DATA_METHOD_NAME);

		assertEquals(2, hitCountAfter);

		tester.stop();

		generateReportFile();

		dom = TestUtils.getXMLReportDOM(new File(tempDir.getAbsolutePath(),
				"coverage.xml"));

		int hitCountFinal = TestUtils.getHitCount(dom,
				SIMPLE_SERVLET_CLASSNAME, SAVE_DATA_METHOD_NAME);

		assertEquals(hitCountAfter, hitCountFinal);
	}

	@Test
	public void testFlushCoberturaDataOnly() throws Exception {
		createSimpleWar();

		createCoberturaServlet();

		createCoberturaJar();

		instrumentWar();

		startUpServlet();

		generateReportFile();

		Node dom = TestUtils.getXMLReportDOM(new File(
				tempDir.getAbsolutePath(), "coverage.xml"));

		int hitCountBefore = TestUtils.getHitCount(dom,
				SIMPLE_SERVLET_CLASSNAME, SAVE_DATA_METHOD_NAME);

		assertEquals(0, hitCountBefore);

		generateReportFile();

		dom = TestUtils.getXMLReportDOM(new File(tempDir.getAbsolutePath(),
				"coverage.xml"));

		int hitCountAfter = TestUtils.getHitCount(dom,
				SIMPLE_SERVLET_CLASSNAME, SAVE_DATA_METHOD_NAME);

		assertEquals(0, hitCountAfter);

		tester.stop();

		generateReportFile();

		dom = TestUtils.getXMLReportDOM(new File(tempDir.getAbsolutePath(),
				"coverage.xml"));

		int finalCount = TestUtils.getHitCount(dom, SIMPLE_SERVLET_CLASSNAME,
				SAVE_DATA_METHOD_NAME);

		assertEquals(0, finalCount);

	}

	public void startUpServlet() throws Exception {
		tester = new ServletTester();
		request = new HttpTester();
		response = new HttpTester();

		ClassLoader loader = createClassLoader();
		tester.setClassLoader(loader);

		tester.addServlet("com.acme.servlet.SimpleServlet",
				"/simple/SimpleServlet");
		tester.addServlet(FlushCoberturaServlet.class,
				"/coberturaFlush/flushCobertura");
		tester.start();

		request.setMethod("GET");
		request.setHeader("host", "tester");
		request.setVersion("HTTP/1.0");
	}

	public void pingServer() throws Exception {
		request.setURI("/simple/SimpleServlet");
		response.parse(tester.getResponses(request.generate()));
		assertNull(response.getMethod());
		assertEquals(200, response.getStatus());
		assertEquals("Hi", response.getContent().trim());
	}

	public void pingCoberturaServer() throws Exception {
		request.setURI("/coberturaFlush/flushCobertura");
		response.parse(tester.getResponses(request.generate()));
		assertNull(response.getMethod());
		assertEquals(200, response.getStatus());
		assertNull(response.getContent());
	}

	private void createSimpleWar() throws IOException {
		File webappsDir = new File(tempDir, "webapps");
		File war = new File(webappsDir, "simple.war");

		File classesDir = new File("target/build/simpleWarClasses/");
		if (!classesDir.exists())
			classesDir.mkdirs();

		FileUtils.copyDirectory(new File("target/test-classes/com"), new File(
				classesDir, "com"));

		War antWar = new War();
		antWar.setProject(TestUtils.project);
		antWar.setDestFile(war);
		antWar.setWebxml(new File("src/test/resources/testJetty/web.xml"));

		ZipFileSet classesFileSet = new ZipFileSet();
		classesFileSet.setDir(classesDir);

		antWar.addClasses(classesFileSet);
		antWar.execute();
	}

	private void createCoberturaServlet() {
		File webappsDir = new File(TestUtils.getTempDir(), "webapps");
		File war = new File(webappsDir, "coberturaFlush.war");

		File classesDir = new File("target/build/warClasses");
		if (!classesDir.exists())
			classesDir.mkdirs();
		Javac javac = new Javac();
		javac.setProject(TestUtils.project);
		javac.setSrcdir(new Path(TestUtils.project, "src/main/java"));
		javac.setDestdir(classesDir);
		javac.setDebug(true);

		Path classpath = new Path(TestUtils.project);
		FileSet jettyFileSet = new FileSet();
		jettyFileSet.setDir(new File("src/test/resources/jetty"));
		jettyFileSet.setIncludes("**/*.jar");
		classpath.addFileset(jettyFileSet);

		javac.setIncludes("**/FlushCoberturaServlet.java");

		javac.setClasspath(classpath);
		javac.execute();

		War antWar = new War();
		antWar.setProject(TestUtils.project);
		antWar.setDestFile(war);
		antWar.setWebxml(new File(
				"src/main/resources/net/sourceforge/cobertura/webapp/web.xml"));

		ZipFileSet classesFileSet = new ZipFileSet();
		classesFileSet.setDir(classesDir);

		antWar.addClasses(classesFileSet);
		antWar.execute();
	}

	private ClassLoader createClassLoader() throws Exception {
		File simplewar = new File(tempDir, "webapps/simple.war");
		File coberturawar = new File(tempDir, "webapps/coberturaFlush.war");
		File coberturaJar = new File(tempDir, "lib/cobertura.jar");

		@SuppressWarnings("deprecation")
		URL[] urls = new URL[]{simplewar.toURL(), coberturawar.toURL(),
				coberturaJar.toURL()};

		System.out.println(Arrays.toString(urls));

		// Create a new class loader with the directory

		return new URLClassLoader(urls);
	}

	private void createCoberturaJar() {
		File coberturaJar = new File(tempDir, "lib/cobertura.jar");
		File coberturaClassDir = TestUtils.getCoberturaClassDir();

		Zip zip = new Zip();
		zip.setProject(TestUtils.project);
		zip.setDestFile(coberturaJar);

		FileSet fileSet = new FileSet();
		fileSet.setDir(coberturaClassDir);

		zip.addFileset(fileSet);
		zip.execute();
	}

	private void instrumentWar() {
		instrumentWar(new File(tempDir, "webapps/simple.war"));
		// Future: There is an issue with ServetTester api and adding a custom classloader.
		// When specify the instrumented .war file it still prefers to use the
		// target/test-classes/**/*.class files instead. In this situation we instrument the
		// classes directly but a better solution should be provided for this.
		instrumentClasses(new File(
				"target/test-classes/com/acme/servlet/SimpleServlet.class"));
	}

	private void instrumentClasses(File classesDir) {
		InstrumentTask instrumentTask = new InstrumentTask();
		instrumentTask.setProject(TestUtils.project);
		instrumentTask.setDataFile("cobertura.ser");
		FileSet fileSet = new FileSet();
		fileSet.setDir(classesDir.getParentFile());
		fileSet.setIncludes("**/*.class");

		instrumentTask.addFileset(fileSet);
		instrumentTask.execute();
	}

	private void instrumentWar(File warFile) {
		InstrumentTask instrumentTask = new InstrumentTask();
		instrumentTask.setProject(TestUtils.project);
		instrumentTask.setDataFile("cobertura.ser");
		FileSet fileSet = new FileSet();
		fileSet.setDir(warFile.getParentFile());
		fileSet.setIncludes("**/*.war");

		instrumentTask.addFileset(fileSet);
		instrumentTask.execute();
	}

	public void generateReportFile() {
		File xmlReport = new File(tempDir.getAbsolutePath(), "coverage.xml");

		ReportTask reportTask = new ReportTask();
		reportTask.setProject(TestUtils.project);
		reportTask.setDataFile("cobertura.ser");
		reportTask.setFormat("xml");
		reportTask.setDestDir(new File(xmlReport.getParent()));
		reportTask.execute();
	}
}
