/*******************************************************************************
 * Copyright (c) 2015, 2018 QNX Software Systems and others
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Alena Laskavaia - Initial API and Implementation
 *******************************************************************************/
package org.eclipse.linuxtools.valgrind.core.tests;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.junit.Assert;
import org.osgi.framework.Bundle;

/**
 * Utilities for reading test source code from plug-in .java sources
 */
public class TestSourceReader {
    private final Bundle bundle;
    private final String srcRoot;
    private final Class<?> clazz;
    private final int numSections;

    /**
     * @param bundle
     *            the bundle containing the source, if {@code null} can try to load using classpath (source folder has
     *            to be in the classpath for this to work)
     * @param srcRoot
     *            the directory inside the bundle containing the packages
     * @param clazz
     *            the name of the class containing the test
     */
    public TestSourceReader(Bundle bundle, String srcRoot, Class<?> clazz) {
        this(bundle, srcRoot, clazz, 0);
    }

    /**
     * @param bundle
     *            the bundle containing the source, if {@code null} can try to load using classpath (source folder has
     *            to be in the classpath for this to work)
     * @param srcRoot
     *            the directory inside the bundle containing the packages
     * @param clazz
     *            the name of the class containing the test
     * @param numSections
     *            the number of comment sections preceding the named test to return. Pass zero to get all available
     *            sections.
     */
    public TestSourceReader(Bundle bundle, String srcRoot, Class<?> clazz, int numSections) {
        this.bundle = bundle;
        this.srcRoot = srcRoot;
        this.clazz = clazz;
        this.numSections = numSections;
    }

    public StringBuilder[] getContentsForTest(final String testName) throws IOException {
        return getContentsForTest(bundle, srcRoot, clazz, testName, numSections);
    }
    /**
     * Returns an array of StringBuilder objects for each comment section found preceding the named test in the source
     * code.
     *
     * @param bundle
     *            the bundle containing the source, if {@code null} can try to load using classpath (source folder has
     *            to be in the classpath for this to work)
     * @param srcRoot
     *            the directory inside the bundle containing the packages
     * @param clazz
     *            the name of the class containing the test
     * @param testName
     *            the name of the test
     * @param numSections
     *            the number of comment sections preceding the named test to return. Pass zero to get all available
     *            sections.
     * @return an array of StringBuilder objects for each comment section found preceding the named test in the source
     *         code.
     * @throws IOException if a source file is not found
     */
    public static StringBuilder[] getContentsForTest(Bundle bundle, String srcRoot, Class<?> clazz, final String testName,
            int numSections) throws IOException {
        // Walk up the class inheritance chain until we find the test method.
        try {
            while (clazz.getMethod(testName).getDeclaringClass() != clazz) {
                clazz = clazz.getSuperclass();
            }
        } catch (SecurityException e) {
            Assert.fail(e.getMessage());
        } catch (NoSuchMethodException e) {
            Assert.fail(e.getMessage());
        }

        while (true) {
            // Find and open the .java file for the class clazz.
            String fqn = clazz.getName().replace('.', '/');
            fqn = fqn.indexOf("$") == -1 ? fqn : fqn.substring(0, fqn.indexOf("$"));
            String classFile = fqn + ".java";
            IPath filePath = new Path(srcRoot + '/' + classFile);

            InputStream in;
            Class<?> superclass = clazz.getSuperclass();
            try {
                if (bundle != null) {
                    in = FileLocator.openStream(bundle, filePath, false);
                } else {
                    in = clazz.getResourceAsStream('/' + classFile);
                }
            } catch (IOException e) {
                if (superclass == null || !superclass.getPackage().equals(clazz.getPackage())) {
                    throw e;
                }
                clazz = superclass;
                continue;
            }

			try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
				// Read the java file collecting comments until we encounter the test method.
				List<StringBuilder> contents = new ArrayList<>();
				StringBuilder content = new StringBuilder();
				for (String line = br.readLine(); line != null; line = br.readLine()) {
					line = line.replaceFirst("^\\s*", ""); // Replace leading whitespace, preserve trailing
					if (line.startsWith("//")) {
						content.append(line.substring(2) + "\n");
					} else {
						if (!line.startsWith("@") && content.length() > 0) {
							contents.add(content);
							if (numSections > 0 && contents.size() == numSections + 1)
								contents.remove(0);
							content = new StringBuilder();
						}
						if (line.length() > 0 && !contents.isEmpty()) {
							int idx = line.indexOf(testName);
							if (idx != -1 && !Character.isJavaIdentifierPart(line.charAt(idx + testName.length()))) {
								return contents.toArray(new StringBuilder[contents.size()]);
							}
							if (!line.startsWith("@")) {
								contents.clear();
							}
						}
					}
				}
			}

            if (superclass == null || !superclass.getPackage().equals(clazz.getPackage())) {
                throw new IOException("Test data not found for " + clazz.getName() + "." + testName);
            }
            clazz = superclass;
        }
    }
}
