/*
* Copyright (c) 2005 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/

package com.nokia.sdt.sourcegen;

import com.nokia.sdt.datamodel.IDesignerDataModel;
import com.nokia.sdt.editor.EditorServices;
import com.nokia.sdt.editor.IDesignerDataModelEditor;
import com.nokia.sdt.editor.EditorServices.EditorListener;
import com.nokia.sdt.editor.IDesignerDataModelEditor.SaveListener;
import com.nokia.sdt.sourcegen.core.Messages;
import com.nokia.cpp.internal.api.utils.core.Logging;
import com.nokia.cpp.internal.api.utils.core.TextUtils;
import com.nokia.cpp.internal.api.utils.ui.UITaskUtils;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.*;
import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.eclipse.ui.model.*;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * The main plugin class to be used in the desktop (auto-generated)
 */
public class SourceGenPlugin extends AbstractUIPlugin implements SaveListener, EditorListener {
	//The shared instance.
	private static SourceGenPlugin plugin;
	//Resource bundle.
	private ResourceBundle resourceBundle;

	// Pattern matcher for files which might be written by sourcegen
	static final Pattern sourcePattern = Pattern.compile(
		".*\\.(c|cpp|h|hpp|ra|rss|rssi|loc|l[0-9]{2}|rls)$"); //$NON-NLS-1$


	/**
	 * The constructor.
	 */
	public SourceGenPlugin() {
		super();
		plugin = this;
		
		// attach to any open editors
		IEditorPart[] editors = EditorServices.getEditors();
		for (int i = 0; i < editors.length; i++) {
			editorAdded(editors[i]);
		}
		EditorServices.addListener(this);
	}

	/**
	 * This method is called upon plug-in activation
	 */
	public void start(BundleContext context) throws Exception {
		super.start(context);
		EditorServices.addListener(this);
	}

	/**
	 * This method is called when the plug-in is stopped
	 */
	public void stop(BundleContext context) throws Exception {
		super.stop(context);
		EditorServices.removeListener(this);
		plugin = null;
		resourceBundle = null;
	}

	/**
	 * Returns the shared instance.
	 */
	public static SourceGenPlugin getDefault() {
		return plugin;
	}

	/**
	 * Returns the string from the plugin's resource bundle,
	 * or 'key' if not found.
	 */
	public static String getResourceString(String key) {
		ResourceBundle bundle = SourceGenPlugin.getDefault().getResourceBundle();
		try {
			return (bundle != null) ? bundle.getString(key) : key;
		} catch (MissingResourceException e) {
			return key;
		}
	}

	/**
	 * Returns the plugin's resource bundle,
	 */
	public ResourceBundle getResourceBundle() {
		try {
			if (resourceBundle == null)
				resourceBundle = ResourceBundle.getBundle("com.nokia.sdt.sourcegen.SourceGenPluginResources"); //$NON-NLS-1$
		} catch (MissingResourceException x) {
			resourceBundle = null;
		}
		return resourceBundle;
	}

	/**
	 * Returns an image descriptor for the image file at the given
	 * plug-in relative path.
	 *
	 * @param path the path
	 * @return the image descriptor
	 */
	public static ImageDescriptor getImageDescriptor(String path) {
		return AbstractUIPlugin.imageDescriptorFromPlugin("com.nokia.sdt.sourcegen", path); //$NON-NLS-1$
	}

	/**
	 */
	public void log(Throwable t) {
		IStatus status = Logging.newSimpleStatus(0, t);
		Logging.log(this, status);
	}

    public IStatus createStatus(int severity, String message, Throwable t) {
        return Logging.newStatus(this, severity, message, t);
    }
    
	static interface IEditorPartFilter {
		public boolean accept(IEditorPart part);
	}
	
