/*****************************************************************************
 * 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 the CVS Client Library.
 * The Initial Developer of the Original Code is Robert Greig.
 * Portions created by Robert Greig are Copyright (C) 2000.
 * All Rights Reserved.
 *
 * Contributor(s): Robert Greig.
 *****************************************************************************/
package org.netbeans.lib.cvsclient.response;

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

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

/**
 * @author Thomas Singer
 */
public final class DefaultResponseHandler implements IResponseHandler {

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

	public static IResponseHandler createInstance(IClientEnvironment clientEnvironment, IEventSender eventSender) {
		return new DefaultResponseHandler(clientEnvironment, eventSender);
	}

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

	private final ICvsFileSystem cvsFileSystem;
	private final ILocalFileReader localFileReader;
	private final ILocalFileWriter localFileWriter;
	private final IAdminReader adminReader;
	private final IAdminWriter adminWriter;
	private final CvsRoot cvsRoot;
	private final IEventSender eventSender;
	private FilePermissions nextFilePermissions;
	private Date nextFileDate;

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

	private DefaultResponseHandler(IClientEnvironment clientEnvironment, IEventSender eventSender) {
		BugLog.assertNotNull(clientEnvironment);
		BugLog.assertNotNull(eventSender);

		cvsFileSystem = clientEnvironment.getCvsFileSystem();
		localFileReader = clientEnvironment.getLocalFileReader();
		localFileWriter = clientEnvironment.getLocalFileWriter();
		adminReader = clientEnvironment.getAdminReader();
		adminWriter = clientEnvironment.getAdminWriter();
		cvsRoot = clientEnvironment.getCvsRoot();

		this.eventSender = eventSender;
	}

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

	public void processErrorMessageResponse(String message) {
		eventSender.notifyMessageListeners(message, true, false);
	}

	public void processMessageResponse(String message) {
		eventSender.notifyMessageListeners(message, false, false);
	}

	public void processMessageTaggedResponse(String message) {
		eventSender.notifyMessageListeners(message, false, true);
	}

	public void processCheckedInResponse(String relativeLocalDirectory, String repositoryFilePath, String entryLine) throws IOException {
		final FsFilePath fsFile = cvsFileSystem.getFsFile(relativeLocalDirectory, repositoryFilePath);
		final Entry entry = Entry.createEntryForLine(entryLine);

		if (entry.isAdded()
				|| entry.isRemoved()) {
			// for added and removed entries set the conflict to Dummy timestamp.
			entry.setDummyTimestamp();
		}
		else {
			if (nextFilePermissions != null) {
				localFileWriter.setFilePermissions(fsFile, nextFilePermissions);
			}

			entry.parseConflictString(getLastModifiedDate(fsFile, localFileWriter));
		}

		adminWriter.setEntry(fsFile, entry);
		eventSender.notifyEntryListeners(fsFile, entry);
	}

	public void processNewEntryResponse(String relativeLocalDirectory, String repositoryFilePath, String entryLine) throws IOException {
		// we set the date the file was last modified in the Entry line
		// so that we can easily determine whether the file has been
		// untouched
		final FsFilePath fsFile = cvsFileSystem.getFsFile(relativeLocalDirectory, repositoryFilePath);
		final Entry entry = Entry.createEntryForLine(entryLine);
		entry.setDummyTimestamp();

		adminWriter.setEntry(fsFile, entry);
	}

	public void processSetStaticDirectoryResponse(String relativeLocalDirectory, String repositoryDirectoryPath) throws IOException {
		final FsDirectoryPath fsDirectory = cvsFileSystem.getFsDirectory(relativeLocalDirectory);

		adminWriter.ensureCvsDirectory(fsDirectory, cvsFileSystem.getRelativeRepositoryPath(repositoryDirectoryPath), cvsRoot);
		adminWriter.setEntriesDotStatic(fsDirectory, true);

		eventSender.notifyDirectoryListeners(fsDirectory, true);
	}

	public void processClearStaticDirectoryResponse(String relativeLocalDirectory, String repositoryDirectoryPath) throws IOException {
		final FsDirectoryPath fsDirectory = cvsFileSystem.getFsDirectory(relativeLocalDirectory);

		adminWriter.ensureCvsDirectory(fsDirectory, cvsFileSystem.getRelativeRepositoryPath(repositoryDirectoryPath), cvsRoot);
		adminWriter.setEntriesDotStatic(fsDirectory, false);

		eventSender.notifyDirectoryListeners(fsDirectory, false);
	}

	public void processSetStickyResponse(String relativeLocalDirectory, String repositoryFilePath, String tag) throws IOException {
		final FsDirectoryPath fsDirectory = cvsFileSystem.getFsDirectory(relativeLocalDirectory);
		adminWriter.setStickyTagForDirectory(fsDirectory, tag);
	}

	public void processClearStickyResponse(String relativeLocalDirectory, String repositoryDirectoryPath) throws IOException {
		final FsDirectoryPath fsDirectory = cvsFileSystem.getFsDirectory(relativeLocalDirectory);

		adminWriter.ensureCvsDirectory(fsDirectory, cvsFileSystem.getRelativeRepositoryPath(repositoryDirectoryPath), cvsRoot);
		adminWriter.setStickyTagForDirectory(fsDirectory, null);
	}

	public void processNotifiedResponse(String relativeLocalDirectory, String repositoryFilePath) {
		/*final FsFile fsFile = */
		cvsFileSystem.getFsFile(relativeLocalDirectory, repositoryFilePath);
	}

	public void processRemovedResponse(String relativeLocalDirectory, String repositoryFilePath) throws IOException {
		final FsFilePath fsFile = cvsFileSystem.getFsFile(relativeLocalDirectory, repositoryFilePath);
		localFileWriter.removeLocalFile(fsFile);
		adminWriter.removeEntryForFile(fsFile);
	}

