/*******************************************************************************
 * Copyright (c) 2009, 2016 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.core.test.validation;

import static org.jacoco.core.test.validation.targets.Stubs.nop;
import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.Collection;

import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IClassCoverage;
import org.jacoco.core.analysis.ICounter;
import org.jacoco.core.analysis.IMethodCoverage;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.data.SessionInfoStore;
import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.internal.analysis.CounterImpl;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.RuntimeData;
import org.jacoco.core.runtime.SystemPropertiesRuntime;
import org.jacoco.core.test.TargetLoader;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Various tests for cyclomatic complexity of methods.
 */
public class CyclomaticComplexityTest {

	public interface Target {
		public void test(int arg);
	}

	private RuntimeData data;
	private IRuntime runtime;
	private byte[] bytes;
	private Target target;

	@Before
	public void setup() throws Exception {
		data = new RuntimeData();
		runtime = new SystemPropertiesRuntime();
		runtime.startup(data);
	}

	@After
	public void teardown() {
		runtime.shutdown();
	}

	public static class Simple implements Target {
		public void test(int arg) {
			nop();
			nop();
			nop();
		}
	}

	@Test
	public void testSimple1() throws Exception {
		instrument(Simple.class);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(1, 0), complexity);
	}

	@Test
	public void testSimple2() throws Exception {
		instrument(Simple.class);
		target.test(0);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(0, 1), complexity);
	}

	public static class If implements Target {
		public void test(int arg) {
			if (arg == 0) {
				nop();
			}
		}
	}

	@Test
	public void testIf1() throws Exception {
		instrument(If.class);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(2, 0), complexity);
	}

	@Test
	public void testIf2() throws Exception {
		instrument(If.class);
		target.test(0);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(1, 1), complexity);
	}

	@Test
	public void testIf3() throws Exception {
		instrument(If.class);
		target.test(0);
		target.test(1);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(0, 2), complexity);
	}

	public static class TwoIf implements Target {
		public void test(int arg) {
			if (arg < 0) {
				nop();
			}
			if (arg > 0) {
				nop();
			}
		}
	}

	@Test
	public void testTwoIf1() throws Exception {
		instrument(TwoIf.class);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(3, 0), complexity);
	}

	@Test
	public void testTwoIf2() throws Exception {
		instrument(TwoIf.class);
		target.test(-1);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(2, 1), complexity);
	}

	@Test
	public void testTwoIf3() throws Exception {
		instrument(TwoIf.class);
		target.test(-1);
		target.test(0);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(1, 2), complexity);
	}

	@Test
	public void testTwoIf4() throws Exception {
		instrument(TwoIf.class);
		target.test(-1);
		target.test(+1);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(0, 3), complexity);
	}

	public static class NestedIf implements Target {
		public void test(int arg) {
			if (arg >= 0) {
				if (arg == 0) {
					nop();
				}
				nop();
			}
		}
	}

	@Test
	public void testNestedIf1() throws Exception {
		instrument(NestedIf.class);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(3, 0), complexity);
	}

	@Test
	public void testNestedIf2() throws Exception {
		instrument(NestedIf.class);
		target.test(-1);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(2, 1), complexity);
	}

	@Test
	public void testNestedIf3() throws Exception {
		instrument(NestedIf.class);
		target.test(-1);
		target.test(0);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(1, 2), complexity);
	}

	@Test
	public void testNestedIf4() throws Exception {
		instrument(NestedIf.class);
		target.test(-1);
		target.test(0);
		target.test(+1);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(0, 3), complexity);
	}

	public static class Switch implements Target {
		public void test(int arg) {
			switch (arg) {
			case 1:
				nop();
				break;
			case 2:
				nop();
				break;
			}
		}
	}

	@Test
	public void testSwitch1() throws Exception {
		instrument(Switch.class);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(3, 0), complexity);
	}

	@Test
	public void testSwitch2() throws Exception {
		instrument(Switch.class);
		target.test(0);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(2, 1), complexity);
	}

	@Test
	public void testSwitch3() throws Exception {
		instrument(Switch.class);
		target.test(0);
		target.test(1);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(1, 2), complexity);
	}

	@Test
	public void testSwitch4() throws Exception {
		instrument(Switch.class);
		target.test(0);
		target.test(1);
		target.test(2);
		final ICounter complexity = analyze();
		assertEquals(CounterImpl.getInstance(0, 3), complexity);
	}

	private void instrument(final Class<? extends Target> clazz)
			throws Exception {
		bytes = TargetLoader.getClassDataAsBytes(clazz);
		final byte[] instrumented = new Instrumenter(runtime).instrument(bytes,
				"TestTarget");
		final TargetLoader loader = new TargetLoader();
		target = (Target) loader.add(clazz, instrumented).newInstance();
	}

	private ICounter analyze() throws IOException {
		final CoverageBuilder builder = new CoverageBuilder();
		final ExecutionDataStore store = new ExecutionDataStore();
		data.collect(store, new SessionInfoStore(), false);
		final Analyzer analyzer = new Analyzer(store, builder);
		analyzer.analyzeClass(bytes, "TestTarget");
		final Collection<IClassCoverage> classes = builder.getClasses();
		assertEquals(1, classes.size(), 0.0);
		final IClassCoverage classCoverage = classes.iterator().next();
		for (final IMethodCoverage m : classCoverage.getMethods()) {
			if (m.getName().equals("test")) {
				return m.getComplexityCounter();
			}
		}
		throw new AssertionError("No test() method.");
	}

}
