/*******************************************************************************
 * Copyright (c) 2000, 2007 QNX Software Systems 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:
 *     QNX Software Systems - Initial API and implementation
 *     IBM Corporation
 *******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.launching;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Set;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IBinaryParser;
import org.eclipse.cdt.core.ICExtensionReference;
import org.eclipse.cdt.core.IBinaryParser.IBinaryFile;
import org.eclipse.cdt.core.IBinaryParser.IBinaryShared;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.debug.internal.ui.PixelConverter;
import org.eclipse.cdt.debug.internal.ui.dialogfields.DialogField;
import org.eclipse.cdt.debug.internal.ui.dialogfields.IDialogFieldListener;
import org.eclipse.cdt.debug.internal.ui.dialogfields.IListAdapter;
import org.eclipse.cdt.debug.internal.ui.dialogfields.LayoutUtil;
import org.eclipse.cdt.debug.internal.ui.dialogfields.ListDialogField;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.utils.ui.controls.ControlFactory;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
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.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.DirectoryDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.CheckedTreeSelectionDialog;

/**
 * The UI component to access the shared libraries search path.
 */
public class SolibSearchPathBlock extends Observable implements IMILaunchConfigurationComponent, IDialogFieldListener {

	class AddDirectoryDialog extends Dialog {

		protected Text fText;
		
		private Button fBrowseButton;

		private IPath fValue;

		/** 
		 * Constructor for AddDirectoryDialog. 
		 */
		public AddDirectoryDialog(Shell parentShell) {
			super(parentShell);
		}

		@Override
		protected Control createDialogArea(Composite parent) {
			Composite composite = (Composite)super.createDialogArea(parent);

			Composite subComp = ControlFactory.createCompositeEx(composite, 2, GridData.FILL_HORIZONTAL);
			((GridLayout)subComp.getLayout()).makeColumnsEqualWidth = false;
			GridData data = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER);
			data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);
			subComp.setLayoutData(data);
			subComp.setFont(parent.getFont());