	public void processRemoveEntryResponse(String relativeLocalDirectory, String repositoryFilePath) throws IOException {
		final FsFilePath fsFile = cvsFileSystem.getFsFile(relativeLocalDirectory, repositoryFilePath);
		adminWriter.removeEntryForFile(fsFile);
		eventSender.notifyEntryListeners(fsFile, null);
	}

	public void processCopyFileResponse(String relativeLocalDirectory, String repositoryFilePath, String newName) throws IOException {
		final FsFilePath fsFile = cvsFileSystem.getFsFile(relativeLocalDirectory, repositoryFilePath);
		localFileWriter.renameLocalFile(fsFile, newName);
	}

	public void processModTimeResponse(Date modifiedDate) {
		this.nextFileDate = modifiedDate;
	}

	public void processModeResponse(String nextFileMode) {
		nextFilePermissions = FilePermissions.parseCvsString(nextFileMode);
	}

	public void processTemplateResponse(String relativeLocalDirectory, String repositoryFilePath, int length, IConnectionStreams connectionStreams) throws IOException {
		final FsDirectoryPath fsDirectory = cvsFileSystem.getFsDirectory(relativeLocalDirectory);
		adminWriter.writeTemplateFile(fsDirectory, length, connectionStreams.getInputStream(),
		                              connectionStreams.getReaderFactory(), localFileWriter);
	}

	public void processModuleExpansionResponse(String localPath) {
		eventSender.notifyModuleExpansionListeners(localPath);
	}

	public void processUpdatedResponse(String relativeLocalDirectory, String repositoryFilePath, String entryLine, String mode, int fileLength, IConnectionStreams connectionStreams) throws IOException {
		processUpdatedMergedResponse(relativeLocalDirectory, repositoryFilePath, entryLine,
		                             FilePermissions.parseCvsString(mode), fileLength, connectionStreams, false);
	}

	public void processMergedResponse(String relativeLocalDirectory, String repositoryFilePath, String entryLine, String mode, int fileLength, IConnectionStreams connectionStreams) throws IOException {
		processUpdatedMergedResponse(relativeLocalDirectory, repositoryFilePath, entryLine,
		                             FilePermissions.parseCvsString(mode), fileLength, connectionStreams, true);
	}

	public void processValidRequestsResponse(String validRequests) {
	}

	public void processOkResponse() {
		eventSender.notifyTerminationListeners(false);
	}

	public void processErrorResponse(String message) {
		eventSender.notifyMessageListeners(message, true, false);
		eventSender.notifyTerminationListeners(true);
	}

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

	private void processUpdatedMergedResponse(String relativeLocalDirectory, String repositoryFilePath, String entryLine, FilePermissions permissions, int fileLength,
	                                          IConnectionStreams connectionStreams, boolean merged) throws IOException {
		final FsFilePath fsFile = cvsFileSystem.getFsFile(relativeLocalDirectory, repositoryFilePath);
		final Entry entry = Entry.createEntryForLine(entryLine);

		final Date date = getNextFileDate();

		// SM-1385
		if (localFileReader.exists(fsFile)) {
			if (adminReader.getEntry(fsFile) == null) {
				localFileWriter.renameLocalFile(fsFile, ".#" + fsFile.getName());
			}
		}

		final InputStream inputStream = connectionStreams.getInputStream();
		if (entry.isBinary()) {
			localFileWriter.writeBinaryFile(fsFile, fileLength, inputStream, date);
		}
		else {
			localFileWriter.writeTextFile(fsFile, fileLength, inputStream, date, connectionStreams.getReaderFactory());
		}
		localFileWriter.setFilePermissions(fsFile, permissions);

		updateEntriesFileTime(fsFile, entry, merged, localFileWriter);

		adminWriter.ensureCvsDirectory(fsFile.getParent(), cvsFileSystem.getRelativeRepositoryPath(repositoryFilePath), cvsRoot);
		adminWriter.setEntry(fsFile, entry);
		eventSender.notifyEntryListeners(fsFile, entry);
	}

	private Date getNextFileDate() {
		final Date nextFileDate = this.nextFileDate;
		this.nextFileDate = null;
		return nextFileDate;
	}

	private static void updateEntriesFileTime(FsFilePath fsFile, Entry entry, boolean merged, ILocalFileWriter localFileWriter) {
		if (entry.isAdded()) {
			entry.setDummyTimestamp();
		}
		else {
			// we set the date the file was last modified in the Entry line
			// so that we can easily determine whether the file has been
			// untouched
			// for files with conflicts skip the setting of the conflict field.
			final String conflictString;
			if (entry.isConflict()) {
				if (entry.isTimeStampMatchesFile()) {
					conflictString = getEntryConflict(fsFile, true, merged, localFileWriter);
				}
				else {
					conflictString = entry.getConflictStringWithoutConflict();
				}
			}
			else {
				conflictString = getEntryConflict(fsFile, false, merged, localFileWriter);
			}
			entry.parseConflictString(conflictString);
		}
	}

	private static String getEntryConflict(FsFilePath fsFile, boolean conflict, boolean merged, ILocalFileWriter localFileWriter) {
		if (merged) {
			if (conflict) {
				return "Result of merge+" + getLastModifiedDate(fsFile, localFileWriter);
			}

			return "Result of merge";
		}
		return getLastModifiedDate(fsFile, localFileWriter);
	}

	private static String getLastModifiedDate(FsFilePath fsFile, ILocalFileWriter localFileWriter) {
		final long modificationTime = localFileWriter.getModificationTime(fsFile);
		final Date date = new Date(modificationTime);
		return Entry.formatLastModifiedDate(date);
	}
}
