// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.debug.ui.actions;

import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionDelegate2;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;

/**
 * A base class for actions. User implementation is responsible
 * for getting elements from context (i.e. selection) and for performing the action itself.
 * This class implements all methods of {@link IObjectActionDelegate} and {@link IActionDelegate2}.
 * @param <S> user type for data that is read from selection
 */
public abstract class SelectionBasedAction<S> implements IObjectActionDelegate, IActionDelegate2 {

  /**
   * Base class for actions that are enabled only for a single element in selection.
   * User implements {@link #castElement(Object)} method that map raw selection object
   * to a custom type T. Action optionally may allow selections with more than 1 object,
   * but it is required that only a single object is filtered out.
   */
  public static abstract class Single<T> extends SelectionBasedAction<T> {
    private final boolean allowMutipleSelection;

    /**
     * @param allowMutipleSelection if false requires that selection contains exactly 1 element;
     *     if true only requires that selection contains exactly 1 element that gets cast
     *     by {@link #castElement(Object)} method
     */
    protected Single(boolean allowMutipleSelection) {
      this.allowMutipleSelection = allowMutipleSelection;
    }

    @Override
    protected T readSelection(IStructuredSelection selection) {
      if (!allowMutipleSelection && selection.size() != 1) {
        return null;
      }
      T result = null;
      for (Iterator<?> it = selection.iterator(); it.hasNext(); ) {
        Object element = it.next();
        T userElement = castElement(element);
        if (userElement == null) {
          continue;
        }
        if (result != null) {
          return null;
        }
        result = userElement;
      }
      return result;
    }

    /**
     * User-provided method that casts raw selection element to user type T. Method
     * may return null which means that this raw object should be ignored.
     */
    protected abstract T castElement(Object element);
  }

  /**
   * Base class for actions that works with any number of elements in selection.
   */
  static abstract class Multiple<T> extends SelectionBasedAction<List<? extends T>> {
  }

  private ActionRunnable currentRunnable = null;
  private Shell currentShell = null;
  private String originalActionText = null;
  private IWorkbenchPart currentTargetPart = null;
  private IAction action;

  protected abstract S readSelection(IStructuredSelection selection);

  public void setActivePart(IAction action, IWorkbenchPart targetPart) {
    currentShell = targetPart.getSite().getShell();
    currentTargetPart = targetPart;
  }

  public void run(IAction action) {
    assert action == getAction();
    if (currentRunnable == null) {
      return;
    }
    currentRunnable.run(currentShell, currentTargetPart);
    currentRunnable = null;
  }

  public void selectionChanged(IAction actionParam, ISelection selection) {
    currentRunnable = createRunnableFromRawSelection(selection);
    this.action.setEnabled(currentRunnable != null);
    if (currentRunnable == null) {
      restoreActionText();
    } else {
      currentRunnable.adjustAction();
    }
  }

  private ActionRunnable createRunnableFromRawSelection(ISelection selection) {
    if (selection instanceof IStructuredSelection == false) {
      return null;
    }
    IStructuredSelection structured = (IStructuredSelection) selection;
    final S selectedElements = readSelection(structured);
    return createRunnable(selectedElements);
  }

  protected abstract ActionRunnable createRunnable(S selectedElements);

  protected interface ActionRunnable {
    void adjustAction();
    void run(Shell shell, IWorkbenchPart workbenchPart);
  }

  protected void modifyActionText(String newText) {
    if (originalActionText == null) {
      originalActionText = action.getText();
    }
    action.setText(newText);
  }
  protected void restoreActionText() {
    if (originalActionText != null) {
      action.setText(originalActionText);
      originalActionText = null;
    }
  }
  protected IAction getAction() {
    return action;
  }

  public void dispose() {
    currentRunnable = null;
  }

  public void init(IAction action) {
    this.action = action;
  }

  public void runWithEvent(IAction action, Event event) {
    run(action);
  }
}