	/** 
	 * Save editors that appear to be source-related.
	 */
	public boolean saveAllSourceEditors(final IEditorPartFilter filter, boolean confirm) {
		final IWorkbench workbench = PlatformUI.getWorkbench();
		final boolean finalConfirm = confirm;
		final boolean[] result = new boolean[1];
		result[0] = true;

		SafeRunner.run(new SafeRunnable() {
			public void run() {
				// Collect unsaved parts
				ArrayList dirtyParts = new ArrayList();
				ArrayList dirtyEditorsInput = new ArrayList();
				IWorkbenchWindow windows[] = workbench.getWorkbenchWindows();
				for (int i = 0; i < windows.length; i++) {
					IWorkbenchPage pages[] = windows[i].getPages();
					for (int j = 0; j < pages.length; j++) {
						IWorkbenchPage page = (IWorkbenchPage) pages[j];
						
                        ISaveablePart[] parts = page.getDirtyEditors();
                        
                        for (int k = 0; k < parts.length; k++) {
                            ISaveablePart part = parts[k];
                            
                            if (part instanceof IEditorPart) {
                            	IEditorPart editor = (IEditorPart)part;
                            	if (editor.isSaveOnCloseNeeded() && filter.accept(editor)) {
                                    if (!dirtyEditorsInput.contains(editor
                                            .getEditorInput())) {
                                        dirtyParts.add(editor);
                                        dirtyEditorsInput.add(editor
                                                .getEditorInput());
                                    }
                                }
                            }
                        }
   					}
				}
				
				if (dirtyParts.size() > 0) {
					IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
					if (window == null)
						window = windows[0];
					result[0] = confirmAndSaveAll(dirtyParts, finalConfirm, window);
				}
			}
		});
		return result[0];
	}

	/**
	 * Put up a dialog showing possible sources that will be
	 * modified and allow the user to select items to save,
	 * and save the selected items.
	 * <p>
	 * This is a ripoff of the Eclipse EditorManager#saveAll()
	 * method, which is internal.
	 * @param dirtyParts
	 * @return true: saving confirmed, false: save canceled
	 */
	private boolean confirmAndSaveAll(List dirtyParts, boolean confirm, IWorkbenchWindow window) {
    	// If the editor list is empty return.
        if (dirtyParts.isEmpty())
        	return true;
        
        // Convert the list into an element collection.
        AdaptableList input = new AdaptableList(dirtyParts);

        ListSelectionDialog dlg = new ListSelectionDialog(
                window.getShell(), input,
                new BaseWorkbenchContentProvider(),
                new WorkbenchPartLabelProvider(), 
                Messages.getString("SourceGenPlugin.SelectResources")); //$NON-NLS-1$

        dlg.setInitialSelections(dirtyParts
                .toArray(new Object[dirtyParts.size()]));
        dlg.setTitle(Messages.getString("SourceGenPlugin.SaveSourcesTitle")); //$NON-NLS-1$
        int result = dlg.open();

        if (result == IDialogConstants.CANCEL_ID)
            return false;

        final List savingDirtyParts = Arrays.asList(dlg.getResult());

        final boolean[] saveSucceeded = new boolean[1];
        
		saveSucceeded[0] = true;
        UITaskUtils.runImmediately(new IRunnableWithProgress() {

			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
				monitor.beginTask(Messages.getString("SourceGenPlugin.SavingEditors"), savingDirtyParts.size()); //$NON-NLS-1$
		        for (Iterator iter = savingDirtyParts.iterator(); iter.hasNext();) {
		        	if (monitor.isCanceled())
		        		saveSucceeded[0] = false;
		        	IEditorPart editor = (IEditorPart) iter.next();
		        	editor.doSave(monitor);
		        	monitor.worked(1);
				}
				monitor.done();
			}
        	
        });
        