			fText = new Text(subComp, SWT.SINGLE | SWT.BORDER);
			fText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL));
			fText.addModifyListener(new ModifyListener() {

				public void modifyText(ModifyEvent e) {
					updateOKButton();
				}
			});

			fBrowseButton = ControlFactory.createPushButton(subComp, LaunchUIMessages.getString("GDBServerDebuggerPage.7")); //$NON-NLS-1$
			data = new GridData();
			data.horizontalAlignment = GridData.FILL;
			fBrowseButton.setLayoutData(data);
			fBrowseButton.addSelectionListener(new SelectionAdapter() {

				@Override
				public void widgetSelected(SelectionEvent evt) {
					DirectoryDialog dialog = new DirectoryDialog(AddDirectoryDialog.this.getShell());
					dialog.setMessage(LaunchUIMessages.getString("SolibSearchPathBlock.5")); //$NON-NLS-1$
					String res = dialog.open();
					if (res != null) {
						fText.setText(res);
					}
				}
			});

			applyDialogFont(composite);
			return composite;
		}

		@Override
		protected void configureShell(Shell newShell) {
			super.configureShell(newShell);
			newShell.setText(LaunchUIMessages.getString("SolibSearchPathBlock.Add_Directory")); //$NON-NLS-1$
		}

		public IPath getValue() {
			return fValue;
		}

		private void setValue(String value) {
			fValue = (value != null) ? new Path(value) : null;
		}

		@Override
		protected void buttonPressed(int buttonId) {
			if (buttonId == IDialogConstants.OK_ID) {
				setValue(fText.getText());
			}
			else {
				setValue(null);
			}
			super.buttonPressed(buttonId);
		}

		protected void updateOKButton() {
			Button okButton = getButton(IDialogConstants.OK_ID);
			String text = fText.getText();
			okButton.setEnabled(isValid(text));
		}

		protected boolean isValid(String text) {
			return (text.trim().length() > 0);
		}

		@Override
		protected Control createButtonBar(Composite parent) {
			Control control = super.createButtonBar(parent);
			updateOKButton();
			return control;
		}
	}

	private Composite fControl;

	public class SolibSearchPathListDialogField extends ListDialogField {

		public SolibSearchPathListDialogField(IListAdapter adapter, String[] buttonLabels, ILabelProvider lprovider) {
			super(adapter, buttonLabels, lprovider);
		}

		/* (non-Javadoc)
		 * @see org.eclipse.cdt.debug.internal.ui.dialogfields.ListDialogField#managedButtonPressed(int)
		 */
		@Override
		protected boolean managedButtonPressed(int index) {
			boolean result = super.managedButtonPressed(index);
			if (result)
				buttonPressed(index);
			return result;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.cdt.debug.mi.internal.ui.dialogfields.ListDialogField#getManagedButtonState(org.eclipse.jface.viewers.ISelection, int)
		 */
		@Override
		protected boolean getManagedButtonState(ISelection sel, int index) {
			if (index > 3)
				return getButtonState(sel, index);
			return super.getManagedButtonState(sel, index);
		}
	}

	private static String[] fgStaticButtonLabels = new String[] {
			LaunchUIMessages.getString("SolibSearchPathBlock.0"), //$NON-NLS-1$
			LaunchUIMessages.getString("SolibSearchPathBlock.1"), //$NON-NLS-1$
			LaunchUIMessages.getString("SolibSearchPathBlock.2"), //$NON-NLS-1$
			LaunchUIMessages.getString("SolibSearchPathBlock.3"), //$NON-NLS-1$
			LaunchUIMessages.getString("SolibSearchPathBlock.6"), //$NON-NLS-1$
			null, // separator
		};

	private IProject fProject;

	private Shell fShell;

	private SolibSearchPathListDialogField fDirList;

	private IListAdapter fCustomListAdapter;

	private File[] fAutoSolibs = new File[0];
	
	public SolibSearchPathBlock() {
		this(new String[0], null);
	}

	public SolibSearchPathBlock(String[] customButtonLabels, IListAdapter customListAdapter) {
		super();
		fCustomListAdapter = customListAdapter;
		int length = fgStaticButtonLabels.length;
		if (customButtonLabels.length > 0)
			length += customButtonLabels.length;
		String[] buttonLabels = new String[length];
		System.arraycopy(fgStaticButtonLabels, 0, buttonLabels, 0, fgStaticButtonLabels.length);
		if (length > fgStaticButtonLabels.length) {
			for (int i = fgStaticButtonLabels.length; i < length; ++i)
				buttonLabels[i] = customButtonLabels[i - fgStaticButtonLabels.length];
		}
		IListAdapter listAdapter = new IListAdapter() {
			public void customButtonPressed(DialogField field, int index) {
				buttonPressed(index);
			}
			public void selectionChanged(DialogField field) {
			}
		};
		ILabelProvider lp = new LabelProvider() {
			@Override
			public String getText(Object element) {
				if (element instanceof IPath)
					return ((IPath)element).toOSString();
				return super.getText(element);
			}
		};
		fDirList = new SolibSearchPathListDialogField(listAdapter, buttonLabels, lp);
		fDirList.setLabelText(LaunchUIMessages.getString("SolibSearchPathBlock.4")); //$NON-NLS-1$
		fDirList.setUpButtonIndex(1);
		fDirList.setDownButtonIndex(2);
		fDirList.setRemoveButtonIndex(3);
		
		fDirList.setDialogFieldListener(this);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.mi.internal.ui.IMILaunchConfigurationComponent#createControl(org.eclipse.swt.widgets.Composite)
	 */
	public void createControl(Composite parent) {
		fShell = parent.getShell();
		Composite comp = ControlFactory.createCompositeEx(parent, 2, GridData.FILL_BOTH);
		((GridLayout)comp.getLayout()).makeColumnsEqualWidth = false;
		((GridLayout)comp.getLayout()).marginHeight = 0;
		((GridLayout)comp.getLayout()).marginWidth = 0;
		comp.setFont(parent.getFont());
		PixelConverter converter = new PixelConverter(comp);
		fDirList.doFillIntoGrid(comp, 3);
		LayoutUtil.setHorizontalSpan(fDirList.getLabelControl(null), 2);
		LayoutUtil.setWidthHint(fDirList.getLabelControl(null), converter.convertWidthInCharsToPixels(30));
		LayoutUtil.setHorizontalGrabbing(fDirList.getListControl(null));
		fControl = comp;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.mi.internal.ui.IMILaunchConfigurationComponent#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
	 */
	public void initializeFrom(ILaunchConfiguration configuration) {
		IProject project = null;
		try {
			String projectName = configuration.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String)null);
			if (projectName != null) {
				projectName = projectName.trim();
				if (projectName.length() > 0) {
					project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
				}
			}
		}
		catch(CoreException e) {
		}
		setProject(project);

		if (fDirList != null) {
			try {
				@SuppressWarnings("unchecked")
				List<String> values = configuration.getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_SOLIB_PATH, 
						                                 Collections.EMPTY_LIST);
				ArrayList<Path> paths = new ArrayList<Path>(values.size());
				Iterator<String> it = values.iterator();
				while(it.hasNext()) {
					paths.add(new Path(it.next()));
				}
				fDirList.addElements(paths);
			}
			catch(CoreException e) {
			}
		}

		try {
			fAutoSolibs = getAutoSolibs(configuration);
		}
		catch(CoreException e) {
		}
	}

	public static File[] getAutoSolibs(ILaunchConfiguration configuration) throws CoreException {
		@SuppressWarnings("unchecked")
		List<String> autoSolibs = configuration.getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_AUTO_SOLIB_LIST, Collections.EMPTY_LIST );

		List<File> list = new ArrayList<File>(autoSolibs.size());
		Iterator<String> it = autoSolibs.iterator();
		while(it.hasNext()) {
			list.add(new File(it.next()));
		}
		return list.toArray(new File[list.size()]);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.mi.internal.ui.IMILaunchConfigurationComponent#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
	 */
	public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
		configuration.setAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_SOLIB_PATH, Collections.EMPTY_LIST);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.mi.internal.ui.IMILaunchConfigurationComponent#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
	 */
	public void performApply(ILaunchConfigurationWorkingCopy configuration) {
		if (fDirList != null) {
			
			@SuppressWarnings("unchecked")
			List<IPath> elements = fDirList.getElements();
			
			ArrayList<String> values = new ArrayList<String>(elements.size());
			Iterator<IPath> it = elements.iterator();
			while(it.hasNext()) {
				values.add((it.next()).toOSString());
			}
			configuration.setAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_SOLIB_PATH, values);
		}
		ArrayList<String> autoLibs = new ArrayList<String>(fAutoSolibs.length);
		for (int i = 0; i < fAutoSolibs.length; ++i)
			autoLibs.add(fAutoSolibs[i].getPath());
		configuration.setAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_AUTO_SOLIB_LIST, autoLibs); 
	}

	protected void buttonPressed(int index) {
		boolean changed = false; 
		if (index == 0) { // Add button
			changed = addDirectory();
		}
		else if (index == 4) { //Select from list
			changed = selectFromList();
		}
		else if (index >= fgStaticButtonLabels.length && fCustomListAdapter != null) {
			fCustomListAdapter.customButtonPressed(fDirList, index);
			changed = true;
		}
		if (changed) {
			setChanged();
			notifyObservers();
		}
	}

	protected boolean getButtonState(ISelection sel, int index) {
		if (index == 4) { // select from list
			return (!sel.isEmpty());
		}
		return true;
	}

	protected Shell getShell() {
		return fShell;
	}

	private boolean addDirectory() {
		boolean changed = false;
		AddDirectoryDialog dialog = new AddDirectoryDialog(getShell());
		dialog.open();
		IPath result = dialog.getValue();
		if (result != null && !contains(result)) {
			fDirList.addElement(result);
			changed = true; 
		}
		return changed;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.mi.internal.ui.IMILaunchConfigurationComponent#dispose()
	 */
	public void dispose() {
		deleteObservers();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.mi.internal.ui.IMILaunchConfigurationComponent#getControl()
	 */
	public Control getControl() {
		return fControl;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.mi.internal.ui.IMILaunchConfigurationComponent#isValid(org.eclipse.debug.core.ILaunchConfiguration)
	 */
	public boolean isValid(ILaunchConfiguration launchConfig) {
		// TODO Auto-generated method stub
		return false;
	}

	private boolean contains(IPath path) {
		@SuppressWarnings("unchecked")
		List<IPath> list = fDirList.getElements();
		
		Iterator<IPath> it = list.iterator();
		while(it.hasNext()) {
			IPath p = it.next();
			if (p.toFile().equals(path.toFile()))
				return true;
		}
		return false;
	}

	protected IProject getProject() {
		return fProject;
	}

	private void setProject(IProject project) {
		fProject = project;
	}

	protected boolean selectFromList() {
		boolean changed = false;
		
		@SuppressWarnings("unchecked")
		List<IPath> dirList = fDirList.getSelectedElements();
		
		final HashSet<IPath> libs = new HashSet<IPath>(10);
		if (generateLibraryList(dirList.toArray(new IPath[dirList.size()]), libs)) {
			ITreeContentProvider cp = new ITreeContentProvider() {
	
				public Object[] getChildren(Object parentElement) {
					return getElements(parentElement);
				}
	
				public Object getParent(Object element) {
					if (libs.contains(element))
						return libs;
					return null;
				}
	
				public boolean hasChildren(Object element) {
					return false;
				}
	
				public Object[] getElements(Object inputElement) {
					if (inputElement instanceof Set) {
						return ((Set)inputElement).toArray();
					}
					return new Object[0];
				}
	
				public void dispose() {
				}
	
				public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
				}	
			};
	
			LabelProvider lp = new LabelProvider() {
	
				@Override
				public String getText(Object element) {
					if (element instanceof File)
						return ((File)element).getName();
					return super.getText(element);
				}
			};
			CheckedTreeSelectionDialog dialog = new CheckedTreeSelectionDialog(getShell(), lp, cp);
			dialog.setTitle(LaunchUIMessages.getString("SolibSearchPathBlock.7")); //$NON-NLS-1$
			dialog.setMessage(LaunchUIMessages.getString("SolibSearchPathBlock.8")); //$NON-NLS-1$
			dialog.setEmptyListMessage(LaunchUIMessages.getString("SolibSearchPathBlock.9")); //$NON-NLS-1$
			dialog.setSorter(new ViewerSorter());
			dialog.setInput(libs);
			dialog.setInitialElementSelections(Arrays.asList(fAutoSolibs));
			if (dialog.open() == Window.OK) {
				Object[] result = dialog.getResult();
				fAutoSolibs = (File[])Arrays.asList(result).toArray(new File[result.length]);
				changed = true;
			}
		}
		return changed;
	}

	private boolean generateLibraryList(final IPath[] paths, final Set libs) {
		boolean result = true;

		IRunnableWithProgress runnable = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {

				for (int i = 0; i < paths.length; ++i) {
					File dir = paths[i].toFile();
					if (dir.exists() && dir.isDirectory()) {
						File[] all = dir.listFiles();
						for (int j = 0; j < all.length; ++j) {
							if (monitor.isCanceled()) {
								throw new InterruptedException();
							}
							monitor.subTask(all[j].getPath());
							String libName = getSharedLibraryName(all[j]);
							if (libName != null) {
								libs.add(new File(libName));
							}
						}
					}
				}
			}
		};
        try {
        	IRunnableContext context = new ProgressMonitorDialog(getShell());
			context.run(true, true, runnable);
		}
		catch(InvocationTargetException e) {
		}
		catch(InterruptedException e) {
			result = false;
		}
		return result;
	}

	protected String getSharedLibraryName(File file) {
		if (!file.isFile())
			return null;
		IProject project = getProject();
		if (project != null) {
			IPath fullPath = new Path(file.getPath());
			try {
				ICExtensionReference[] binaryParsersExt = CCorePlugin.getDefault().getBinaryParserExtensions(project);
				for(int i = 0; i < binaryParsersExt.length; i++) {
					IBinaryParser parser = (IBinaryParser)binaryParsersExt[i].createExtension();
					try {
						IBinaryFile bin = parser.getBinary(fullPath);
						if (bin instanceof IBinaryShared) {
							String soname = ((IBinaryShared)bin).getSoName();
							return (soname.length() != 0) ? soname : file.getName();
						}
					}
					catch(IOException e) {
					}
				}
			}
			catch(CoreException e) {
			}
			return null;
		}
		// no project: for now
		IPath path = new Path(file.getPath());
		String name = path.lastSegment();
		String extension = path.getFileExtension();
		if (extension != null && (extension.compareTo("so") == 0 || extension.compareToIgnoreCase("dll") == 0)) //$NON-NLS-1$ //$NON-NLS-2$
			return name;
		return (name.indexOf(".so.") >= 0) ? name : null; //$NON-NLS-1$
	}

	protected boolean isSharedLibrary(File file) {
		if (!file.isFile())
			return false;
		IProject project = getProject();
		if (project != null) {
			IPath fullPath = new Path(file.getPath());
			try {
				ICExtensionReference[] binaryParsersExt = CCorePlugin.getDefault().getBinaryParserExtensions(project);
				for(int i = 0; i < binaryParsersExt.length; i++) {
					IBinaryParser parser = (IBinaryParser)binaryParsersExt[i].createExtension();
					try {
						IBinaryFile bin = parser.getBinary(fullPath);
						return (bin instanceof IBinaryShared);
					}
					catch(IOException e) {
					}
				}
			}
			catch(CoreException e) {
			}
			return false;
		}
		// no project: for now
		IPath path = new Path(file.getPath());
		String extension = path.getFileExtension();
		if (extension != null && (extension.compareTo("so") == 0 || extension.compareToIgnoreCase("dll") == 0)) //$NON-NLS-1$ //$NON-NLS-2$
			return true;
		String name = path.lastSegment();
		return (name.indexOf(".so.") >= 0); //$NON-NLS-1$
	}

	public void dialogFieldChanged(DialogField field) {
		setChanged();
		notifyObservers();
	}
}
