package ts.eclipse.ide.jsdt.internal.ui.actions;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.actions.ActionGroup;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.wst.jsdt.ui.actions.SelectionDispatchAction;

import ts.client.CommandNames;
import ts.client.refactors.ApplicableRefactorInfo;
import ts.client.refactors.RefactorActionInfo;
import ts.client.refactors.RefactorEditInfo;
import ts.eclipse.ide.core.resources.IIDETypeScriptFile;
import ts.eclipse.ide.jsdt.internal.ui.editor.TypeScriptEditor;
import ts.eclipse.ide.jsdt.internal.ui.refactoring.RefactoringMessages;
import ts.eclipse.ide.jsdt.ui.IContextMenuConstants;
import ts.eclipse.ide.jsdt.ui.actions.ITypeScriptEditorActionDefinitionIds;
import ts.eclipse.ide.jsdt.ui.actions.TypeScriptActionConstants;
import ts.eclipse.ide.ui.preferences.StatusInfo;
import ts.eclipse.ide.ui.utils.EditorUtils;
import ts.resources.ITypeScriptFile;
import ts.utils.CompletableFutureUtils;

/**
 * Action group that adds refactor actions (for example 'Rename', 'Move') to a
 * context menu and the global menu bar.
 *
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 *
 * @noextend This class is not intended to be subclassed by clients.
 */
public class RefactorActionGroup extends ActionGroup {

	/**
	 * Pop-up menu: id of the refactor sub menu (value
	 * <code>ts.eclipse.ide.jsdt.ui.refactoring.menu</code>).
	 */
	public static final String MENU_ID = "ts.eclipse.ide.jsdt.ui.refactoring.menu"; //$NON-NLS-1$

	/**
	 * Pop-up menu: id of the reorg group of the refactor sub menu (value
	 * <code>reorgGroup</code>).
	 *
	 */
	public static final String GROUP_REORG = "reorgGroup"; //$NON-NLS-1$

	private TypeScriptEditor fEditor;
	private String fGroupName = IContextMenuConstants.GROUP_REORGANIZE;

	private final List<SelectionDispatchAction> fActions = new ArrayList<SelectionDispatchAction>();

	private static class NoActionAvailable extends Action {
		public NoActionAvailable() {
			setEnabled(true);
			setText(RefactoringMessages.RefactorActionGroup_no_refactoring_available);
		}
	}

	private Action fNoActionAvailable = new NoActionAvailable();

	private final ISelectionProvider fSelectionProvider;

	private RenameAction fRenameAction;

	private IEditorSite fSite;

	private CompletableFuture<List<ApplicableRefactorInfo>> refactorInfosPromise;

	public RefactorActionGroup(TypeScriptEditor editor, String groupName) {
		fEditor = editor;
		fGroupName = groupName;
		fSite = editor.getEditorSite();
		fSelectionProvider = fSite.getSelectionProvider();

		ISelectionProvider provider = editor.getSelectionProvider();
		ISelection selection = provider.getSelection();

		fRenameAction = new RenameAction(editor);
		initAction(fRenameAction, selection, ITypeScriptEditorActionDefinitionIds.RENAME_ELEMENT);
		editor.setAction("RenameElement", fRenameAction); //$NON-NLS-1$

	}

	@Override
	public void fillActionBars(IActionBars actionBars) {
		super.fillActionBars(actionBars);
		actionBars.setGlobalActionHandler(TypeScriptActionConstants.RENAME, fRenameAction);
	}

	private void initAction(SelectionDispatchAction action, ISelection selection, String actionDefinitionId) {
		initUpdatingAction(action, null, null, selection, actionDefinitionId);
	}

	/**
	 * Sets actionDefinitionId, updates enablement, adds to fActions, and adds
	 * selection changed listener if provider is not <code>null</code>.
	 *
	 * @param action
	 *            the action
	 * @param provider
	 *            can be <code>null</code>
	 * @param specialProvider
	 *            a special selection provider or <code>null</code>
	 * @param selection
	 *            the selection
	 * @param actionDefinitionId
	 *            the action definition id
	 */
	private void initUpdatingAction(SelectionDispatchAction action, ISelectionProvider provider,
			ISelectionProvider specialProvider, ISelection selection, String actionDefinitionId) {
		action.setActionDefinitionId(actionDefinitionId);
		action.update(selection);
		if (provider != null)
			provider.addSelectionChangedListener(action);
		if (specialProvider != null)
			action.setSpecialSelectionProvider(specialProvider);
		fActions.add(action);
	}

	@Override
	public void fillContextMenu(IMenuManager menu) {
		super.fillContextMenu(menu);
		addRefactorSubmenu(menu);
	}

	private void addRefactorSubmenu(IMenuManager menu) {
		MenuManager refactorSubmenu = new MenuManager(RefactoringMessages.RefactorMenu_label, MENU_ID);
		// refactorSubmenu.setActionDefinitionId(QUICK_MENU_ID);
		if (fEditor != null) {
			// final ITypeRoot element= getEditorInput();
			// if (element != null && ActionUtil.isOnBuildPath(element)) {
			refactorSubmenu.addMenuListener(new IMenuListener() {
				@Override
				public void menuAboutToShow(IMenuManager manager) {
					refactorMenuShown(manager);
				}
			});
			refactorSubmenu.add(fNoActionAvailable);
			menu.appendToGroup(fGroupName, refactorSubmenu);
			// }
		} else {
			ISelection selection = fSelectionProvider.getSelection();
			for (Iterator<SelectionDispatchAction> iter = fActions.iterator(); iter.hasNext();) {
				iter.next().update(selection);
			}
			if (fillRefactorMenu(refactorSubmenu) > 0)
				menu.appendToGroup(fGroupName, refactorSubmenu);
		}
	}

