/*******************************************************************************
 * Copyright (c) 2008, 2018 Standards for Technology in Automotive Retail and others.
 * All rights reserved. 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:
 *     David Carver - initial API and implementation - bug 212330 -
 *                    Based off FormatActionDelegate.java
 *******************************************************************************/

package org.eclipse.wst.sse.ui.internal.handlers;

import java.io.IOException;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.wst.sse.core.internal.exceptions.MalformedInputExceptionWithDetail;
import org.eclipse.wst.sse.core.internal.format.IStructuredFormatProcessor;
import org.eclipse.wst.sse.ui.internal.FormatProcessorsExtensionReader;
import org.eclipse.wst.sse.ui.internal.Logger;
import org.eclipse.wst.sse.ui.internal.SSEUIMessages;
import org.eclipse.wst.sse.ui.internal.SSEUIPlugin;

public class FormatHandler extends AbstractHandler implements IHandler {

	protected IStructuredSelection fSelection;
	
	public void dispose() {
		// nulling out just in case
	}
	
	private IWorkbenchSiteProgressService getActiveProgressService() {
		IWorkbenchSiteProgressService service = null;
		if (PlatformUI.isWorkbenchRunning()) {
			IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
			if (activeWorkbenchWindow != null) {
				IWorkbenchPage activePage = activeWorkbenchWindow.getActivePage();
				if (activePage != null) {
					IWorkbenchPart activePart = activePage.getActivePart();
					if (activePart != null) {
						service = activePart.getSite().getAdapter(IWorkbenchSiteProgressService.class);
					}
				}
			}
		}
		return service;
	}
	
	
	

	public Object execute(ExecutionEvent event) throws ExecutionException {
		
	    ISelection selection = HandlerUtil.getActiveWorkbenchWindow(event).getSelectionService().getSelection();
		
		if (selection instanceof IStructuredSelection) {
			fSelection = (IStructuredSelection) selection;
			boolean available = false;

			Object[] elements = fSelection.toArray();
			for (int i = 0; i < elements.length; i++) {
				if (elements[i] instanceof IResource) {
					available = processorAvailable((IResource) elements[i]);

					if (available)
						break;
				}
			}
		}
		
		if (fSelection != null && !fSelection.isEmpty()) {
			Job job = getJob();
			if (job != null) {
				IWorkbenchSiteProgressService progressService = getActiveProgressService();
				if (progressService != null) {
					progressService.schedule(job);
				}
				else {
					job.schedule();
				}
			}
		}
	
		return null;
	}
	
	
	class FormatJob extends Job {

		public FormatJob(String name) {
			super(name);
		}

		protected IStatus run(IProgressMonitor monitor) {
			IStatus status = Status.OK_STATUS;

			Object[] elements = fSelection.toArray();
			monitor.beginTask("", elements.length); //$NON-NLS-1$
			for (int i = 0; i < elements.length; i++) {
				if (elements[i] instanceof IResource) {
					process(new SubProgressMonitor(monitor, 1), (IResource) elements[i]);
				}
				else {
					monitor.worked(1);
				}
			}
			monitor.done();

			if (fErrorStatus.getChildren().length > 0) {
				status = fErrorStatus;
				fErrorStatus = new MultiStatus(SSEUIPlugin.ID, IStatus.ERROR, SSEUIMessages.FormatActionDelegate_errorStatusMessage, null); //$NON-NLS-1$
			}

			return status;
		}

	}

	private MultiStatus fErrorStatus = new MultiStatus(SSEUIPlugin.ID, IStatus.ERROR, SSEUIMessages.FormatActionDelegate_errorStatusMessage, null); //$NON-NLS-1$

	protected void format(IProgressMonitor monitor, IFile file) {
		if(monitor == null || monitor.isCanceled())
			return;
		
		try {
			monitor.beginTask("", 100);
			IContentDescription contentDescription = file.getContentDescription();
			monitor.worked(5);
			if (contentDescription != null) {
				IContentType contentType = contentDescription.getContentType();
				IStructuredFormatProcessor formatProcessor = getFormatProcessor(contentType.getId());
				if (formatProcessor != null && (monitor == null || !monitor.isCanceled())) {
					String message = NLS.bind(SSEUIMessages.FormatActionDelegate_3, new String[]{file.getFullPath().toString().substring(1)});					monitor.subTask(message);
					formatProcessor.setProgressMonitor(new SubProgressMonitor(monitor, 95));
					formatProcessor.formatFile(file);
				}
			}
			monitor.done();
		} catch (MalformedInputExceptionWithDetail e) {
			String message = NLS.bind(SSEUIMessages.FormatActionDelegate_5, new String[]{file.getFullPath().toString()});
			fErrorStatus.add(new Status(IStatus.ERROR, SSEUIPlugin.ID, IStatus.ERROR, message, e));
		} catch (IOException e) {
			String message = NLS.bind(SSEUIMessages.FormatActionDelegate_4, new String[]{file.getFullPath().toString()});
			fErrorStatus.add(new Status(IStatus.ERROR, SSEUIPlugin.ID, IStatus.ERROR, message, e));
		} catch (CoreException e) {
			String message = NLS.bind(SSEUIMessages.FormatActionDelegate_4, new String[]{file.getFullPath().toString()});
			fErrorStatus.add(new Status(IStatus.ERROR, SSEUIPlugin.ID, IStatus.ERROR, message, e));
		}
	}

