/*******************************************************************************
 * Copyright (c) 2004, 2009 Tasktop Technologies 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
 *     Sebastian Davids <sdavids@gmx.de> - layout tweaks
 *     Jeff Pound - modified for attachment input
 *     Chris Aniszczyk <caniszczyk@gmail.com> - bug 20957
 *     Tasktop Technologies - improvements
 *******************************************************************************/

package org.eclipse.mylyn.internal.tasks.ui.wizards;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.resources.mapping.ResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.mylyn.internal.tasks.core.data.FileTaskAttachmentSource;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.wizards.TaskAttachmentWizard.ClipboardTaskAttachmentSource;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentSource;
import org.eclipse.mylyn.tasks.core.data.TaskAttachmentModel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.views.navigator.ResourceComparator;

/**
 * A wizard to input the source of the attachment.
 * <p>
 * Based on org.eclipse.compare.internal.InputPatchPage.
 */
public class AttachmentSourcePage extends WizardPage {

	// constants
	protected static final int SIZING_TEXT_FIELD_WIDTH = 250;

	protected static final int COMBO_HISTORY_LENGTH = 5;

	// input constants
	protected final static int CLIPBOARD = 1;

	protected final static int FILE = 2;

	protected final static int WORKSPACE = 3;

	protected final static int SCREENSHOT = 4;

	static final char SEPARATOR = System.getProperty("file.separator").charAt(0); //$NON-NLS-1$

	private boolean showError = false;

	// SWT widgets
	private Button useClipboardButton;

//	private Button useScreenshotButton;

	private Combo fileNameField;

	private Button fileBrowseButton;

	private Button useFileButton;

	private Button useWorkspaceButton;

	private Label workspaceSelectLabel;

	private TreeViewer treeViewer;

	private String clipboardContents;

	private boolean initUseClipboard = false;

	private final String DIALOG_SETTINGS = "InputAttachmentSourcePage"; //$NON-NLS-1$

	private final String S_LAST_SELECTION = "lastSelection"; //$NON-NLS-1$

	private final String S_FILE_HISTORY = "fileHistory"; //$NON-NLS-1$

	private final String S_LAST_FILE = "lastFile"; //$NON-NLS-1$

	private final TaskAttachmentModel model;

	/**
	 * The last filename that was selected through the browse button.
	 */
	private String lastFilename;

	public AttachmentSourcePage(TaskAttachmentModel model) {
		super("InputAttachmentPage"); //$NON-NLS-1$
		this.model = model;
		setTitle(Messages.AttachmentSourcePage_Select_attachment_source);
		setDescription(Messages.AttachmentSourcePage_Clipboard_supports_text_and_image_attachments_only);
		// setMessage("Please select the source for the attachment");
	}

	private void restoreDialogSettings() {
		IDialogSettings settings = getDialogSettings();
		if (settings == null) {
			updateWidgetEnablements();
			return;
		}

		String selection = settings.get(S_LAST_SELECTION);
		if (selection != null) {
			setInputMethod(Integer.valueOf(selection).intValue());
		} else {
			updateWidgetEnablements();
		}

		String[] fileNames = settings.getArray(S_FILE_HISTORY);
		if (fileNames != null) {
			// destination
			for (String fileName : fileNames) {
				fileNameField.add(fileName);
			}
		}
		lastFilename = settings.get(S_LAST_FILE);
	}

	/*
	 * Get a path from the supplied text widget. @return
	 * org.eclipse.core.runtime.IPath
	 */
	protected IPath getPathFromText(Text textField) {
		return (new Path(textField.getText())).makeAbsolute();
	}

	public String getAttachmentName() {
		if (getInputMethod() == CLIPBOARD) {
			return Messages.AttachmentSourcePage__Clipboard_;
		} else if (getInputMethod() == WORKSPACE) {
			return getResources(treeViewer.getSelection())[0].getFullPath().toOSString();
		}
		return getAttachmentFilePath();
	}

	public void createControl(Composite parent) {
		Composite composite = new Composite(parent, SWT.NULL);
		composite.setLayout(new GridLayout());
		GridData gd = new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL);
		gd.heightHint = 800;
		composite.setLayoutData(gd);
		setControl(composite);

		initializeDialogUnits(parent);

		createAttachmentFileGroup(composite);

		// No error for dialog opening
		showError = false;
		clearErrorMessage();
		restoreDialogSettings();

