/*******************************************************************************
 * Copyright (c) 2008, 2017 Wind River Systems, Inc. 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:
 *     Martin Oberhuber (Wind River) - initial API and implementation for [232426]
 *     Szymon Ptaszkiewicz (IBM) - Symlink test failures on Windows 7 [331716]
 *     Sergey Prigogin (Google) -  ongoing development
 *******************************************************************************/
package org.eclipse.core.tests.internal.localstore;

import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;

/**
 *
 */
public class SymlinkResourceTest extends LocalStoreTest {

	public SymlinkResourceTest() {
		super();
	}

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

	public static Test suite() {
		return new TestSuite(SymlinkResourceTest.class);
	}

	protected void mkLink(IFileStore dir, String src, String tgt, boolean isDir) {
		try {
			createSymLink(dir.toLocalFile(EFS.NONE, getMonitor()), src, tgt, isDir);
		} catch (CoreException e) {
			fail("mkLink", e);
		}
	}

	protected void createBug232426Structure(IFileStore rootDir) throws CoreException {
		IFileStore folderA = rootDir.getChild("a");
		IFileStore folderB = rootDir.getChild("b");
		IFileStore folderC = rootDir.getChild("c");
		folderA.mkdir(EFS.NONE, getMonitor());
		folderB.mkdir(EFS.NONE, getMonitor());
		folderC.mkdir(EFS.NONE, getMonitor());

		/* create symbolic links */
		mkLink(folderA, "link", new Path("../b").toOSString(), true);
		mkLink(folderB, "linkA", new Path("../a").toOSString(), true);
		mkLink(folderB, "linkC", new Path("../c").toOSString(), true);
		mkLink(folderC, "link", new Path("../b").toOSString(), true);
	}

	protected void createBug358830Structure(IFileStore rootDir) throws CoreException {
		IFileStore folderA = rootDir.getChild("a");
		folderA.mkdir(EFS.NONE, getMonitor());

		/* create trivial recursive symbolic link */
		mkLink(folderA, "link", new Path("../").toOSString(), true);
	}

	/**
	 * Test a very specific case of mutually recursive symbolic links:
	 * <pre>
	 *   a/link  -> ../b
	 *   b/link1 -> ../a, b/link2 -> ../c
	 *   c/link  -> ../b
	 * </pre>
	 * In the specific bug, the two links in b were followed in an alternated
	 * fashion while walking down the tree. A correct implementation should
	 * stop following symbolic links as soon as a node is reached that has
	 * been visited before.
	 * See <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=232426">bug 232426</a>
	 */
	public void testBug232426() throws Exception {
		// Only activate this test if testing of symbolic links is possible.
		if (!canCreateSymLinks()) {
			return;
		}
		/* Re-use projects which are cleaned up automatically */
		final IProject project = projects[0];
		getWorkspace().run((IWorkspaceRunnable) monitor -> {
			/* delete open project because we must re-open with BACKGROUND_REFRESH */
			project.delete(IResource.NEVER_DELETE_PROJECT_CONTENT, getMonitor());
			project.create(null);
			createBug232426Structure(EFS.getStore(project.getLocationURI()));
			//Bug only happens with BACKGROUND_REFRESH.
			project.open(IResource.BACKGROUND_REFRESH, getMonitor());
		}, null);

		//wait for BACKGROUND_REFRESH to complete.
		waitForRefresh();
		project.accept(new IResourceVisitor() {
			int resourceCount = 0;

			@Override
			public boolean visit(IResource resource) {
				resourceCount++;
				//We have 1 root + 3 folders + 4 elements --> 8 elements to visit at most
				assertTrue(resourceCount <= 8);
				return true;
			}
		});
	}

	public void testBug358830() throws Exception {
		// Only activate this test if testing of symbolic links is possible.
		if (!canCreateSymLinks()) {
			return;
		}
		/* Re-use projects which are cleaned up automatically */
		final IProject project = projects[0];
		getWorkspace().run((IWorkspaceRunnable) monitor -> {
			/* delete open project because we must re-open with BACKGROUND_REFRESH */
			project.delete(IResource.NEVER_DELETE_PROJECT_CONTENT, getMonitor());
			project.create(null);
			createBug358830Structure(EFS.getStore(project.getLocationURI()));
			project.open(IResource.BACKGROUND_REFRESH, getMonitor());
		}, null);

		//wait for BACKGROUND_REFRESH to complete.
		waitForRefresh();
		final int resourceCount[] = new int[] {0};
		project.accept(resource -> {
			resourceCount[0]++;
			return true;
		});
		//We have 1 root + 1 folder + 1 file (.project) --> 3 elements to visit
		assertEquals(3, resourceCount[0]);
	}
}
