/*******************************************************************************
 * Copyright (c) 2018 Simeon Andreev and others.
 * 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:
 *     Simeon Andreev - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.core.tests.builder;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.compiler.BuildContext;
import org.eclipse.jdt.core.tests.builder.ParticipantBuildTests.BuildTestParticipant;
import org.eclipse.jdt.core.tests.util.Util;
import org.eclipse.jdt.internal.core.builder.AbstractImageBuilder;

import junit.framework.Test;

public class Bug531382Test extends BuilderTests {

	private IPath project;
	private IPath src;
	private IPath srcPackage;

	private int previousLimit;

	public Bug531382Test(String name) {
		super(name);
	}

	public static Test suite() {
		return buildTestSuite(Bug531382Test.class);
	}

	@Override
	protected void setUp() throws Exception {
		super.setUp();

		this.project = env.addProject("TestProjectBug531382");
		env.addExternalJars(this.project, Util.getJavaClassLibs());

		env.removePackageFragmentRoot(this.project, "");
		this.src = env.addPackageFragmentRoot(this.project, "src");
		this.srcPackage = env.addPackage(this.src, "p");

		/*
		 * We can work with the limit "as is", however that would mean creating a lot of classes.
		 * To improve test time we set the limit to a small number and then restore it once the test is done.
		 *
		 * This improvement can be removed if the field is to be hidden or made final.
		 */
		this.previousLimit = AbstractImageBuilder.MAX_AT_ONCE;
		AbstractImageBuilder.MAX_AT_ONCE = 42;
	}

	@Override
	protected void tearDown() throws Exception {
		TestBuilderParticipant.PARTICIPANT = null;

		AbstractImageBuilder.MAX_AT_ONCE = this.previousLimit;

		env.removeProject(this.project);

		super.tearDown();
	}

	/**
	 * Test for Bug 531382.
	 *
	 * We create {@link AbstractImageBuilder#MAX_AT_ONCE} sources (e.g. 2000 sources).
	 *
	 * A build participant generates one more source during the build.
	 *
	 * We expect that this generated source is also compiled after the build.
	 * To check this we generate the source with an error for it, and we check for the error.
	 */
	public void testBug531382() throws Exception {
		IFolder srcPackageFolder = env.getWorkspace().getRoot().getFolder(this.srcPackage);
		assertTrue("package in source must exist", srcPackageFolder.exists());

		for (int i = 0; i < AbstractImageBuilder.MAX_AT_ONCE; ++i) {
			env.addClass(this.src, "p", "X" + i, "package p;\n public class X" + i + " {}");
		}

		final IFile generatedFile = srcPackageFolder.getFile("Generated.java");
		final String contents = "package p;\n public class NameMismatch {}";

		class GenerateBrokenSource extends BuildTestParticipant {
			public void buildStarting(BuildContext[] files, boolean isBatch) {
				if (files.length > 0 && !generatedFile.exists()) {
					BuildContext context = files[0];
					createFile(generatedFile, contents);
					IFile[] generatedFiles = { generatedFile };
					context.recordAddedGeneratedFiles(generatedFiles);
				}
			}
		}
		// Creating this sets the build participant singleton.
		new GenerateBrokenSource();

		assertFalse("source to be generated from build participant should not exist before build", generatedFile.exists());
		fullBuild(this.project);
		assertTrue("expected source to be generated from build participant", generatedFile.exists());

		expectCompileProblem("The public type NameMismatch must be defined in its own file");
	}

	protected void createFile(IFile generatedFile, String contents) {
		boolean force = true;
		IProgressMonitor monitor = new NullProgressMonitor();
		try {
			generatedFile.create(new ByteArrayInputStream(contents.getBytes()), force, monitor);
			generatedFile.setDerived(true, monitor);
		} catch (CoreException e) {
			throw new AssertionError("failed to generate file in build participant", e);
		}
	}

	private void expectCompileProblem(String expectedProblemMessage) {
		List<String> actualProblemMessages = new ArrayList<>();
		Problem[] problems = env.getProblemsFor(this.project, "org.eclipse.jdt.core.tests.compile.problem");
		if (problems != null) {
			for (Problem problem : problems) {
				actualProblemMessages.add(problem.getMessage());
			}
		}

		List<String> expectedProblemMessages = Arrays.asList(expectedProblemMessage);
		assertEquals("expected build participant to cause compile problems",
				expectedProblemMessages, actualProblemMessages);
	}
}