	private int fillRefactorMenu(final IMenuManager refactorSubmenu) {
		int added = 0;
		refactorSubmenu.add(new Separator(GROUP_REORG));
		added += addAction(refactorSubmenu, fRenameAction);

		ITextSelection textSelection = (ITextSelection) fEditor.getSelectionProvider().getSelection();
		try {
			ITypeScriptFile tsFile = fEditor.getTypeScriptFile();
			if (tsFile.getProject().canSupport(CommandNames.GetApplicableRefactors)) {
				// TypeScript >= 2.4.1 : support for applicable refactors

				final Menu menu = ((MenuManager) refactorSubmenu).getMenu();
				
				int startPosition = textSelection.getOffset();
				Integer endPosition = textSelection.getOffset() + textSelection.getLength();
				CompletableFutureUtils.cancel(refactorInfosPromise);
				refactorInfosPromise = tsFile.getApplicableRefactors(startPosition, endPosition);
				refactorInfosPromise.whenComplete(new BiConsumer<List<ApplicableRefactorInfo>, Throwable>() {

					@Override
					public void accept(List<ApplicableRefactorInfo> infos, Throwable u) {						
						UIJob job = new UIJob(menu.getDisplay(), "refactoring menu job") {
							@Override
							public IStatus runInUIThread(IProgressMonitor monitor) {
								// if (u != null) {
								// // log?
								// item.setText(u.getMessage());
								// } else {
								// for (CodeLens lens : t) {
								// if (lens != null) {
								// final MenuItem item = new MenuItem(menu, SWT.NONE, index);
								// item.setText(lens.getCommand().getTitle());
								// item.setEnabled(false);
								// }
								// }
								// }
								if (infos != null) {
									for (ApplicableRefactorInfo info : infos) {
										if (info.isInlineable()) {
											for (RefactorActionInfo action : info.getActions()) {
												addMenu(info.getDescription(), info.getName(), action.getName(),
														startPosition, endPosition, tsFile, menu);
											}
										} else {
											// TODO: support no inlineable

										}
									}
								}
								return Status.OK_STATUS;
							}

							private void addMenu(String title, String refactor, String action, int startPosition,
									Integer endPosition, ITypeScriptFile tsFile, Menu menu) {
								final MenuItem item = new MenuItem(menu, SWT.NONE);
								item.setText(title);
								item.setEnabled(true);

								item.addSelectionListener(new SelectionListener() {

									@Override
									public void widgetSelected(SelectionEvent e) {
										try {
											RefactorEditInfo info = tsFile
													.getEditsForRefactor(startPosition, endPosition, refactor, action)
													.get(5000, TimeUnit.MILLISECONDS);
											EditorUtils.applyEdit(info.getEdits(),
													((IIDETypeScriptFile) tsFile).getDocument(), tsFile.getName());
										} catch (Exception ex) {
											ErrorDialog.openError(menu.getShell(), "Refactoring Error",
													"Error while applying refactoring",
													new StatusInfo(IStatus.ERROR, ex.getMessage()));
										}
									}

									@Override
									public void widgetDefaultSelected(SelectionEvent e) {

									}
								});
							}
						};
						job.schedule();
					}
				});

			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return added;
	}

	private int addAction(IMenuManager menu, IAction action) {
		if (action != null && action.isEnabled()) {
			menu.add(action);
			return 1;
		}
		return 0;
	}

	private void refactorMenuShown(IMenuManager refactorSubmenu) {
		// we know that we have an MenuManager since we created it in
		// addRefactorSubmenu.
		Menu menu = ((MenuManager) refactorSubmenu).getMenu();
		menu.addMenuListener(new MenuAdapter() {
			@Override
			public void menuHidden(MenuEvent e) {
				refactorMenuHidden();
			}
		});
		ITextSelection textSelection = (ITextSelection) fEditor.getSelectionProvider().getSelection();
		// JavaTextSelection javaSelection= new
		// JavaTextSelection(getEditorInput(), getDocument(),
		// textSelection.getOffset(), textSelection.getLength());

		for (Iterator<SelectionDispatchAction> iter = fActions.iterator(); iter.hasNext();) {
			SelectionDispatchAction action = iter.next();
			action.update(textSelection);
		}
		refactorSubmenu.removeAll();
		if (fillRefactorMenu(refactorSubmenu) == 0)
			refactorSubmenu.add(fNoActionAvailable);
	}

	private void refactorMenuHidden() {
		ITextSelection textSelection = (ITextSelection) fEditor.getSelectionProvider().getSelection();
		for (Iterator<SelectionDispatchAction> iter = fActions.iterator(); iter.hasNext();) {
			SelectionDispatchAction action = iter.next();
			action.update(textSelection);
		}
	}

	@Override
	public void dispose() {
		super.dispose();
		disposeAction(fRenameAction, fSelectionProvider);
	}

	private void disposeAction(ISelectionChangedListener action, ISelectionProvider provider) {
		if (action != null)
			provider.removeSelectionChangedListener(action);
	}

}