/**
 * 
 */
package org.objectstyle.wolips.wodclipse.action;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
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.Point;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.objectstyle.wolips.wodclipse.core.completion.WodCompletionProposal;
import org.objectstyle.wolips.wodclipse.core.completion.WodCompletionUtils;

public class ComponentLiveSearch implements ModifyListener, SelectionListener {
	private static final boolean isWindows = System.getProperty("os.name").startsWith("Windows");
	private IJavaProject _project;

	private IProgressMonitor _progressMonitor;

	private String _lastSearch;
	private Point _lastSelection;
	private String _lastValue;
	private Boolean _ignoreModify = false;

	private Object _completionLock = new Object();

	public ComponentLiveSearch(IJavaProject project, IProgressMonitor progressMonitor) {
		_project = project;
		_progressMonitor = progressMonitor;
	}

	public void attachTo(Combo combo) {
		if (combo != null) {
			combo.addModifyListener(this);
			combo.addSelectionListener(this);
		}
	}

	public void detachFrom(Combo combo) {
		if (combo != null) {
			combo.removeModifyListener(this);
			combo.removeSelectionListener(this);
		}
	}

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

	public void widgetSelected(SelectionEvent e) {
		Combo componentNameCombo = (Combo) e.getSource();
		Point selection = componentNameCombo.getSelection();
		if (_lastSelection != null && selection.x == 0) {
			selection.x = _lastSelection.y;
			synchronized (_ignoreModify) {				
				_ignoreModify = true;
				componentNameCombo.setSelection(selection);
				_ignoreModify = false;
			}
			componentNameCombo.notifyListeners(SWT.Modify, new Event());

		}
	}

	public void modifyText(ModifyEvent e) {
		if (_ignoreModify)
			return;
		final Combo componentNameCombo = (Combo) e.getSource();
		Point selection = componentNameCombo.getSelection();

		final String partialName;
		boolean _deleted = false;

		
		/* Handle deletion of type ahead selection */
		if (selection.x > 0 && _lastSelection != null 
				&& selection.x == selection.y 
				&& selection.x ==  _lastSelection.x 
				&& _lastSelection.y >= selection.x
				&& _lastSelection.y >= componentNameCombo.getText().length()) {
			_deleted = true;
			String text = componentNameCombo.getText();
			synchronized (_ignoreModify) {
				_ignoreModify = true;
				componentNameCombo.setText(text.substring(0, selection.x-1));
				selection.x = text.length()-1;
				selection.y = selection.x;
				componentNameCombo.setSelection(selection);
				_ignoreModify = false;
			}
		}
		
		if (isWindows 
				&& selection.x == 0
				&& selection.x != selection.y
				&& selection.y == componentNameCombo.getText().length())
			return;
			
		if (selection != null && selection.x != selection.y) {
			partialName = componentNameCombo.getText().substring(0, selection.x);
		} else {
			partialName = componentNameCombo.getText();
			_lastSelection = selection;
		}
		
		if (_lastValue != null && !_deleted) {
			_deleted = partialName.length() < _lastValue.length() && selection.x != 0 ;
		}
		
		
		_lastValue = partialName;

		if (_lastSearch == null || !_lastSearch.toLowerCase().equals(partialName.toLowerCase())) {
			
			final boolean wasDeleted = _deleted;
			_lastSearch = partialName;
			_progressMonitor.setCanceled(true);

			WorkspaceJob job = new WorkspaceJob("Searching for components ...") {
				public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
					synchronized (_completionLock) {
						_progressMonitor.setCanceled(false);
						Comparator<WodCompletionProposal> nameOrder = new Comparator<WodCompletionProposal>() {
							public int compare(WodCompletionProposal p1, WodCompletionProposal p2) {
								return p1.getDisplayString().compareTo(p2.getDisplayString());
							}
						};

						try {
							final Set<WodCompletionProposal> proposals = new TreeSet<WodCompletionProposal>(nameOrder);
							WodCompletionUtils.fillInElementTypeCompletionProposals(_project, partialName, 0, partialName.length(), proposals, false, _progressMonitor);
							if (!_progressMonitor.isCanceled()) {
								Display.getDefault().asyncExec(new Runnable() {
									public void run() {
										if (!componentNameCombo.isDisposed()) {
											componentNameCombo.remove(0, componentNameCombo.getItemCount() - 1);
											boolean exactMatch = false;
											for (WodCompletionProposal elementName : proposals) {		
												String displayString = elementName.getDisplayString();
												if (displayString != null && displayString.equals(_lastSearch)) {
													exactMatch = true;
												}
												componentNameCombo.add(displayString);
											}
											
											try {
												Method setListVisible = componentNameCombo.getClass().getDeclaredMethod("setListVisible", boolean.class);
												setListVisible.setAccessible(true);
												if (!isWindows) {
													setListVisible.invoke(componentNameCombo, false);
												}
												Point _selection = componentNameCombo.getSelection();
												if (!(componentNameCombo.getItemCount() <= 1)) {
													String text = componentNameCombo.getItem(0);
													_selection.y = text.length();
													synchronized (_ignoreModify) {
														String partialText = componentNameCombo.getText();
														if (!text.toLowerCase().startsWith(partialText.toLowerCase())) {
															return;
														}
														_ignoreModify = true;
														setListVisible.invoke(componentNameCombo, true);
														componentNameCombo.setText(text);
														componentNameCombo.setSelection(_selection);
														_ignoreModify = false;
													}

												} else {
													if (componentNameCombo.getItemCount() == 1) {
														setListVisible.invoke(componentNameCombo, false);
														String text = componentNameCombo.getItem(0);
														if (_selection.x == _selection.y && !wasDeleted) {
															_selection.y = text.length();
															synchronized (_ignoreModify) {
																_ignoreModify = true;
																componentNameCombo.setText(text);
																componentNameCombo.setSelection(_selection);
																_ignoreModify = false;
															}
															_lastSelection = _selection;
														}
													}
												}
											} catch (Throwable ex) {
												ex.printStackTrace();
											}
										}
									}
								});
							}
						} catch (OperationCanceledException t) {
							// ignore
						} catch (Throwable t) {
							t.printStackTrace();
						}
						return Status.OK_STATUS;
					}
				}
			};
			job.schedule();
		}
	}
}