/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (C) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 * Copyright (C) 2009 John Lewis
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Ant" and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package net.sourceforge.cobertura.test.util;

import static org.junit.Assert.*;

import groovy.lang.Closure;
import net.sourceforge.cobertura.ant.InstrumentTask;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.taskdefs.*;
import org.apache.tools.ant.types.Environment.Variable;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ZipFileSet;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class WebappServer {
	public static final String SIMPLE_SERVLET_CLASSNAME = "com.acme.servlet.SimpleServlet";

	private static final String JETTY_DIR = "src/test/resources/jetty";

	public static final String SIMPLE_SERVLET_TEXT = "\n package com.acme.servlet;"
			+ "\n "
			+ "\n import javax.servlet.http.HttpServlet;"
			+ "\n import javax.servlet.http.HttpServletRequest;"
			+ "\n import javax.servlet.http.HttpServletResponse;"
			+ "\n "
			+ "\n import java.io.PrintWriter;"
			+ "\n import java.io.IOException;"
			+ "\n "
			+ "\n public class SimpleServlet extends HttpServlet"
			+ "\n {"
			+ "\n  protected void doGet(HttpServletRequest req, HttpServletResponse response) throws IOException"
			+ "\n  {"
			+ "\n   response.setContentType(\"text/html;charset=UTF-8\");"
			+ "\n   PrintWriter out = response.getWriter();"
			+ "\n   try"
			+ "\n   {"
			+ "\n    out.println(\"Hi\");"
			+ "\n   }"
			+ "\n   finally"
			+ "\n   {"
			+ "\n    if (out != null)"
			+ "\n    {"
			+ "\n     out.close();" + "\n    }" + "\n   }" + "\n  }" + "\n }";

	public static final String SIMPLE_SERVLET_WEB_XML_TEXT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
			+ "\n <web-app version=\"2.4\" xmlns=\"http://java.sun.com/xml/ns/j2ee\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd\">"
			+ "\n  <servlet>"
			+ "\n   <servlet-name>SimpleServlet</servlet-name>"
			+ "\n   <servlet-class>com.acme.servlet.SimpleServlet</servlet-class>"
			+ "\n  </servlet>"
			+ "\n  <servlet-mapping>"
			+ "\n   <servlet-name>SimpleServlet</servlet-name>"
			+ "\n   <url-pattern>/SimpleServlet</url-pattern>"
			+ "\n  </servlet-mapping>" + "\n </web-app>";

	private static final String LOCALHOST = "127.0.0.1";
	private static final String WEB_XML = "src/main/resources/net/sourceforge/cobertura/webapp/web.xml";
	private static final String SRC_DIR = "src/main/java";
	private static final String appName = "simple";
	private int webappPort;
	private int stopPort;

	File dir = new File(TestUtils.getTempDir(), "webserver");
	boolean modifyMainCoberturaDataFile;
	boolean tomcat;

	public WebappServer(File webappServerDir, boolean tomcat) {
		this.tomcat = tomcat;
		ServerSocket s;
		try {
			s = new ServerSocket(0);
			webappPort = s.getLocalPort();
			s.close();

			s = new ServerSocket(0);
			stopPort = s.getLocalPort();
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void deployApp(File srcDir, boolean instrumentCobertura,
			boolean deployCoberturaFlush, boolean modifyMainCoberturaDataFile,
			String instrumentRegEx) throws Exception {
		this.modifyMainCoberturaDataFile = modifyMainCoberturaDataFile;

		File extractedDir = new File(dir, "extracted");
		File webInfDir = new File(extractedDir, "WEB-INF");
		File classesDir = new File(webInfDir, "classes");

		File webInfFile = writeWebInfFile(webInfDir,
				SIMPLE_SERVLET_WEB_XML_TEXT);

		compileSourceFiles(srcDir, classesDir);

		copyJettyFiles(dir);

		File war = makeWarFile(appName, webInfFile, classesDir);

		FileUtils.deleteDirectory(extractedDir);

		File coberturaJar = createCoberturaJar();

		if (instrumentRegEx != null) {
			instrumentWar(war, instrumentRegEx);
		}

		if (instrumentCobertura != false) {
			instrumentCoberturaJar(coberturaJar);
		}

		if (deployCoberturaFlush != false) {
			deployCoberturaFlush(deployCoberturaFlush);
		}
	}

	public void deployApp(File srcDir, boolean instrumentCobertura,
			boolean deployCoberturaFlush, boolean modifyMainCoberturaDataFile)
			throws Exception {
		deployApp(srcDir, instrumentCobertura, deployCoberturaFlush,
				modifyMainCoberturaDataFile, "");
	}

	public void deployApp(File srcDir, boolean deployCoberturaFlush,
			String instrumentRegEx) throws Exception {
		deployApp(srcDir, false, deployCoberturaFlush, false, instrumentRegEx);
	}

	public void deployApp(File srcDir, String instrumentRegEx) throws Exception {
		deployApp(srcDir, false, instrumentRegEx);
	}

	public void deployApp(File srcDir) throws Exception {
		deployApp(srcDir, "");
	}

	private File writeWebInfFile(File webInfDir, String text)
			throws IOException {
		assertTrue(webInfDir.mkdirs());
		File webInfFile = new File(webInfDir, "web.xml");

		FileUtils.writeStringToFile(webInfFile, text);
		return webInfFile;
	}

	private void compileSourceFiles(File srcDir, File classesDir) {
		Mkdir mkdir = new Mkdir();
		mkdir.setProject(TestUtils.project);
		mkdir.setDir(classesDir);
		mkdir.execute();

		Javac javac = new Javac();
		javac.setProject(TestUtils.project);
		javac.setSrcdir(new Path(TestUtils.project, srcDir.getAbsolutePath()));
		javac.setDestdir(classesDir);
		javac.setDebug(true);
		javac.setTarget("1.7");

		Path classpath = new Path(TestUtils.project);
		FileSet fileSet = new FileSet();
		fileSet.setDir(new File(JETTY_DIR));
		fileSet.setIncludes("**/*.jar");
		classpath.addFileset(fileSet);

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

	private void copyJettyFiles(File todir) {
		Copy copy = new Copy();
		copy.setProject(TestUtils.project);
		copy.setTodir(todir);
		FileSet fileSet = new FileSet();
		fileSet.setProject(TestUtils.project);
		fileSet.setDir(new File(JETTY_DIR));
		copy.addFileset(fileSet);
		copy.execute();
	}

	private File makeWarFile(String appName, File webInfFile, File classesDir) {
		File webappsDir = new File(dir, "webapps");
		File war = new File(webappsDir, appName + ".war");

		War antWar = new War();
		antWar.setProject(TestUtils.project);
		antWar.setDestFile(war);
		antWar.setWebxml(webInfFile);
		ZipFileSet zipFileSet = new ZipFileSet();
		zipFileSet.setDir(classesDir);
		antWar.addClasses(zipFileSet);
		antWar.execute();

		return war;
	}

	private void deployCoberturaFlush(boolean instrumentCobertura)
			throws Exception {
		File webappsDir = new File(dir, "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_DIR));
		javac.setDestdir(classesDir);
		javac.setDebug(true);

		Path classpath = new Path(TestUtils.project);
		FileSet jettyFileSet = new FileSet();
		jettyFileSet.setDir(new File(JETTY_DIR));
		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(WEB_XML));

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

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

		if (instrumentCobertura) {
			instrumentWar(war, "net.sourceforge.cobertura.*");
		}
	}

	private void instrumentWar(File war, String instrumentRegEx) {
		InstrumentTask instrumentTask = new InstrumentTask();
		instrumentTask.setProject(TestUtils.project);
		instrumentTask.setDataFile(dir.getAbsolutePath() + "/cobertura.ser");
		instrumentTask.createIncludeClasses().setRegex(instrumentRegEx);
		instrumentTask.createExcludeClasses().setRegex(".*Test.*");
		System.out.println(war.getAbsolutePath());
		FileSet fileSet = new FileSet();
		fileSet.setDir(war.getParentFile());
		fileSet.setIncludes("**/*.war");

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

	private void instrumentCoberturaJar(File coberturaJar) {
		InstrumentTask instrumentTask = new InstrumentTask();
		instrumentTask.setProject(TestUtils.project);
		instrumentTask.setDataFile(dir.getAbsolutePath() + "/cobertura.ser");
		instrumentTask.createIncludeClasses().setRegex(
				"net.sourceforge.cobertura.*");
		instrumentTask.createExcludeClasses().setRegex(".*Test.*");

		FileSet fileSet = new FileSet();
		fileSet.setDir(new File(coberturaJar.getParent()));
		fileSet.setIncludes(coberturaJar.getName());

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

	/**
	 * Starts the web server and calls the closure.   The web server will be stopped 
	 * when the closure exits (successfully or not).
	 * @throws Exception 
	 * 
	 */
	public void withRunningServer() throws Exception {
		startWebServer();
		Map<String, Object> data = new HashMap<String, Object>();
		try {
			data.put("xmlReport", new File(dir.getAbsolutePath()
					+ "/coverage.xml"));
			data.put("hostname", LOCALHOST);
			data.put("webappPort", webappPort);
			data.put("coberturaAnt", TestUtils.getCoberturaAntBuilder(TestUtils
					.getTempDir()));

			data.put("datafile", getDatafileToUse());

			/*
			 * add a closure that can be called to flush the cobertura data
			 */
			Closure closure = new Closure(null) {
				private static final long serialVersionUID = 1L;

				@SuppressWarnings("unused")
				public void doCall(HashMap<String, Object> values) {
				}
			};

			System.err.println(closure.call(data));
		} finally {
		}
	}

	public void killServer() {
		stopWebServer();
	}

	private File getDatafileToUse() {
		File datafile = new File(dir.getAbsolutePath() + "/cobertura.ser");

		if (modifyMainCoberturaDataFile) {
			/*
			 * modify the cobertura.ser file created by the instrumentation target
			 * of the main build.xml file.   That way, the counts
			 * will end up in the overall coverage report
			 */
			File tempfile = new File("target/build/cobertura.ser");
			if (tempfile.exists()) {
				datafile = tempfile;
			} else {
				/*
				 * It looks like the main build.xml's instrument target has not been run.
				 * So, a coverage report is not being generated.
				 */
			}
		}
		return datafile;
	}

	private void startWebServer() {
		System.out.println("Starting Jetty webapp server on " + webappPort);

		Thread t = new Thread() {
			@Override
			public void run() {
				Java java = new Java();
				java.setProject(TestUtils.project);
				java.setDir(dir);
				java.setJar(new File(dir, "start.jar"));
				java.setFork(true);

				Variable jettyPort = new Variable();
				jettyPort.setKey("jetty.port");
				jettyPort.setValue(String.valueOf(webappPort));
				java.addSysproperty(jettyPort);

				Variable stopPortVariable = new Variable();
				stopPortVariable.setKey("STOP.PORT");
				stopPortVariable.setValue(String.valueOf(stopPort));
				java.addSysproperty(stopPortVariable);

				Variable stopKey = new Variable();
				stopKey.setKey("STOP.KEY");
				stopKey.setValue("cobertura");
				java.addSysproperty(stopKey);

				if (tomcat) {
					Variable catalinaHome = new Variable();
					catalinaHome.setKey("catalina.home");
					catalinaHome.setValue(dir.getAbsolutePath());
					java.addSysproperty(catalinaHome);
				}

				if (modifyMainCoberturaDataFile) {
					Variable dataFile = new Variable();
					dataFile.setKey("net.sourceforge.cobertura.datafile");
					dataFile.setValue(getDatafileToUse().getAbsolutePath());
					java.addSysproperty(dataFile);
				}

				java.execute();
			}
		};
		t.start();

		try {
			Thread.sleep(10 * 1000); // Sleep for 10 seconds. The servlet needs a few seconds to boot up.
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void stopWebServer() {
		Thread t = new Thread() {
			@Override
			public void run() {
				Java java = new Java();
				java.setProject(TestUtils.project);
				java.setJar(new File(TestUtils.getTempDir(),
						"webserver/start.jar"));
				java.setDir(dir);
				java.setFork(true);
				java.setArgs("--stop");
				Variable stopPortVariable = new Variable();
				Variable stopKeyVariable = new Variable();

				stopPortVariable.setKey("STOP.PORT");
				stopPortVariable.setValue(String.valueOf(stopPort));

				stopKeyVariable.setKey("STOP.KEY");
				stopKeyVariable.setValue("cobertura");

				java.addSysproperty(stopPortVariable);
				java.addSysproperty(stopKeyVariable);

				java.execute();

				java.execute();
			}
		};
		t.start();
	}

	private File createCoberturaJar() {
		File coberturaJar = new File(dir, "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();

		return coberturaJar;
	}

	public static void writeSimpleServletSource(File srcDir) throws IOException {
		File servletSourceFile = new File(srcDir,
				"com/acme/servlet/SimpleServlet.java");
		assertTrue(servletSourceFile.getParentFile().mkdirs());

		FileUtils.write(servletSourceFile, SIMPLE_SERVLET_TEXT);
	}

	public void pingServer() {
		String webappResponse = null;
		try {
			webappResponse = IOUtils.toString(new URL("http://localhost:"
					+ webappPort + "/" + appName + "/SimpleServlet")
					.openConnection().getInputStream());
		} catch (MalformedURLException e) {
			e.printStackTrace();
			fail(e.getMessage());
		} catch (IOException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

		assertNotNull(webappResponse);
		assertEquals("Webapp response was incorrect", "Hi", webappResponse
				.trim());
	}

	public void pingCoberturaServer() {
		//flush the cobertura data by doing an HTTP get
		String flushing = null;
		try {
			flushing = IOUtils.toString(new java.net.URL("http://localhost:"
					+ webappPort + "/coberturaFlush/flushCobertura")
					.openConnection().getInputStream());
		} catch (MalformedURLException e) {
			e.printStackTrace();
			fail(e.getMessage());
		} catch (IOException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

		assertNotNull(flushing);
		assertEquals("", flushing.trim());

	}

	public File getXmlReport() {
		return new File(dir.getAbsolutePath(), "coverage.xml");
	}
}
