package org.netbeans.lib.cvsclient.progress.receiving;

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

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

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

	// Constants ==============================================================

	private static final ILogger LOG = LoggerManager.getLogger("javacvs.responseProgress");

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

	public static Collection<FsFilePath> getFsFiles(ICvsFiles cvsFiles) {
		final Collection<FsFilePath> fsFiles = new ArrayList<FsFilePath>(2000);
		try {
			cvsFiles.visitFiles(new ICvsFilesVisitor() {
				public void handleFile(FsFilePath fsFile, Entry entry, boolean exists) {
					fsFiles.add(fsFile);
				}
			});
		}
		catch (CommandAbortedException e) {
			e.printStackTrace();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
		return fsFiles;
	}

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

	private final Set<FsFilePath> knownFsFiles = new HashSet<FsFilePath>(2000);
	private final Map<FsDirectoryPath, FsFilesCount> directoryPaths = new HashMap<FsDirectoryPath, FsFilesCount>(200);
	private final IProgressViewer progressViewer;
	private final int maxCount;

	private int count;
	private FsDirectoryPath previousDirectoryPath;

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

	public ResponseProgressHandler(IProgressViewer progressViewer, Collection<FsFilePath> fsFiles) {
		BugLog.assertNotNull(progressViewer);
		BugLog.assertNotNull(fsFiles);

		this.progressViewer = progressViewer;

		for (final FsFilePath fsFile : fsFiles) {
			if (LOG.isDebugEnabled()) {
				LOG.debug("adding file " + fsFile);
			}

			knownFsFiles.add(fsFile);
			addDirectory(fsFile.getParent());
		}

		maxCount = this.knownFsFiles.size();

		if (LOG.isDebugEnabled()) {
			LOG.debug("max file count " + maxCount);
		}
	}

	// Accessing ==============================================================

	public void fileProcessed(FsFilePath fsFile) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("processing file " + fsFile);
		}

		if (!knownFsFiles.remove(fsFile)) {
			if (LOG.isDebugEnabled()) {
				LOG.debug("unknown file is ignored");
			}
			return;
		}

		final FsDirectoryPath directoryPath = fsFile.getParent();
		final FsFilesCount count = directoryPaths.get(directoryPath);
		if (count == null) {
			if (LOG.isDebugEnabled()) {
				LOG.debug("no directory found for file");
			}

			return;
		}

		count.dec();

		if (LOG.isDebugEnabled()) {
			LOG.debug("decremented file count for " + directoryPath + ": " + count.getUnprocessedFilesInDirectory());
		}

		notifyProgressViewer(1);
	}

	public void directoryProcessed(FsDirectoryPath directoryPath) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("processing directory " + directoryPath);
		}

		if (previousDirectoryPath != null && !previousDirectoryPath.equals(directoryPath)) {
			if (LOG.isDebugEnabled()) {
				LOG.debug("clearing previous directory " + previousDirectoryPath);
			}

			final FsFilesCount previousDirectoryCount = directoryPaths.remove(previousDirectoryPath);
			if (previousDirectoryCount != null) {
				notifyProgressViewer(previousDirectoryCount.getUnprocessedFilesInDirectory());
			}
		}

		previousDirectoryPath = directoryPath;
	}

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

	private void addDirectory(FsDirectoryPath directoryPath) {
		FsFilesCount count = directoryPaths.get(directoryPath);
		if (count == null) {
			count = new FsFilesCount();
			directoryPaths.put(directoryPath, count);

			if (LOG.isDebugEnabled()) {
				LOG.debug("added directory " + directoryPath);
			}
		}

		count.inc();

		if (LOG.isDebugEnabled()) {
			LOG.debug("incremented file count for " + directoryPath + ": " + count.getUnprocessedFilesInDirectory());
		}
	}

	private void notifyProgressViewer(int incrementProgress) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("increment progress by " + incrementProgress);
		}

		if (incrementProgress == 0) {
			return;
		}

		count += incrementProgress;

		if (LOG.isDebugEnabled()) {
			LOG.debug("progress: " + count + " of " + maxCount);
		}

		progressViewer.setProgress(maxCount > 0 ? (double)count / maxCount : Double.NaN);
	}

	// Inner Classes ==========================================================

	private static final class FsFilesCount {
		private int fsFilesPerDirectory;

		public FsFilesCount() {
		}

		// Implemented ============================================================

		@Override
		public String toString() {
			return String.valueOf(fsFilesPerDirectory);
		}

		private void inc() {
			fsFilesPerDirectory++;
		}

		private void dec() {
			fsFilesPerDirectory--;
		}

		private int getUnprocessedFilesInDirectory() {
			return fsFilesPerDirectory;
		}
	}
}
