/*******************************************************************************
 * Copyright (c) 2008, 2012 Institute for Software, HSR Hochschule fuer Technik
 * Rapperswil, University of applied sciences and others
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Institute for Software - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.parser.tests.rewrite;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.cdt.core.testplugin.CTestPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.TextSelection;
import org.osgi.framework.Bundle;

import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * @author Emanuel Graf
 */
public class RewriteTester extends TestSuite {
	enum MatcherState {
		skip, inTest, inSource, inExpectedResult
	}

	private static final String classRegexp = "//#(.*)\\s*(\\w*)*$"; //$NON-NLS-1$
	private static final String testRegexp = "//!(.*)\\s*(\\w*)*$"; //$NON-NLS-1$
	private static final String fileRegexp = "//@(.*)\\s*(\\w*)*$"; //$NON-NLS-1$
	private static final String resultRegexp = "//=.*$"; //$NON-NLS-1$

	public static Test suite(String name, String file) throws Exception {
		BufferedReader in = createReader(file);

		ArrayList<RewriteBaseTest> testCases = createTests(in);
		in.close();
		return createSuite(testCases, name);
	}

	protected static BufferedReader createReader(String file) throws IOException {
		Bundle bundle = CTestPlugin.getDefault().getBundle();
		Path path = new Path(file);
		String file2 = FileLocator.toFileURL(FileLocator.find(bundle, path, null)).getFile();
		return new BufferedReader(new FileReader(file2));
	}

	private static ArrayList<RewriteBaseTest> createTests(BufferedReader inputReader) throws Exception {
		String line;
		List<TestSourceFile> files = new ArrayList<>();
		TestSourceFile actFile = null;
		MatcherState matcherState = MatcherState.skip;
		ArrayList<RewriteBaseTest> testCases = new ArrayList<>();
		String testName = null;
		String className = null;
		boolean bevorFirstTest = true;

		while ((line = inputReader.readLine()) != null) {
			if (lineMatchesBeginOfTest(line)) {
				if (!bevorFirstTest) {
					RewriteBaseTest test = createTestClass(className, testName, files);
					testCases.add(test);
					files = new ArrayList<>();
					className = null;
					testName = null;
				}
				matcherState = MatcherState.inTest;
				testName = getNameOfTest(line);
				bevorFirstTest = false;
				continue;
			} else if (lineMatchesBeginOfResult(line)) {
				matcherState = MatcherState.inExpectedResult;
				continue;
			} else if (lineMatchesFileName(line)) {
				matcherState = MatcherState.inSource;
				actFile = new TestSourceFile(getFileName(line));
				files.add(actFile);
				continue;
			} else if (lineMatchesClassName(line)) {
				className = getNameOfClass(line);
				continue;
			}

			switch (matcherState) {
			case inSource:
				if (actFile != null) {
					actFile.addLineToSource(line);
				}
				break;
			case inExpectedResult:
				if (actFile != null) {
					actFile.addLineToExpectedSource(line);
				}
				break;
			default:
				break;
			}
		}
		RewriteBaseTest test = createTestClass(className, testName, files);
		testCases.add(test);
		return testCases;
	}

	private static RewriteBaseTest createTestClass(String className, String testName, List<TestSourceFile> files)
			throws Exception {
		try {
			Class<?> refClass = Class.forName(className);
			Constructor<?> ct = refClass.getConstructor(new Class[] { String.class, List.class });
			RewriteBaseTest test = (RewriteBaseTest) ct.newInstance(new Object[] { testName, files });
			for (TestSourceFile file : files) {
				TextSelection sel = file.getSelection();
				if (sel != null) {
					test.setFileWithSelection(file.getName());
					test.setSelection(sel);
					break;
				}
			}
			return test;
		} catch (ClassNotFoundException e) {
			throw new Exception("Unknown TestClass: " + e.getMessage()
					+ ". Make sure the test's sourcefile specifies a valid test class.");
		} catch (SecurityException e) {
			throw new Exception("Security Exception during Test creation", e);
		} catch (NoSuchMethodException e) {
			throw new Exception("Test class does not provied required constructor.");
		} catch (IllegalArgumentException e) {
			throw new Exception("IllegalArgumentException during Test creation", e);
		} catch (InstantiationException e) {
			throw new Exception("InstantiationException during Test creation", e);
		} catch (IllegalAccessException e) {
			throw new Exception("IllegalAccessException during Test creation", e);
		} catch (InvocationTargetException e) {
			throw new Exception("InvocationTargetException during Test creation", e);
		}
	}

	private static String getFileName(String line) {
		Matcher matcherBeginOfTest = createMatcherFromString(fileRegexp, line);
		if (matcherBeginOfTest.find())
			return matcherBeginOfTest.group(1);
		return null;
	}

	private static String getNameOfClass(String line) {
		Matcher matcherBeginOfTest = createMatcherFromString(classRegexp, line);
		if (matcherBeginOfTest.find())
			return matcherBeginOfTest.group(1);
		return null;
	}

	private static boolean lineMatchesBeginOfTest(String line) {
		return createMatcherFromString(testRegexp, line).find();
	}

	private static boolean lineMatchesClassName(String line) {
		return createMatcherFromString(classRegexp, line).find();
	}

	private static boolean lineMatchesFileName(String line) {
		return createMatcherFromString(fileRegexp, line).find();
	}

	protected static Matcher createMatcherFromString(String pattern, String line) {
		return Pattern.compile(pattern).matcher(line);
	}

	private static String getNameOfTest(String line) {
		Matcher matcherBeginOfTest = createMatcherFromString(testRegexp, line);
		if (matcherBeginOfTest.find())
			return matcherBeginOfTest.group(1);
		return "Not Named";
	}

	private static boolean lineMatchesBeginOfResult(String line) {
		return createMatcherFromString(resultRegexp, line).find();
	}

	private static TestSuite createSuite(ArrayList<RewriteBaseTest> testCases, String name) {
		TestSuite suite = new TestSuite(name);
		Iterator<RewriteBaseTest> it = testCases.iterator();
		while (it.hasNext()) {
			RewriteBaseTest subject = it.next();
			suite.addTest(subject);
		}
		return suite;
	}
}
