/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.base;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;

import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;

import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.Resources;

/**
 * JDT specific change object.
 */
public abstract class JDTChange extends Change {

	private long fModificationStamp;
	private boolean fReadOnly;
	
	private static class ValidationState {
		private IResource fResource;
		private int fKind;
		private boolean fDirty;
		private boolean fReadOnly;
		private long fModificationStamp;
		private ITextFileBuffer fTextFileBuffer;
		public static final int RESOURCE= 1;
		public static final int DOCUMENT= 2;
		public ValidationState(IResource resource) {
			fResource= resource;
			if (resource instanceof IFile) {
				initializeFile((IFile)resource);
			} else {
				initializeResource(resource);
			}
		}
		public void checkDirty(RefactoringStatus status, long stampToMatch, IProgressMonitor pm) throws CoreException {
			if (fDirty) {
				if (fKind == DOCUMENT && fTextFileBuffer != null && stampToMatch == fModificationStamp) {
					fTextFileBuffer.commit(pm, false);
				} else {
					status.addFatalError(Messages.format(
						RefactoringCoreMessages.Change_is_unsaved, fResource.getFullPath().toString())); //$NON-NLS-1$
				}
			}
		}
		public void checkDirty(RefactoringStatus status) {
			if (fDirty) {
				status.addFatalError(Messages.format(
					RefactoringCoreMessages.Change_is_unsaved, fResource.getFullPath().toString())); //$NON-NLS-1$
			}
		}
		public void checkReadOnly(RefactoringStatus status) {
			if (fReadOnly) {
				status.addFatalError(Messages.format(
					RefactoringCoreMessages.Change_is_read_only, fResource.getFullPath().toString())); //$NON-NLS-1$
			}
		}
		public void checkSameReadOnly(RefactoringStatus status, boolean valueToMatch) {
			if (fReadOnly != valueToMatch) {
				status.addFatalError(Messages.format(
					RefactoringCoreMessages.Change_same_read_only,
					fResource.getFullPath().toString()));
			}
		}
		public void checkModificationStamp(RefactoringStatus status, long stampToMatch) {
			if (fKind == DOCUMENT) {
				if (stampToMatch != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP && fModificationStamp != stampToMatch) {
					status.addFatalError(Messages.format(
						RefactoringCoreMessages.Change_has_modifications, fResource.getFullPath().toString())); //$NON-NLS-1$
				}
			} else {
				if (stampToMatch != IResource.NULL_STAMP && fModificationStamp != stampToMatch) {
					status.addFatalError(Messages.format(
						RefactoringCoreMessages.Change_has_modifications, fResource.getFullPath().toString())); //$NON-NLS-1$
					
				}
			}
		}
		private void initializeFile(IFile file) {
			fTextFileBuffer= getBuffer(file);
			if (fTextFileBuffer == null) {
				initializeResource(file);
			} else {
				IDocument document= fTextFileBuffer.getDocument();
				fDirty= fTextFileBuffer.isDirty();
				fReadOnly= Resources.isReadOnly(file);
				if (document instanceof IDocumentExtension4) {
					fKind= DOCUMENT;
					fModificationStamp= ((IDocumentExtension4)document).getModificationStamp();
				} else {
					fKind= RESOURCE;
					fModificationStamp= file.getModificationStamp();
				}
			}
			
		}
		private void initializeResource(IResource resource) {
			fKind= RESOURCE;
			fDirty= false;
			fReadOnly= Resources.isReadOnly(resource);
			fModificationStamp= resource.getModificationStamp();
		}
	}

	protected static final int NONE= 0;
	protected static final int READ_ONLY= 1 << 0;
	protected static final int DIRTY= 1 << 1;
	private static final int SAVE= 1 << 2;
	protected static final int SAVE_IF_DIRTY= SAVE | DIRTY;
	
	protected JDTChange() {
		fModificationStamp= IResource.NULL_STAMP;
		fReadOnly= false;
	}
	
	public void initializeValidationData(IProgressMonitor pm) {
		IResource resource= getResource(getModifiedElement());
		if (resource != null) {
			fModificationStamp= getModificationStamp(resource);
			fReadOnly= Resources.isReadOnly(resource);
		}
	}

