/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (C) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 * Copyright (C) 2010 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;

import groovy.util.AntBuilder;
import groovy.util.Node;
import net.sourceforge.cobertura.ant.ReportTask;
import net.sourceforge.cobertura.test.util.TestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.taskdefs.Java;
import org.junit.Test;

import java.io.File;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class SwitchFunctionalTest extends AbstractCoberturaTestCase {
	AntBuilder ant = TestUtils.getCoberturaAntBuilder(TestUtils
			.getCoberturaClassDir());

	@Test
	public void noDefaultTest() throws Exception {
		/*
		 * Use a temporary directory and create a Main.java source file that
		 * has a switch statement with no default.
		 */
		mainSourceFile.getParentFile().mkdirs();

		/*
		 * Note that if the code below is changed, the line numbers in the
		 * asserts at the bottom of this method will likely need to be adjusted.
		 */
		FileUtils
				.write(
						mainSourceFile,
						"\n package mypackage;"
								+ "\n "
								+ "\n public class Main {"
								+ "\n   public void callNoDefaultSwitch() {"
								+ "\n    int i=2;"
								+ "\n    switch (i) {"
								+ "\n     case 1:"
								+ "\n      System.out.println(\"1\");"
								+ "\n      break;"
								+ "\n     case 2:"
								+ "\n      System.out.println(\"2\");"
								+ "\n     break;"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchFallThrough() {"
								+ "\n   switchFallThrough(5);"
								+ "\n   switchFallThrough(1);"
								+ "\n  }"
								+ "\n "
								+ "\n  private void switchFallThrough(int i) {"
								+ "\n   switch (i) {"
								+ "\n    case 2:"
								+ "\n     System.out.println(\"2\");"
								+ "\n     break;"
								+ "\n    case 3:"
								+ "\n     break;"
								+ "\n    case 1: // fall-through!"
								+ "\n    default:"
								+ "\n     System.out.println(\"1 or default\");"
								+ "\n   }"
								+ "\n  }"
								+ "\n     "
								+ "\n  public void callSwitchWithDefault() {"
								+ "\n   switchWithDefault(15);"
								+ "\n  }"
								+ "\n "
								+ "\n  private void switchWithDefault(int i) {"
								+ "\n   switch (i) {"
								+ "\n    case 15:"
								+ "\n     System.out.println(\"1\");"
								+ "\n     break;"
								+ "\n    case 16:"
								+ "\n     System.out.println(\"2\");"
								+ "\n     break;"
								+ "\n    case 17:"
								+ "\n     System.out.println(\"3\");"
								+ "\n     break;"
								+ "\n    case 18:"
								+ "\n     System.out.println(\"4\");"
								+ "\n     break;"
								+ "\n     // intentionally skip 19 and 20"
								+ "\n    case 21:"
								+ "\n     System.out.println(\"5\");"
								+ "\n     break;"
								+ "\n    /*"
								+ "\n     * The compiler will add cases for any numbers that are skipped."
								+ "\n     * Note the next two cases are commented out, but the compiler"
								+ "\n     * adds them to the default case."
								+ "\n     */"
								+ "\n    //case 19:"
								+ "\n    //case 20:"
								+ "\n    default:"
								+ "\n     System.out.println(\"default\");"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchWithoutGaps() {"
								+ "\n   switchWithoutGaps(1);"
								+ "\n  }"
								+ "\n "
								+ "\n  private void switchWithoutGaps(int i) {"
								+ "\n   boolean aBoolean = false;"
								+ "\n "
								+ "\n   switch (i) {                   // tests assume this is line 76"
								+ "\n    // these cases have no gaps - the numbers are sequential"
								+ "\n    case 0:"
								+ "\n     System.out.println(\"0\");"
								+ "\n     break;"
								+ "\n    case 1:"
								+ "\n     System.out.println(\"1\");"
								+ "\n     break;"
								+ "\n    case 2:"
								+ "\n     System.out.println(\"2\");"
								+ "\n     break;"
								+ "\n    default:"
								+ "\n     System.out.println(\"default\");"
								+ "\n     break;"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchWithoutGapsWithFallThrough() {"
								+ "\n   switchWithoutGapsWithFallThrough(1);"
								+ "\n  }"
								+ "\n "
								+ "\n  private void switchWithoutGapsWithFallThrough(int i) {"
								+ "\n   boolean aBoolean = false;"
								+ "\n "
								+ "\n   switch (i) {                   // tests assume this is line 100"
								+ "\n   // these cases have no gaps - the numbers are sequential"
								+ "\n    case 0:"
								+ "\n     System.out.println(\"0\");"
								+ "\n     break;"
								+ "\n    case 1:"
								+ "\n     if (aBoolean) {"
								+ "\n      System.out.println(\"1\");"
								+ "\n      // the break is intentionally put inside this block to cause a fall-through"
								+ "\n      break;"
								+ "\n     }"
								+ "\n    case 2:"
								+ "\n     System.out.println(\"2\");"
								+ "\n     break;"
								+ "\n    default:"
								+ "\n     System.out.println(\"default\");"
								+ "\n     break;"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n  public enum AnEnumeration {"
								+ "\n   FOO, BAR, GONK"
								+ "\n  };"
								+ "\n "
								+ "\n  public void callSwitchBug2075537() {"
								+ "\n   switchBug2075537(AnEnumeration.FOO);"
								+ "\n   switchBug2075537(AnEnumeration.BAR);"
								+ "\n   switchBug2075537(AnEnumeration.GONK);"
								+ "\n  }"
								+ "\n "
								+ "\n  public boolean switchBug2075537(AnEnumeration e) {"
								+ "\n   // see bug http://sourceforge.net/tracker/?func=detail&aid=2075537&group_id=130558&atid=720015"
								+ "\n   switch (e) {                     // tests assume this is line 132"
								+ "\n    case FOO:"
								+ "\n    case BAR:"
								+ "\n    case GONK:"
								+ "\n     return true;"
								+ "\n "
								+ "\n    default:"
								+ "\n     return false;"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchBug2075537_2() {"
								+ "\n   switchBug2075537_2(AnEnumeration.FOO);"
								+ "\n   switchBug2075537_2(AnEnumeration.BAR);"
								+ "\n   switchBug2075537_2(AnEnumeration.GONK);"
								+ "\n  }"
								+ "\n "
								+ "\n  public boolean switchBug2075537_2(AnEnumeration e) {"
								+ "\n   switch (e) {                     // tests assume this is line 150"
								+ "\n    case FOO:"
								+ "\n     return true;"
								+ "\n "
								+ "\n    case BAR:"
								+ "\n     return true;"
								+ "\n "
								+ "\n    case GONK:"
								+ "\n     return true;"
								+ "\n "
								+ "\n    default:"
								+ "\n     return false;"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchNonDefaultFallThrough() {"
								+ "\n   switchNonDefaultFallThrough(AnEnumeration.FOO);"
								+ "\n  }"
								+ "\n "
								+ "\n  public boolean switchNonDefaultFallThrough(AnEnumeration e) {"
								+ "\n   switch (e) {                     // tests assume this is line 170"
								+ "\n    case FOO:"
								+ "\n     System.out.println(\"FOO\");"
								+ "\n "
								+ "\n    case BAR:"
								+ "\n     System.out.println(\"BAR\");"
								+ "\n     return true;"
								+ "\n "
								+ "\n    case GONK:"
								+ "\n     return true;"
								+ "\n "
								+ "\n    default:"
								+ "\n     return false;"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchWithBoolean() {"
								+ "\n   switchWithBoolean(1, false);"
								+ "\n   switchWithBoolean(3, false);"
								+ "\n   switchWithBoolean(7, true);"
								+ "\n   switchWithBoolean(7, false);"
								+ "\n   switchWithBoolean(8, true);"
								+ "\n  }"
								+ "\n "
								+ "\n  public void switchWithBoolean(int type, boolean aBoolean) {"
								+ "\n   switch (type) {                     // tests assume this is line 195"
								+ "\n    case 9:{"
								+ "\n     System.out.println(\"9\");"
								+ "\n     break;"
								+ "\n    }"
								+ "\n    case 1:{"
								+ "\n     try {"
								+ "\n      System.out.println(\"1\");"
								+ "\n     } catch (Exception e) {}"
								+ "\n     break;                     // tests assume this is line 204"
								+ "\n    }"
								+ "\n    case 5:{"
								+ "\n     System.out.println(\"5\");"
								+ "\n     break;"
								+ "\n    }"
								+ "\n    case 4:{"
								+ "\n     System.out.println(\"4\");"
								+ "\n     break;"
								+ "\n    }"
								+ "\n    case 3:{"
								+ "\n     System.out.println(\"3\");"
								+ "\n     break;"
								+ "\n    }"
								+ "\n    case 7:{"
								+ "\n     if (aBoolean) {"
								+ "\n      System.out.println(\"7\");"
								+ "\n      break;"
								+ "\n     }"
								+ "\n     // if aBoolean == false, this will fall through to the next case."
								+ "\n    }"
								+ "\n    case 8:{"
								+ "\n     if (aBoolean) {"
								+ "\n      System.out.println(\"8\");"
								+ "\n      break;"
								+ "\n     }"
								+ "\n    }"
								+ "\n   }"
								+ "\n  }"
								+ "\n "
								+ "\n enum ABCDEnum { A,B,C,D;};"
								+ "\n "
								+ "\n  public void switchWithEnum(ABCDEnum value) {"
								+ "\n   switch (value) {			// tests assume this is line 237"
								+ "\n    case B: "
								+ "\n     System.out.println(\"B\");"
								+ "\n     break;"
								+ "\n    case C:"
								+ "\n     System.out.println(\"C\");"
								+ "\n    case A:"
								+ "\n     System.out.println(\"A\");"
								+ "\n     break;"
								+ "\n    default:"
								+ "\n     System.out.println(\"default\");"
								+ "\n   }	"
								+ "\n  }"
								+ "\n "
								+ "\n  public void switchWithAllEnumValues(ABCDEnum value) {"
								+ "\n   switch (value) {		// tests assume this is line 252"
								+ "\n    case B: "
								+ "\n     System.out.println(\"B\");"
								+ "\n     break;"
								+ "\n    case C:"
								+ "\n     System.out.println(\"C\");"
								+ "\n    case A:"
								+ "\n     System.out.println(\"A\");"
								+ "\n     break;"
								+ "\n    case D:"
								+ "\n     System.out.println(\"D\");"
								+ "\n     break;"
								+ "\n    default:"
								+ "\n     // Not reachable, but compiler does not mark it as dead code."
								+ "\n     System.out.println(\"default\");	// tests assume this is line 266"
								+ "\n   }	"
								+ "\n  }"
								+ "\n "
								+ "\n  public void switchWithAllButDefaultEnumValues(ABCDEnum value) {"
								+ "\n   switch (value) {			// tests assume this is line 271		"
								+ "\n    case B: "
								+ "\n     System.out.println(\"B\");"
								+ "\n     break;"
								+ "\n    case C:"
								+ "\n     System.out.println(\"C\");"
								+ "\n    case A:"
								+ "\n     System.out.println(\"A\");"
								+ "\n     break;"
								+ "\n    case D:"
								+ "\n     System.out.println(\"D\");"
								+ "\n     break;"
								+ "\n   }	"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchWithEnum() {"
								+ "\n   switchWithEnum(ABCDEnum.B);"
								+ "\n   switchWithEnum(ABCDEnum.C);"
								+ "\n   switchWithEnum(ABCDEnum.D);"
								+ "\n   switchWithEnum(ABCDEnum.B);"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchWithAllEnumValues() {"
								+ "\n   switchWithAllEnumValues(ABCDEnum.A);"
								+ "\n   switchWithAllEnumValues(ABCDEnum.B);"
								+ "\n   switchWithAllEnumValues(ABCDEnum.C);"
								+ "\n   switchWithAllEnumValues(ABCDEnum.D);"
								+ "\n  }"
								+ "\n "
								+ "\n  public void callSwitchWithAllButDefaultEnumValues() {"
								+ "\n   switchWithAllButDefaultEnumValues(ABCDEnum.A);"
								+ "\n   switchWithAllButDefaultEnumValues(ABCDEnum.B);"
								+ "\n   switchWithAllButDefaultEnumValues(ABCDEnum.C);"
								+ "\n   switchWithAllButDefaultEnumValues(ABCDEnum.D);"
								+ "\n  }"
								+ "\n "
								+ "\n "
								+ "\n  public static void main(String[] args) {"
								+ "\n   Main main = new Main();"
								+ "\n   main.callNoDefaultSwitch();"
								+ "\n   main.callSwitchFallThrough();"
								+ "\n   main.callSwitchWithDefault();"
								+ "\n   main.callSwitchWithoutGaps();"
								+ "\n   main.callSwitchWithoutGapsWithFallThrough();"
								+ "\n   main.callSwitchBug2075537();"
								+ "\n   main.callSwitchBug2075537_2();"
								+ "\n   main.callSwitchNonDefaultFallThrough();"
								+ "\n   main.callSwitchWithBoolean();"
								+ "\n   main.callSwitchWithEnum();"
								+ "\n   main.callSwitchWithAllEnumValues();"
								+ "\n   main.callSwitchWithAllButDefaultEnumValues();"
								+ "\n  }	" + "\n }");

		TestUtils.compileSource(ant, srcDir);

		TestUtils.instrumentClasses(ant, srcDir, datafile, instrumentDir);

		/*
		 * Kick off the Main (instrumented) class.
		 */
		Java java = new Java();
		java.setProject(TestUtils.project);
		java.setClassname("mypackage.Main");
		java.setDir(srcDir);
		java.setFork(true);
		java.setFailonerror(true);
		java.setClasspath(TestUtils.getCoberturaDefaultClasspath());
		java.execute();

		/*
		 * Now create a cobertura html report and make sure the files are created.
		 */
		ReportTask reportTask = new ReportTask();
		reportTask.setProject(TestUtils.project);
		reportTask.setDataFile(datafile.getAbsolutePath());
		reportTask.setFormat("html");
		reportTask.setDestDir(reportDir);
		reportTask.setSrcDir(srcDir.getAbsolutePath());
		reportTask.execute();
		assertTrue(new File(reportDir, "index.html").exists());
		assertTrue(new File(reportDir, "mypackage.Main.html").exists());

		/*
		 * Now create a cobertura xml file and make sure the correct counts are in it.
		 */
		reportTask = new ReportTask();
		reportTask.setProject(TestUtils.project);
		reportTask.setDataFile(datafile.getAbsolutePath());
		reportTask.setFormat("xml");
		reportTask.setDestDir(srcDir);
		reportTask.execute();

		Node dom = TestUtils.getXMLReportDOM(srcDir.getAbsolutePath()
				+ "/coverage.xml");

		//switchWithBoolean
		List<Node> lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchWithBoolean");

		assertConditionCoverage(lines, "50% (4/8)", 195);

		/*
		 * A try catch just before a break statement used to cause Cobertura
		 * to report the line with the break as uncovered.  Make sure
		 * this no longer happens.
		 */
		// Unhapilly Java compiler is messing the code too much to support the case.			  
		// def breakInSwitchWithBooleanLine = lines.grep {it.number == '204'}[0]
		// assertEquals(1, breakInSwitchWithBooleanLine.hits)			

		//switchNonDefaultFallThrough
		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchNonDefaultFallThrough");

		assertConditionCoverage(lines, "33% (1/3)", 170);

		//switchBug2075537
		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchBug2075537");

		assertConditionCoverage(lines, "50% (1/2)", 132);

		//switchBug2075537_2
		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchBug2075537_2");

		assertConditionCoverage(lines, "100% (3/3)", 150);

		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchWithoutGaps");

		assertConditionCoverage(lines, "25% (1/4)", 76);

		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchWithoutGapsWithFallThrough");

		assertConditionCoverage(lines, "25% (1/4)", 100);

		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"callNoDefaultSwitch");

		assertConditionCoverage(lines, "33% (1/3)", 7);

		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchFallThrough");

		assertConditionCoverage(lines, "33% (1/3)", 23);

		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchWithDefault");

		assertConditionCoverage(lines, "16% (1/6)", 40);

		lines = TestUtils
				.getLineCounts(dom, "mypackage.Main", "switchWithEnum");
		assertConditionCoverage(lines, "75% (3/4)", 237);

		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchWithAllEnumValues");
		assertConditionCoverage(lines, "100% (4/4)", 252);

		boolean found = false;
		for (Node node : lines) {
			if (Integer.valueOf((String) node.attribute("number")) == 266) {
				found = true;
				assertEquals((Integer) 0, Integer.valueOf((String) node
						.attribute("hits")));
			}
		}

		assertTrue("Failed to find node on line 266", found);

		lines = TestUtils.getLineCounts(dom, "mypackage.Main",
				"switchWithAllButDefaultEnumValues");
		assertConditionCoverage(lines, "100% (4/4)", 271);
	}
}
