/*
 *                 Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 *
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.netbeans.lib.cvsclient.command;

import java.io.*;
import java.util.*;

import org.netbeans.lib.cvsclient.*;
import org.netbeans.lib.cvsclient.admin.*;
import org.netbeans.lib.cvsclient.file.*;
import org.netbeans.lib.cvsclient.util.*;

/**
 * @author Thomas Singer
 */
public final class FileSystemScanner {

	// Static =================================================================

	public static ICvsFiles scan(Collection<? extends FsPath> paths, boolean recursive, IClientEnvironment clientEnvironment) throws IOException {
		final FileSystemScanner scanner = new FileSystemScanner(clientEnvironment, recursive);
		return scanner.scan(paths);
	}

	public static ICvsFiles scan(FsDirectoryPath fsDirectory, boolean recursive, IClientEnvironment clientEnvironment) throws IOException {
		final FileSystemScanner scanner = new FileSystemScanner(clientEnvironment, recursive);
		return scanner.scan(Collections.singleton(fsDirectory));
	}

	public static ICvsFiles scan(Collection<FsFilePath> fsFiles, IClientEnvironment clientEnvironment) throws IOException {
		final FileSystemScanner scanner = new FileSystemScanner(clientEnvironment, false);
		return scanner.scan(fsFiles);
	}

	public static ICvsFiles scan(FsFilePath fsFile, IClientEnvironment clientEnvironment) throws IOException {
		final FileSystemScanner scanner = new FileSystemScanner(clientEnvironment, false);
		return scanner.scan(Collections.singleton(fsFile));
	}

	// Fields =================================================================

	private final ILocalFileReader localFileReader;
	private final IAdminReader adminReader;
	private final boolean recursive;

	// Setup ==================================================================

	private FileSystemScanner(IClientEnvironment clientEnvironment, boolean recursive) {
		BugLog.assertNotNull(clientEnvironment);

		this.localFileReader = clientEnvironment.getLocalFileReader();
		this.adminReader = clientEnvironment.getAdminReader();
		this.recursive = recursive;
	}

	// Utils ==================================================================

	private ICvsFiles scan(Collection<? extends FsPath> fsPaths) throws IOException {
		if (!localFileReader.exists(FsDirectoryPath.ROOT)) {
			throw new FileNotFoundException("The local directory does not exist.");
		}

		final String cvsRootFileContent = adminReader.hasCvsDirectory(FsDirectoryPath.ROOT) ? adminReader.getCvsRootFileContent(FsDirectoryPath.ROOT) : null;

		final CvsFiles cvsFiles = new CvsFiles();

		if (fsPaths.isEmpty()) {
			// if no arguments have been specified, then scan the
			// local directory - the "top level" for this command
			scanDirectories(FsDirectoryPath.ROOT, cvsFiles, cvsRootFileContent);
		}
		else {
			for (final FsPath fsPath : fsPaths) {
				if (fsPath instanceof FsDirectoryPath) {
					final FsDirectoryPath fsDirectory = (FsDirectoryPath)fsPath;
					scanDirectories(fsDirectory, cvsFiles, cvsRootFileContent);
				}
				else if (fsPath instanceof FsFilePath) {
					final FsFilePath fsFile = (FsFilePath)fsPath;
					addRequestsForFile(fsFile, cvsFiles);
				}
			}
		}

		return cvsFiles;
	}

	private void scanDirectories(FsDirectoryPath rootFsDirectory, CvsFiles cvsFiles, String expectedCvsRootFileContent) throws IOException {
		final List<FsDirectoryPath> directories = new LinkedList<FsDirectoryPath>();
		directories.add(rootFsDirectory);
		while (directories.size() > 0) {
			final FsDirectoryPath fsDirectory = directories.remove(0);

			scanDirectory(fsDirectory, cvsFiles, expectedCvsRootFileContent, true, recursive ? directories : null);
		}
	}

	private boolean isValidCvsRootContent(FsDirectoryPath fsDirectory, String expectedCvsRootFileContent) throws IOException {
		final FsDirectoryPath parentFsDirectory = fsDirectory.getParent();
		if (parentFsDirectory != null) {
			if (!isValidCvsRootContent(parentFsDirectory, expectedCvsRootFileContent)) {
				return false;
			}
		}

		return expectedCvsRootFileContent.equals(adminReader.getCvsRootFileContent(fsDirectory));
	}

	private void scanDirectory(FsDirectoryPath fsDirectory, CvsFiles cvsFiles, String expectedCvsRootFileContent, boolean scanContent, List<FsDirectoryPath> directories) throws IOException {
		if (!localFileReader.exists(fsDirectory)) {
			scanDirectory(fsDirectory.getParent(), cvsFiles, expectedCvsRootFileContent, false, null);
			scanContent = false;
		}
		else {
			if (expectedCvsRootFileContent != null && !isValidCvsRootContent(fsDirectory, expectedCvsRootFileContent)) {
				return;
			}
		}

		cvsFiles.add(CvsFile.createCvsDirectory(fsDirectory));

		if (!scanContent) {
			return;
		}

		scanDirectoryContent(fsDirectory, cvsFiles, directories);
	}

	private void scanDirectoryContent(FsDirectoryPath fsDirectory, CvsFiles cvsFiles, List<FsDirectoryPath> directories) throws IOException {
		final Set<String> subDirectoryNames = new HashSet<String>();
		final LocalFiles localFiles = new LocalFiles(fsDirectory, localFileReader);

		// get all the entries we know about, and process them
		for (final Entry entry : adminReader.getEntries(fsDirectory)) {
			if (entry.isDirectory()) {
				subDirectoryNames.add(entry.getFileName());
			}
			else {
				final FsFilePath fsFile = FsFilePath.createInstance(fsDirectory, entry.getFileName());
				final boolean fileExists = localFileReader.exists(fsFile);

				cvsFiles.add(CvsFile.createCvsFileForEntry(fsFile, entry, fileExists));

				localFiles.removeFile(entry.getFileName());
			}
		}

		for (final String fileName : localFiles.getFileNames()) {
			cvsFiles.add(CvsFile.createCvsFileForExistingFile(FsFilePath.createInstance(fsDirectory, fileName)));
		}

		if (directories == null) {
			return;
		}

		for (final String directoryName : subDirectoryNames) {
			directories.add(FsDirectoryPath.createRelativeInstance(fsDirectory, directoryName));
		}
	}

	private void addRequestsForFile(FsFilePath fsFile, CvsFiles cvsFiles) {
		cvsFiles.add(CvsFile.createCvsDirectory(fsFile.getParent()));

		try {
			final Entry entry = adminReader.getEntry(fsFile);
			// a non-null entry means the file does exist in the
			// Entries file for this directory
			if (entry != null) {
				final boolean exists = localFileReader.exists(fsFile);
				cvsFiles.add(CvsFile.createCvsFileForEntry(fsFile, entry, exists));
			}
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}