	// protected final RefactoringStatus isValid(IProgressMonitor pm, boolean checkReadOnly, boolean checkDirty) throws CoreException {
	protected final RefactoringStatus isValid(IProgressMonitor pm, int flags) throws CoreException {
		pm.beginTask("", 2); //$NON-NLS-1$
		try {
			RefactoringStatus result= new RefactoringStatus();
			Object modifiedElement= getModifiedElement();
			checkExistence(result, modifiedElement);
			if (result.hasFatalError())
				return result;
			if (flags == NONE)
				return result;
			IResource resource= getResource(modifiedElement);
			if (resource != null) {
				ValidationState state= new ValidationState(resource);
				state.checkModificationStamp(result, fModificationStamp);
				if (result.hasFatalError())
					return result;
				state.checkSameReadOnly(result, fReadOnly);
				if (result.hasFatalError())
					return result;
				if ((flags & READ_ONLY) != 0) {
					state.checkReadOnly(result);
					if (result.hasFatalError())
						return result;
				}
				if ((flags & DIRTY) != 0) {
					if ((flags & SAVE) != 0) {
						state.checkDirty(result, fModificationStamp, new SubProgressMonitor(pm, 1));
					} else {
						state.checkDirty(result);
					}
				}
			}
			return result;
		} finally {
			pm.done();
		}
	}

	protected final RefactoringStatus isValid(int flags) throws CoreException {
		return isValid(new NullProgressMonitor(), flags);
	}

	protected static void checkIfModifiable(RefactoringStatus status, Object element, int flags) {
		checkIfModifiable(status, getResource(element), flags);
	}

	protected static void checkIfModifiable(RefactoringStatus result, IResource resource, int flags) {
		checkExistence(result, resource);
		if (result.hasFatalError())
			return;
		if (flags == NONE)
			return;
		ValidationState state= new ValidationState(resource);
		if ((flags & READ_ONLY) != 0) {
			state.checkReadOnly(result);
			if (result.hasFatalError())
				return;
		}
		if ((flags & DIRTY) != 0) {
			state.checkDirty(result);
		}
	}

	protected static void checkExistence(RefactoringStatus status, Object element) {
		if (element == null) {
			status.addFatalError(RefactoringCoreMessages.DynamicValidationStateChange_workspace_changed); //$NON-NLS-1$
			
		} else if (element instanceof IResource && !((IResource)element).exists()) {
			status.addFatalError(Messages.format(
				RefactoringCoreMessages.Change_does_not_exist, ((IResource)element).getFullPath().toString())); //$NON-NLS-1$
		} else if (element instanceof IJavaElement && !((IJavaElement)element).exists()) {
			status.addFatalError(Messages.format(
				RefactoringCoreMessages.Change_does_not_exist, ((IJavaElement)element).getElementName())); //$NON-NLS-1$
		}
	}

	private static IResource getResource(Object element) {
		if (element instanceof IResource) {
			return (IResource)element;
		}
		if (element instanceof ICompilationUnit) {
			return JavaModelUtil.toOriginal((ICompilationUnit)element).getResource();
		}
		if (element instanceof IJavaElement) {
			return ((IJavaElement)element).getResource();
		}
		if (element instanceof IAdaptable) {
			return (IResource)((IAdaptable)element).getAdapter(IResource.class);
		}
		return null;
	}

	public String toString() {
		return getName();
	}
	
	public long getModificationStamp(IResource resource) {
		if (!(resource instanceof IFile))
			return resource.getModificationStamp();
		IFile file= (IFile)resource;
		ITextFileBuffer buffer= getBuffer(file);
		if (buffer == null) {
			return file.getModificationStamp();
		} else {
			IDocument document= buffer.getDocument();
			if (document instanceof IDocumentExtension4) {
				return ((IDocumentExtension4)document).getModificationStamp();
			} else {
				return file.getModificationStamp();
			}
		}
	}
	
	private static ITextFileBuffer getBuffer(IFile file) {
		ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
		return manager.getTextFileBuffer(file.getFullPath());
	}
}