		Dialog.applyDialogFont(composite);
	}

	@Override
	public IWizardPage getNextPage() {
		AbstractTaskAttachmentSource source = getSource();
		model.setSource(source);
		if (source != null) {
			model.setContentType(source.getContentType());
		}
		saveDialogSettings();
		return super.getNextPage();
	}

	private void saveDialogSettings() {
		IDialogSettings settings = getDialogSettings();
		settings.put(S_LAST_SELECTION, getInputMethod());

		String[] fileNames = settings.getArray(S_FILE_HISTORY);
		String newFileName = fileNameField.getText().trim();
		if (getInputMethod() == FILE && newFileName.length() > 0) {
			List<String> history = new ArrayList<String>(10);
			history.add(newFileName);
			if (fileNames != null) {
				for (int i = 0; i < fileNames.length && history.size() < COMBO_HISTORY_LENGTH; i++) {
					if (!newFileName.equals(fileNames[i])) {
						history.add(fileNames[i]);
					}
				}
			}
			settings.put(S_FILE_HISTORY, history.toArray(new String[0]));
		}

		settings.put(S_LAST_FILE, lastFilename);
	}

	@Override
	public boolean canFlipToNextPage() {
		return isPageComplete();
	}

	private void setEnableAttachmentFile(boolean enable) {
		fileNameField.setEnabled(enable);
		fileBrowseButton.setEnabled(enable);
	}

	private void setEnableWorkspaceAttachment(boolean enable) {
		workspaceSelectLabel.setEnabled(enable);
		treeViewer.getTree().setEnabled(enable);
	}

	/*
	 * Create the group for selecting the attachment file
	 */
	private void createAttachmentFileGroup(Composite parent) {
		final Composite composite = new Composite(parent, SWT.NULL);
		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 3;
		composite.setLayout(gridLayout);
		composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		// new row
		GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		useFileButton = new Button(composite, SWT.RADIO);
		useFileButton.setText(Messages.AttachmentSourcePage_File);

		fileNameField = new Combo(composite, SWT.BORDER);
		gd = new GridData(GridData.FILL_HORIZONTAL);
		gd.widthHint = SIZING_TEXT_FIELD_WIDTH;
		fileNameField.setLayoutData(gd);
		fileNameField.setText(""); //$NON-NLS-1$

		fileBrowseButton = new Button(composite, SWT.PUSH);
		fileBrowseButton.setText(Messages.AttachmentSourcePage_Browse_);
		GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
		Point minSize = fileBrowseButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
		data.widthHint = Math.max(widthHint, minSize.x);
		fileBrowseButton.setLayoutData(data);

		// new row		
		gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.horizontalSpan = 3;
		useClipboardButton = new Button(composite, SWT.RADIO);
		useClipboardButton.setText(Messages.AttachmentSourcePage_Clipboard);
		useClipboardButton.setLayoutData(gd);
		useClipboardButton.setSelection(initUseClipboard);

		// new row
		useWorkspaceButton = new Button(composite, SWT.RADIO);
		useWorkspaceButton.setText(Messages.AttachmentSourcePage_Workspace);
		gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		useWorkspaceButton.setLayoutData(gd);

		addWorkspaceControls(parent);

		// Add listeners
		useClipboardButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				if (!useClipboardButton.getSelection()) {
					return;
				}

				clearErrorMessage();
				showError = true;
				storeClipboardContents();
				updateWidgetEnablements();
			}
		});

		useFileButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				if (!useFileButton.getSelection()) {
					return;
				}
				// If there is anything typed in at all
				clearErrorMessage();
				showError = (fileNameField.getText() != ""); //$NON-NLS-1$
				updateWidgetEnablements();
			}
		});
		fileNameField.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				setSourceName(fileNameField.getText());
				updateWidgetEnablements();
			}
		});
		fileNameField.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				clearErrorMessage();
				showError = true;
				updateWidgetEnablements();
			}
		});
		fileBrowseButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				clearErrorMessage();
				showError = true;

				FileDialog fileChooser = new FileDialog(composite.getShell(), SWT.OPEN);
				fileChooser.setText(Messages.AttachmentSourcePage_Select_File_Dialog_Title);
				if (fileNameField.getText().trim().length() > 0) {
					lastFilename = fileNameField.getText().trim();
				}
				fileChooser.setFileName(lastFilename);
				String file = fileChooser.open();
				if (file == null) {
					return;
				}

				// remember the last selected directory 
				lastFilename = file;
				fileNameField.setText(file);
				updateWidgetEnablements();
			}
		});
		useWorkspaceButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				if (!useWorkspaceButton.getSelection()) {
					return;
				}
				clearErrorMessage();
				// If there is anything typed in at all
				showError = (!treeViewer.getSelection().isEmpty());
				updateWidgetEnablements();
			}
		});

		treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				clearErrorMessage();
				updateWidgetEnablements();
			}
		});

		treeViewer.addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				ISelection selection = event.getSelection();
				if (selection instanceof TreeSelection) {
					TreeSelection treeSel = (TreeSelection) selection;
					Object res = treeSel.getFirstElement();
					if (res != null) {
						if (res instanceof IProject || res instanceof IFolder) {
							if (treeViewer.getExpandedState(res)) {
								treeViewer.collapseToLevel(res, 1);
							} else {
								treeViewer.expandToLevel(res, 1);
							}
						} else if (res instanceof IFile) {
							getContainer().showPage(getNextPage());
						}
					}
				}
			}
		});

		useFileButton.setSelection(!initUseClipboard);
		setEnableWorkspaceAttachment(false);
	}

	private void addWorkspaceControls(Composite composite) {

		Composite newComp = new Composite(composite, SWT.NONE);
		GridLayout layout = new GridLayout(1, false);
		layout.marginLeft = 16; // align w/ lable of check button
		newComp.setLayout(layout);
		newComp.setLayoutData(new GridData(GridData.FILL_BOTH));

		workspaceSelectLabel = new Label(newComp, SWT.LEFT);
		workspaceSelectLabel.setText(Messages.AttachmentSourcePage_Select_the_location_of_the_attachment);

		treeViewer = new TreeViewer(newComp, SWT.BORDER);
		treeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		treeViewer.setLabelProvider(new WorkbenchLabelProvider());
		treeViewer.setContentProvider(new WorkbenchContentProvider());
		treeViewer.setComparator(new ResourceComparator(ResourceComparator.NAME));
		treeViewer.setInput(ResourcesPlugin.getWorkspace().getRoot());
	}

	/**
	 * Updates the enable state of this page's controls.
	 */
	private void updateWidgetEnablements() {

		String error = null;

		boolean attachmentFound = false;
		int inputMethod = getInputMethod();
		if (inputMethod == CLIPBOARD) {
			if (ClipboardTaskAttachmentSource.isSupportedType(getControl().getDisplay())) {
				attachmentFound = true;
			} else {
				error = Messages.AttachmentSourcePage_Clipboard_contains_an_unsupported_data;
			}
		} else if (inputMethod == SCREENSHOT) {
			attachmentFound = true;
		} else if (inputMethod == FILE) {
			String path = fileNameField.getText();
			if (path != null && path.length() > 0) {
				File file = new File(path);
				attachmentFound = file.exists() && file.isFile() && file.length() > 0;
				if (!attachmentFound) {
					error = Messages.AttachmentSourcePage_Cannot_locate_attachment_file;
				}
			} else {
				error = Messages.AttachmentSourcePage_No_file_name;
			}
		} else if (inputMethod == WORKSPACE) {
			// Get the selected attachment file (tree will only allow for one
			// selection)
			IResource[] resources = getResources(treeViewer.getSelection());
			if (resources == null || resources.length <= 0) {
				error = Messages.AttachmentSourcePage_No_file_name;
			} else {
				IResource attachmentFile = resources[0];
				if (attachmentFile != null && attachmentFile.getType() == IResource.FILE) {
					File actualFile = attachmentFile.getRawLocation().toFile();
					attachmentFound = actualFile.exists() && actualFile.isFile() && actualFile.length() > 0;
					if (!attachmentFound) {
						error = Messages.AttachmentSourcePage_Cannot_locate_attachment_file;
					}
				}
			}
		}

		setPageComplete(attachmentFound);

		if (showError) {
			setErrorMessage(error);
		}

		setEnableAttachmentFile(inputMethod == FILE);
		setEnableWorkspaceAttachment(inputMethod == WORKSPACE);
	}

	/**
	 * Sets the source name of the import to be the supplied path. Adds the name of the path to the list of items in the
	 * source combo and selects it.
	 * 
	 * @param path
	 *            the path to be added
	 */
	protected void setSourceName(String path) {

		if (path.length() > 0) {

			String[] currentItems = fileNameField.getItems();
			int selectionIndex = -1;
			for (int i = 0; i < currentItems.length; i++) {
				if (currentItems[i].equals(path)) {
					selectionIndex = i;
				}
			}

			if (selectionIndex < 0) { // not found in history
				int oldLength = currentItems.length;
				String[] newItems = new String[oldLength + 1];
				System.arraycopy(currentItems, 0, newItems, 0, oldLength);
				newItems[oldLength] = path;
				fileNameField.setItems(newItems);
				selectionIndex = oldLength;
			}
			fileNameField.select(selectionIndex);

			// resetSelection();
		}
	}

	/*
	 * Clears the dialog message box
	 */
	private void clearErrorMessage() {
		setErrorMessage(null);
	}

	protected int getInputMethod() {
		if (useClipboardButton == null) {
			if (initUseClipboard) {
				return CLIPBOARD;
			}
			return FILE;
		}
		if (useClipboardButton.getSelection()) {
			return CLIPBOARD;
		}
		if (useFileButton.getSelection()) {
			return FILE;
		}
		return WORKSPACE;
	}

	protected void setInputMethod(int input) {
		switch (input) {
		case WORKSPACE:
			useWorkspaceButton.setSelection(true);
			useClipboardButton.setSelection(false);
			useFileButton.setSelection(false);
			break;
		case CLIPBOARD:
			storeClipboardContents();

			useClipboardButton.setSelection(true);
			useFileButton.setSelection(false);
			useWorkspaceButton.setSelection(false);
			break;
		default:
			useFileButton.setSelection(true);
			useWorkspaceButton.setSelection(false);
			useClipboardButton.setSelection(false);
			break;
		}
		updateWidgetEnablements();
	}

	private String getAttachmentFilePath() {
		if (fileNameField != null) {
			return fileNameField.getText();
		}
		return null;
	}

	public String getAbsoluteAttachmentPath() {
		switch (getInputMethod()) {
		case CLIPBOARD:
			return Messages.AttachmentSourcePage__Clipboard_;
		case SCREENSHOT:
			return Messages.AttachmentSourcePage__Screenshot_;
		case WORKSPACE:
			IResource[] resources = getResources(treeViewer.getSelection());
			if (resources.length > 0 && resources[0].getRawLocation() != null) {
				return resources[0].getRawLocation().toOSString();
			} else {
				return null;
			}
		case FILE:
		default:
			return getAttachmentFilePath();
		}
	}

	/*
	 * Based on .eclipse.compare.internal.Utilities
	 * 
	 * Convenience method: extract all accessible <code>IResources</code> from
	 * given selection. Never returns null.
	 */
	public static IResource[] getResources(ISelection selection) {
		ArrayList<IResource> tmp = new ArrayList<IResource>();
		Class<?> type = IResource.class;
		if (selection instanceof IStructuredSelection) {
			Object[] s = ((IStructuredSelection) selection).toArray();

			for (Object o : s) {
				IResource resource = null;
				if (type.isInstance(o)) {
					resource = (IResource) o;

				} else if (o instanceof ResourceMapping) {
					try {
						ResourceTraversal[] travs = ((ResourceMapping) o).getTraversals(
								ResourceMappingContext.LOCAL_CONTEXT, null);
						if (travs != null) {
							for (ResourceTraversal trav : travs) {
								IResource[] resources = trav.getResources();
								for (IResource resource2 : resources) {
									if (type.isInstance(resource2) && resource2.isAccessible()) {
										tmp.add(resource2);
									}
								}
							}
						}
					} catch (CoreException ex) {
						// TODO handle error
					}
				} else if (o instanceof IAdaptable) {
					IAdaptable a = (IAdaptable) o;
					Object adapter = a.getAdapter(IResource.class);
					if (type.isInstance(adapter)) {
						resource = (IResource) adapter;
					}
				}

				if (resource != null && resource.isAccessible()) {
					tmp.add(resource);
				}
			}
		}

		return tmp.toArray(new IResource[tmp.size()]);
	}

	private void storeClipboardContents() {
		Control c = getControl();
		if (c != null) {
			Clipboard clipboard = new Clipboard(c.getDisplay());
			Object o = clipboard.getContents(TextTransfer.getInstance());
			clipboard.dispose();
			if (o instanceof String) {
				clipboardContents = ((String) o).trim();
			}
		}
	}

	public String getClipboardContents() {
		return clipboardContents;
	}

	public void setClipboardContents(String attachContents) {
		clipboardContents = attachContents;
	}

	public void setUseClipboard(boolean b) {
		if (useClipboardButton != null) {
			useClipboardButton.setSelection(b);
		}
		initUseClipboard = b;
	}

	@Override
	protected IDialogSettings getDialogSettings() {
		TasksUiPlugin plugin = TasksUiPlugin.getDefault();
		IDialogSettings settings = plugin.getDialogSettings();
		IDialogSettings section = settings.getSection(DIALOG_SETTINGS);
		if (section == null) {
			section = settings.addNewSection(DIALOG_SETTINGS);
		}
		return section;
	}

	public AbstractTaskAttachmentSource getSource() {
		switch (getInputMethod()) {
		case CLIPBOARD:
			return new TaskAttachmentWizard.ClipboardTaskAttachmentSource();
		case WORKSPACE:
			IResource[] resources = getResources(treeViewer.getSelection());
			if (resources.length > 0) {
				return new FileTaskAttachmentSource(resources[0].getLocation().toFile());
			} else {
				return null;
			}
		default: // FILE
			return new FileTaskAttachmentSource(new File(getAttachmentFilePath()));
		}
	}

}