	protected void format(final IProgressMonitor monitor, IResource resource) {
		if (resource instanceof IFile) {
			final IFile file = (IFile) resource;

			// BUG 178598 - If the resource is shared, and it's possible to
			// get the workbench Display, the UI thread is asked to execute the
			// format of the file when it can
			monitor.beginTask("", 20); //$NON-NLS-1$
			try {
				ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
				ITextFileBuffer buffer = null;
				
				try {
					if(manager != null) {
						manager.connect(file.getFullPath(), LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
						buffer = manager.getTextFileBuffer(resource.getFullPath(), LocationKind.IFILE);
					}
					
					if(buffer != null && buffer.isShared()) {
						Display display = getDisplay();
						if (display != null) {
							display.syncExec(new Runnable() {
								public void run() {
									format(new SubProgressMonitor(monitor, 18), file);
								}
							});
						}
					}
					else
						format(new SubProgressMonitor(monitor, 18), file);
				}
				finally {
					if(manager != null)
						manager.disconnect(file.getFullPath(), LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
				}
				monitor.done();
			}
			catch(CoreException e) {
				String message = NLS.bind(SSEUIMessages.FormatActionDelegate_4, new String[]{file.getFullPath().toString()});
				fErrorStatus.add(new Status(IStatus.ERROR, SSEUIPlugin.ID, IStatus.ERROR, message, e));
			}
			finally {
				if(monitor != null)
					monitor.done();
			}
			
			
		} else if (resource instanceof IContainer) {
			IContainer container = (IContainer) resource;

			try {
				IResource[] members = container.members();
				monitor.beginTask("", members.length); //$NON-NLS-1$
				for (int i = 0; i < members.length; i++) {
					if (monitor != null && !monitor.isCanceled())
						format(new SubProgressMonitor(monitor, 1), members[i]);
				}
				monitor.done();
			} catch (CoreException e) {
				String message = NLS.bind(SSEUIMessages.FormatActionDelegate_4, new String[]{resource.getFullPath().toString()});
				fErrorStatus.add(new Status(IStatus.ERROR, SSEUIPlugin.ID, IStatus.ERROR, message, e));
			}
		}
	}
	
	private Display getDisplay() {

		// Note: the workbench should always have a display
		// (unless running headless), whereas Display.getCurrent()
		// only returns the display if the currently executing thread
		// has one.
		if (PlatformUI.isWorkbenchRunning())
			return PlatformUI.getWorkbench().getDisplay();
		else
			return null;
	}

	protected IStructuredFormatProcessor getFormatProcessor(String contentTypeId) {
		return FormatProcessorsExtensionReader.getInstance().getFormatProcessor(contentTypeId);
	}

	protected Job getJob() {
		return new FormatJob(SSEUIMessages.FormatActionDelegate_jobName); //$NON-NLS-1$
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.wst.sse.ui.edit.util.ResourceActionDelegate#process(org.eclipse.core.runtime.IProgressMonitor,
	 *      org.eclipse.core.resources.IResource)
	 */
	protected void process(IProgressMonitor monitor, IResource resource) {
		monitor.beginTask("", 100);
		format(new SubProgressMonitor(monitor, 98), resource);

		try {
			resource.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 2));
		} catch (CoreException e) {
			String message = NLS.bind(SSEUIMessages.FormatActionDelegate_4, new String[]{resource.getFullPath().toString()});
			fErrorStatus.add(new Status(IStatus.ERROR, SSEUIPlugin.ID, IStatus.ERROR, message, e));
		}
		monitor.done();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.sse.ui.internal.actions.ResourceActionDelegate#processorAvailable(org.eclipse.core.resources.IResource)
	 */
	protected boolean processorAvailable(IResource resource) {
		boolean result = false;
		if (resource.isAccessible()) {
			try {
				if (resource instanceof IFile) {
					IFile file = (IFile) resource;

					IStructuredFormatProcessor formatProcessor = null;
					IContentDescription contentDescription = file.getContentDescription();
					if (contentDescription != null) {
						IContentType contentType = contentDescription.getContentType();
						formatProcessor = getFormatProcessor(contentType.getId());
					}
					if (formatProcessor != null)
						result = true;
				}
				else if (resource instanceof IContainer) {
					IContainer container = (IContainer) resource;
					IResource[] members;
					members = container.members();
					for (int i = 0; i < members.length; i++) {
						boolean available = processorAvailable(members[i]);

						if (available) {
							result = true;
							break;
						}
					}
				}
			}
			catch (CoreException e) {
				Logger.logException(e);
			}
		}

		return result;
	}
	
}