        return saveSucceeded[0];
	}

	/**
	 * Check that the workspace is synchronized and in a 
	 * sane state to save sources.
	 * @return true: everything is up-to-date, false: cancel the save
	 */
	private boolean checkSynchronizedWorkspace(IDesignerDataModel dataModel) {
		// ask to save any source editors
		if (dataModel.getProjectContext() == null)
			return true;
		final IProject theProject = dataModel.getProjectContext().getProject();
		if (theProject == null)
			return true;
		
		IEditorPartFilter filter = new IEditorPartFilter() {

			public boolean accept(IEditorPart part) {
				// only look at stuff in this project
				IResource rsrc = (IResource) part.getEditorInput().getAdapter(IResource.class);
				if (rsrc != null) {
					IProject project = rsrc.getProject();
					if (project == null || !project.equals(theProject))
						return false;
				}
				String name = part.getEditorInput().getName();
				return sourcePattern.matcher(name).matches();
			}
		};
		if (!saveAllSourceEditors(filter, true))
			return false;
		
		// check for changes made outside workspace
		IProject project = null;
		if (dataModel.getProjectContext() != null
				&& (project = dataModel.getProjectContext().getProject()) != null) {
			
			final List<IPath> unsynchronizedResources = new ArrayList<IPath>();
			
			IResourceProxyVisitor visitor = new IResourceProxyVisitor() {
				public boolean visit(IResourceProxy resourceProxy) throws CoreException {
					String name = resourceProxy.getName();
					boolean looksLikeSource = sourcePattern.matcher(name).matches();
					if (looksLikeSource || resourceProxy.getType() == IResource.FOLDER) {
						IResource resource = resourceProxy.requestResource();
						if (resource != null && !resource.isSynchronized(IResource.DEPTH_ZERO))
							unsynchronizedResources.add(resource.getProjectRelativePath());
					}
					return resourceProxy.getType() == IResource.FOLDER ||
						resourceProxy.getType() == IResource.PROJECT;
				}
			};
			
			try {
				project.accept(visitor, 0);
				if (unsynchronizedResources.size() == 0)
					return true;
			} catch (CoreException e1) {
				SourceGenPlugin.getDefault().log(e1);
				// fall through, something bad happened
			}
			
			Shell shell = null;
			boolean doSync = MessageDialog.openConfirm(shell, 
					Messages.getString("SourceGenProvider.ProjectOutOfSync"), //$NON-NLS-1$
					MessageFormat.format(
							Messages.getString("SourceGenProvider.ProjectOutOfSyncMessage"), //$NON-NLS-1$
							new Object[] { project.getName(), 
								TextUtils.formatTabbedList(unsynchronizedResources) }));
			if (doSync) {
				try {
					project.refreshLocal(IResource.DEPTH_INFINITE, null);
					return true;
				} catch (CoreException e) {
					SourceGenPlugin.getDefault().log(e);
					Logging.showErrorDialog(Messages.getString("SourceGenProvider.Error"),  //$NON-NLS-1$
							null, e.getStatus());
					return false;
				}
			} else {
				return false;
			}
		}
		return true;
	}


	/* (non-Javadoc)
	 * @see com.nokia.sdt.ui.IDesignerEditorBase.SaveListener#queryAboutToSave(com.nokia.sdt.ui.IDesignerEditorBase)
	 */
	public boolean queryAboutToSave(IDesignerDataModelEditor editor) {
		return checkSynchronizedWorkspace(editor.getDataModel());
	}

	/* (non-Javadoc)
	 * @see com.nokia.sdt.ui.IDesignerEditorBase.SaveListener#preSaveNotify(com.nokia.sdt.ui.IDesignerEditorBase)
	 */
	public void preSaveNotify(IDesignerDataModelEditor editor, IProgressMonitor monitor) {
	}

	/* (non-Javadoc)
	 * @see com.nokia.sdt.ui.IDesignerEditorBase.SaveListener#postSaveNotify(com.nokia.sdt.ui.IDesignerEditorBase)
	 */
	public void postSaveNotify(IDesignerDataModelEditor editor, IProgressMonitor monitor) {
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference)
	 */
	public void editorAdded(IEditorPart part) {
		IDesignerDataModelEditor editor = (IDesignerDataModelEditor) part.getAdapter(IDesignerDataModelEditor.class);
		if (editor != null)
			editor.addSaveListener(SourceGenPlugin.this);
	}

	public void editorRemoved(IEditorPart part) {
		IDesignerDataModelEditor editor = (IDesignerDataModelEditor) part.getAdapter(IDesignerDataModelEditor.class);
		if (editor != null)
			editor.removeSaveListener(SourceGenPlugin.this);
	}
}
