/*************************************************************************************
 * Copyright (c) 2009-2011 Red Hat, Inc. 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:
 *     JBoss by Red Hat - Initial implementation.
 ************************************************************************************/
package org.jboss.tools.common.jdt.ui.buildpath.dialog;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;
import org.jboss.tools.common.jdt.ui.JDTExtUIActivator;

public class MaterializeLibraryDialog extends TitleAreaDialog {

	private static final String TITLE = "Materialize ";

	private static final String SOURCE_PROPERTY = "SOURCE_PROPERTY";

	private static final String FILENAME_PROPERTY = "FILENAME_PROPERTY";

	private IContainer libFolder;
	private Map<IClasspathEntry, String> classpathEntryPaths;
	private Map<IPath, String> selectedClasspathEntryPaths;
	private IClasspathContainer containerToMaterialize;

	private final String libName;

	private Image jarImage;
	private Image projectImage;

	private Text libfolderText;

	private CheckboxTableViewer classpathEntriesViewer;

	private Button keepSourceBtn;

	private boolean keepSources;
	
	private IMaterializeLibraryWarningFactory warningfactory;

	private Label warningImg;

	private Label warningLabel;
	
	public MaterializeLibraryDialog(Shell shell, IProject project, IClasspathContainer containerToMaterialize, String defaultLib) {
		super(shell);
		setShellStyle(super.getShellStyle() | SWT.RESIZE | SWT.MODELESS);
		this.libName = containerToMaterialize.getDescription();
		IPath folderPath = project.getFullPath().append(defaultLib);
		libFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(folderPath);
		this.containerToMaterialize = containerToMaterialize;
		warningfactory = new MaterializeLibraryWarningFactory();
		initClasspathEntryPaths();
	}

	private void initClasspathEntryPaths() {
		IClasspathEntry[] cpEntries = containerToMaterialize.getClasspathEntries();
		classpathEntryPaths = new LinkedHashMap<IClasspathEntry, String>(cpEntries.length);
		for (IClasspathEntry entry : cpEntries) {
			if ((entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY && entry.getPath() != null)
			|| (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT)) {
				IPath sourceFilePath = entry.getPath();
				String fileName = sourceFilePath.lastSegment();
				classpathEntryPaths.put(entry, fileName);
			}
		}
	}

