/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.core;

import java.util.*;

import org.eclipse.core.resources.*;
import org.eclipse.core.resources.team.FileModificationValidationContext;
import org.eclipse.core.resources.team.FileModificationValidator;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;

/**
 * Core validator that will load the UI validator only if a prompt is needed
 */
public class CVSCoreFileModificationValidator extends FileModificationValidator implements ICVSFileModificationValidator {
    
    FileModificationValidator uiValidator;

    /* (non-Javadoc)
     * @see org.eclipse.core.resources.team.FileModificationValidator#validateEdit(org.eclipse.core.resources.IFile[], org.eclipse.core.resources.team.FileModificationValidationContext)
     */
    public IStatus validateEdit(IFile[] files, FileModificationValidationContext context) {
	    IFile[] unmanagedReadOnlyFiles = getUnmanagedReadOnlyFiles(files);
	    if (unmanagedReadOnlyFiles.length > 0) {
	        IStatus status = setWritable(unmanagedReadOnlyFiles);
	        if (!status.isOK()) {
	            return status;
	        }
	    }
		IFile[] readOnlyFiles = getManagedReadOnlyFiles(files);
		if (readOnlyFiles.length == 0) return Status.OK_STATUS;
		return edit(readOnlyFiles, context);
    }

    /* (non-Javadoc)
     * @see org.eclipse.core.resources.IFileModificationValidator#validateSave(org.eclipse.core.resources.IFile)
     */
    public IStatus validateSave(IFile file) {
		if (!needsCheckout(file)) {
		    if (file.isReadOnly()) {
		        setWritable(new IFile[] { file } );
		    }
		    return Status.OK_STATUS;
		}
		return edit(new IFile[] {file}, (FileModificationValidationContext)null);
    }

    /**
     * Method for editing a set of files. Is overriden by the
     * UI to prompt the user. Default behavior is to try and load the
     * UI validator and, failing that, to edit without 
     * prompting.
     * @param readOnlyFiles
     * @param context
     * @return
     */
    protected IStatus edit(IFile[] readOnlyFiles, FileModificationValidationContext context) {
        FileModificationValidator override = getUIValidator();
        if (override != null) {
            return override.validateEdit(readOnlyFiles, context);
        } else {
	        performEdit(readOnlyFiles);
	        return Status.OK_STATUS;
        }
    }

    private FileModificationValidator getUIValidator() {
        synchronized(this) {
	        if (uiValidator == null) {
	            uiValidator = getPluggedInValidator();
	        }
        }
        return uiValidator;
    }
    
	/**
	 * @see org.eclipse.team.internal.ccvs.core.ICVSFileModificationValidator#validateMoveDelete(org.eclipse.core.resources.IFile[], org.eclipse.core.runtime.IProgressMonitor)
	 */
	public IStatus validateMoveDelete(IFile[] files, IProgressMonitor monitor) {
		IFile[] readOnlyFiles = getManagedReadOnlyFiles(files);
		if (readOnlyFiles.length == 0) return Status.OK_STATUS;

		performEdit(readOnlyFiles);
		return Status.OK_STATUS;
	}
	
    /*
     * Perform the headless edit check in the background.
     * The user will be notified of any errors that occurred.
     */
	protected void performEdit(final IFile[] readOnlyFiles) {
        setWritable(readOnlyFiles);
        Job job = new Job(CVSMessages.CVSCoreFileModificationValidator_editJob) {
            protected IStatus run(IProgressMonitor monitor) {
                try {
                    performEdit(readOnlyFiles, monitor);
                } catch (CVSException e) {
                    return e.getStatus();
                }
                return Status.OK_STATUS;
            }          
        };
        scheduleEditJob(job);
    }

    protected void scheduleEditJob(Job job) {
        job.schedule();
    }

    protected CVSTeamProvider getProvider(IFile[] files) {
		CVSTeamProvider provider = (CVSTeamProvider)RepositoryProvider.getProvider(files[0].getProject(), CVSProviderPlugin.getTypeId());
		return provider;
	}
	
	protected void performEdit(IFile[] files, IProgressMonitor monitor) throws CVSException {
		getProvider(files).edit(files, false /* recurse */, true /* notify server */, true /* notify for writtable files */, ICVSFile.NO_NOTIFICATION, monitor);
	}
	
	private boolean needsCheckout(IFile file) {
		try {
			if (file.isReadOnly()) {
				ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor(file);
				boolean managed = cvsFile.isManaged();
                return managed;
			}
		} catch (CVSException e) {
			// Log the exception and assume we don't need a checkout
			CVSProviderPlugin.log(e);
		}
		return false;
	}
	
	protected IStatus setWritable(final IFile[] files) {
        for (int i = 0; i < files.length; i++) {
        	IFile file = files[i];
        	ResourceAttributes attributes = file.getResourceAttributes();
        	if (attributes != null) {
        		attributes.setReadOnly(false);
        	}
        	try {
        		file.setResourceAttributes(attributes);
        	} catch (CoreException e) {
        		return CVSException.wrapException(e).getStatus();
        	}
        }
        return Status.OK_STATUS;
    }
    
	private IFile[] getManagedReadOnlyFiles(IFile[] files) {
		List readOnlys = new ArrayList();
		for (int i = 0; i < files.length; i++) {
			IFile iFile = files[i];
			if (needsCheckout(iFile)) {
				readOnlys.add(iFile);
			}
		}
		return (IFile[]) readOnlys.toArray(new IFile[readOnlys.size()]);
	}
	
    protected IFile[] getUnmanagedReadOnlyFiles(IFile[] files) {
		List readOnlys = new ArrayList();
		for (int i = 0; i < files.length; i++) {
			IFile iFile = files[i];
			if (iFile.isReadOnly() && !needsCheckout(iFile)) {
				readOnlys.add(iFile);
			}
		}
		return (IFile[]) readOnlys.toArray(new IFile[readOnlys.size()]);
    }
    
	private static FileModificationValidator getPluggedInValidator() {
		IExtension[] extensions = Platform.getExtensionRegistry().getExtensionPoint(CVSProviderPlugin.ID, CVSProviderPlugin.PT_FILE_MODIFICATION_VALIDATOR).getExtensions();
		if (extensions.length == 0)
			return null;
		IExtension extension = extensions[0];
		IConfigurationElement[] configs = extension.getConfigurationElements();
		if (configs.length == 0) {
			CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("The CVS file modification validator is missing from extension {0}", (new Object[] {extension.getUniqueIdentifier()})), null);//$NON-NLS-1$
			return null;
		}
		try {
			IConfigurationElement config = configs[0];
			return (FileModificationValidator) config.createExecutableExtension("run");//$NON-NLS-1$
		} catch (CoreException ex) {
			CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("The CVS file modification validator registered as ID {0} could not be instantiated", (new Object[] {extension.getUniqueIdentifier()})), ex);//$NON-NLS-1$
			return null;
		}
	}
    
    public ISchedulingRule validateEditRule(CVSResourceRuleFactory factory, IResource[] resources) {
        FileModificationValidator override = getUIValidator();
        if (override instanceof CVSCoreFileModificationValidator && override != this) {
            CVSCoreFileModificationValidator ui = (CVSCoreFileModificationValidator) override;
            return ui.validateEditRule(factory, resources);
        }
        return internalValidateEditRule(factory, resources);
    }

    protected final ISchedulingRule internalValidateEditRule(CVSResourceRuleFactory factory, IResource[] resources) {
        if (resources.length == 0)
            return null;
        //optimize rule for single file
        if (resources.length == 1)
            return isReadOnly(resources[0]) ? factory.getParent(resources[0]) : null;
        //need a lock on the parents of all read-only files
        HashSet rules = new HashSet();
        for (int i = 0; i < resources.length; i++)
            if (isReadOnly(resources[i]))
                rules.add(factory.getParent(resources[i]));
        return createSchedulingRule(rules);
    }

    protected ISchedulingRule createSchedulingRule(Set rules) {
        if (rules.isEmpty())
            return null;
        if (rules.size() == 1)
            return (ISchedulingRule) rules.iterator().next();
        ISchedulingRule[] ruleArray = (ISchedulingRule[]) rules
                .toArray(new ISchedulingRule[rules.size()]);
        return new MultiRule(ruleArray);
    }
    
    protected final boolean isReadOnly(IResource resource) {
        ResourceAttributes a = resource.getResourceAttributes();
        if (a != null) {
            return a.isReadOnly();
        }
        return false;
    }
}