	@Override
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		shell.setText(TITLE + "Classpath Library");
	}

	private void initImages() {
		jarImage = JDTExtUIActivator.getJarIcon();
		projectImage = JDTExtUIActivator.getProjectIcon();
	}

	@Override
	protected Control createDialogArea(Composite parent) {

		initImages();
		setTitle(TITLE + libName);

		Composite area = (Composite) super.createDialogArea(parent);

		Composite container = new Composite(area, SWT.NONE);
		container.setEnabled(true);

		GridLayout layout = new GridLayout(3, false);
		layout.marginLeft = 12;
		container.setLayout(layout);
		container.setLayoutData(new GridData(GridData.FILL_BOTH));
		
		String message = NLS.bind("Copy selected jars from {0} to the destination folder.",libName);
		String warning = warningfactory.getWarning(containerToMaterialize);
		if (warning != null) {
			displayWarning(container, warning);
		}
		setMessage(message);

		Label libFolderLabel = new Label(container, SWT.NONE);
		libFolderLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false,
				false, 1, 1));
		libFolderLabel.setText("Destination folder");

		libfolderText = new Text(container, SWT.BORDER);
		libfolderText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
				false, 1, 1));
		libfolderText.setEditable(true);
		libfolderText.setText(libFolder.getFullPath().toPortableString());
		libfolderText.addModifyListener(new ModifyListener() {
			@Override
			public void modifyText(ModifyEvent event) {
				validate();
			}
		});

		addSelectFolderButton(container);
		displayClasspathEntriesTable(container);

		return area;
	}


	private void displayWarning(Composite container, String warning) {
		Composite composite = new Composite(container, SWT.NONE);
		GridDataFactory.fillDefaults().align(SWT.LEFT,SWT.CENTER).span(3,1).applyTo(composite);
		GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite);
		warningImg = new Label(composite,  SWT.CENTER);
		warningImg.setImage(JFaceResources.getImage(DLG_IMG_MESSAGE_WARNING));
		GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).applyTo(warningImg);
		warningLabel = new Label(composite, SWT.NONE);
		warningLabel.setText(warning);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).applyTo(warningLabel);
	}



	private void addSelectFolderButton(Composite container) {

		Button button = new Button(container, SWT.NONE);
		button.setLayoutData(new GridData(SWT.FILL, SWT.UP, false, false, 1, 1));
		button.setText("Select Folder");
		button.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				ContainerSelectionDialog dialog = new ContainerSelectionDialog(
						getShell(), 
						getLibFolderFromText(libfolderText.getText()), 
						true, 
						"Select Destination Folder");
				dialog.setTitle("Container Selection");
				dialog.open();
				Object[] result = dialog.getResult();
				if (result != null && result[0] instanceof IPath) {
					libfolderText.setText(((IPath) result[0]).toPortableString());
				}
			}

			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});
	}

	@Override
	public boolean close() {
		if (jarImage != null) {
			jarImage.dispose();
		}
		if (projectImage != null) {
			projectImage.dispose();
		}
		return super.close();
	}

	private void displayClasspathEntriesTable(Composite container) {
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 4);
		gd.heightHint = 500;
		gd.widthHint = 550;

		classpathEntriesViewer = CheckboxTableViewer.newCheckList(container,
				SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION );
		Table table = classpathEntriesViewer.getTable();
		table.setFocus();
		table.setLayoutData(gd);
		table.setLinesVisible(true);
		table.setHeaderVisible(true);

		TableColumn emptyColumn = new TableColumn(table, SWT.NONE);
		emptyColumn.setWidth(20);

		TableViewerColumn sourceColumn = new TableViewerColumn(classpathEntriesViewer, SWT.NONE);
		sourceColumn.getColumn().setText("Source ");
		sourceColumn.getColumn().setWidth(299);

		TableViewerColumn destinationColumn = new TableViewerColumn(classpathEntriesViewer, SWT.NONE);
		destinationColumn.getColumn().setText("Copy as...");
		destinationColumn.getColumn().setWidth(248);

		classpathEntriesViewer.setContentProvider(ArrayContentProvider.getInstance());
		classpathEntriesViewer.setLabelProvider(new ClasspathEntryLabelProvider());
		classpathEntriesViewer.addCheckStateListener(new ICheckStateListener() {
			public void checkStateChanged(CheckStateChangedEvent event) {
				refresh();
			}
		});
		classpathEntriesViewer.setInput(classpathEntryPaths.entrySet());
		classpathEntriesViewer.setAllChecked(true);

		addSelectionButton(container, "Select All", true);
		addSelectionButton(container, "Deselect All", false);
		addResetButton(container, "Reset");

		keepSourceBtn = addCheckButton(container, "Keep source attachments", keepSources);
		keepSourceBtn.setToolTipText("Source attachment paths may contain absolute paths");
		addTableListeners();

	}

	private Button addCheckButton(Composite container, String label,
			boolean selected) {
		Button checkBtn = new Button(container, SWT.CHECK);
		checkBtn.setText(label);
		checkBtn.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1));
		checkBtn.setSelection(selected);
		return checkBtn;
	}

	private void addTableListeners() {
		addCellEditors();
	}

	protected void addCellEditors() {
		classpathEntriesViewer.setColumnProperties(
				new String[] { "EMPTY",	SOURCE_PROPERTY, FILENAME_PROPERTY });

		TextCellEditor ce = new TextCellEditor(	classpathEntriesViewer.getTable());
		CellEditor[] editors = new CellEditor[] { null, ce, ce };
		classpathEntriesViewer.setCellEditors(editors);
		classpathEntriesViewer.setCellModifier(new FileNameCellModifier());
	}

	public Map<IPath, String> getSelectedClasspathEntryPaths() {
		return selectedClasspathEntryPaths;
	}

	public IContainer getLibFolder() {
		return libFolder;
	}

	public boolean isKeepSources() {
		return keepSources;
	}

	private static IContainer getLibFolderFromText(String text) {
		String portablePath = text.replaceAll("\\\\", "/");
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		IPath path = new Path(portablePath);
		if (path.segmentCount() == 1) {
			return workspaceRoot.getProject(path.segment(0));
		} if (path.segmentCount() > 1) {
			return ResourcesPlugin.getWorkspace().getRoot().getFolder(path);
		}
		return null;
	}

	@Override
	protected void okPressed() {
		if (!validate()) {
			return;
		}

		String dialogWarning = warningfactory.getDialogWarning(containerToMaterialize); 
		if (dialogWarning != null) {
			String[] buttonLabels = new String[] { IDialogConstants.OK_LABEL,
                    IDialogConstants.CANCEL_LABEL };
			
			MessageDialog dialog = new MessageDialog(getShell(), getShell().getText(), null, dialogWarning,
					MessageDialog.WARNING, buttonLabels, 0);
			if (dialog.open() == CANCEL) {
				return;
			}
		}
		libFolder = getLibFolderFromText(libfolderText.getText());
		keepSources = keepSourceBtn.getSelection();
		super.okPressed();
	}

	private boolean validate() {
		boolean valid = validateLibFolder() && validateEntries();
		if (valid) {
			setErrorMessage(null);
		}
		return valid;
	}

	private boolean validateLibFolder() {
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		
		IContainer folder = getLibFolderFromText(libfolderText.getText());
		if (folder == null || workspaceRoot.equals(folder)) {
			setErrorMessage("You must select a project destination to copy classpath entries");
			return false;
		}
		String ancestorPath = folder.getFullPath().segment(0);
		IResource ancestor = workspaceRoot.findMember(ancestorPath);
		if (ancestor == null || !ancestor.exists()) {
			setErrorMessage(ancestorPath + " does not exist ");
			return false;
		}
		return true;
	}

	@SuppressWarnings("unchecked")
	private boolean validateEntries() {
		Object[] selection = classpathEntriesViewer.getCheckedElements();
		selectedClasspathEntryPaths = new LinkedHashMap<IPath, String>(
				selection.length);
		for (Object o : selection) {
			Map.Entry<IClasspathEntry, String> entry = (Map.Entry<IClasspathEntry, String>) o;
			if (entry.getKey().getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
				String name = entry.getValue();
				if (!checkValidName(name)) {
					setErrorMessage(name + " is not a valid file name");
					return false;
				}
			}
			selectedClasspathEntryPaths.put(entry.getKey().getPath(),
					entry.getValue());
		}

		Set<String> duplicates = findDuplicates(selectedClasspathEntryPaths.values());
		if (!duplicates.isEmpty()) {
			setErrorMessage("Duplicate entries found : " + duplicates.toString());
			return false;
		}
		return true;
	}

	private Set<String> findDuplicates(Collection<String> values) {
		Set<String> uniqueNames = new HashSet<String>(values.size());
		Set<String> duplicateNames = new HashSet<String>();
		for (String name : values) {
			if (!uniqueNames.add(name)) {
				duplicateNames.add(name);
			}
		}
		return duplicateNames;
	}

	private class ClasspathEntryLabelProvider extends LabelProvider implements
			ITableLabelProvider {

		private static final int SOURCE_COLUMN = 1;
		private static final int FILENAME_COLUMN = 2;

		public String getColumnText(Object element, int columnIndex) {
			Map.Entry<IClasspathEntry, String> entry = (Map.Entry<IClasspathEntry, String>) element;
			StringBuilder text = new StringBuilder();
			if (entry != null) {
				if (columnIndex == SOURCE_COLUMN) {
					text.append(entry.getKey().getPath().lastSegment());
				} else if (columnIndex == FILENAME_COLUMN) {
					text.append(entry.getValue());
				}
			}

			return text.toString();
		}

		@Override
		public Image getColumnImage(Object element, int columnIndex) {
			Image img = null;
			if (columnIndex > 0) {
				Map.Entry<IClasspathEntry, String> entry = (Map.Entry<IClasspathEntry, String>) element;
				if (entry.getKey().getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
					img = jarImage;
				} else {
					img = projectImage;
				}
			}
			return img;
		}
	}

	private Button addSelectionButton(Composite container, String label,
			final boolean ischecked) {
		Button button = new Button(container, SWT.NONE);
		button.setLayoutData(new GridData(SWT.FILL, SWT.UP, false, false, 1, 1));
		button.setText(label);
		button.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				classpathEntriesViewer.setAllChecked(ischecked);
				refresh();
			}

			public void widgetDefaultSelected(SelectionEvent e) {

			}
		});

		return button;
	}

	private Button addResetButton(Composite container, String label) {
		Button button = new Button(container, SWT.NONE);
		button.setLayoutData(new GridData(SWT.FILL, SWT.UP, false, false, 1, 1));
		button.setText(label);
		button.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				initClasspathEntryPaths();
				classpathEntriesViewer.setInput(classpathEntryPaths.entrySet());
				classpathEntriesViewer.setAllChecked(true);
				refresh();
			}

			public void widgetDefaultSelected(SelectionEvent e) {

			}
		});

		return button;
	}

	protected void refresh() {
		classpathEntriesViewer.refresh();
	}

	private class FileNameCellModifier implements ICellModifier {

		public boolean canModify(Object element, String property) {
			Map.Entry<IClasspathEntry, String> entry = (Map.Entry<IClasspathEntry, String>) element;
			return (entry.getKey().getEntryKind() == IClasspathEntry.CPE_LIBRARY)
					&& (FILENAME_PROPERTY.equals(property) || SOURCE_PROPERTY
							.equals(property));
		}

		public Object getValue(Object element, String property) {
			Map.Entry<IClasspathEntry, String> entry = (Map.Entry<IClasspathEntry, String>) element;
			// if(entry.getKey().getEntryKind() == IClasspathEntry.CPE_LIBRARY)
			// {
			if (property.equals(SOURCE_PROPERTY)) {
				return entry.getKey().getPath().toOSString();
			} else if (property.equals(FILENAME_PROPERTY)) {
				return entry.getValue();
			}
			// }
			return ""; //$NON-NLS-1$
		}

		public void modify(Object element, String property, Object value) {
			if (property.equals(FILENAME_PROPERTY)) {
				TableItem item = (TableItem) element;
				Map.Entry<IClasspathEntry, String> entry = (Map.Entry<IClasspathEntry, String>) item
						.getData();
				if (entry.getKey().getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
					String name = value.toString();
					entry.setValue(name);
					validate();
					classpathEntriesViewer.refresh();
				}
			}
		}
	}

	public boolean checkValidName(String name) {
		// TODO checks for :
		// - duplicates
		// - existing CPE
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		IStatus result = workspace.validateName(name, IResource.FILE);
		if (!result.isOK()) {
			return false;
		}
		return !name.contains("\\")
				&& (name.endsWith(".jar") || name.endsWith(".zip"));
	}
}
