// 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 org.chromium.debug.core.model.EvaluateContext;
import org.chromium.debug.ui.ChromiumDebugUIPlugin;
import org.chromium.debug.ui.PluginUtil;
import org.chromium.debug.ui.editors.JavascriptUtil;
import org.chromium.sdk.JsEvaluateContext;
import org.chromium.sdk.JsVariable;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.model.IExpression;
import org.eclipse.debug.ui.DebugPopup;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.InspectPopupDialog;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorActionDelegate;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.texteditor.ITextEditor;

/**
 * Action for inspecting a JavaScript snippet.
 */
public class JsInspectSnippetAction implements IEditorActionDelegate {

  private static final String ACTION_DEFINITION_ID = "org.chromium.debug.ui.commands.Inspect"; //$NON-NLS-1$

  private IEditorPart activeEditorPart;

  public void setActiveEditor(IAction action, IEditorPart targetEditor) {
    activeEditorPart = targetEditor;
  }

  public void selectionChanged(IAction action, ISelection selection) {
  }

  private static ISelection getTargetSelection(IWorkbenchPart targetPart) {
    if (targetPart != null) {
      ISelectionProvider provider = targetPart.getSite().getSelectionProvider();
      if (provider != null) {
        return provider.getSelection();
      }
    }
    return null;
  }

  public void run(IAction action) {
    IAdaptable context = DebugUITools.getDebugContext();
    if (context == null) { // debugger not active
      return;
    }
    EvaluateContext evaluateContext = (EvaluateContext) context.getAdapter(EvaluateContext.class);
    if (evaluateContext == null) {
      return;
    }
    IEditorPart editorPart = activeEditorPart;
    String currentSelectedText = retrieveSelection(editorPart);
    EvaluateCallbackImpl callback =
        new EvaluateCallbackImpl(evaluateContext, editorPart, currentSelectedText);
    evaluateContext.getJsEvaluateContext().evaluateAsync(currentSelectedText, null,
        callback, null);
  }

  private static String retrieveSelection(IWorkbenchPart targetPart) {
    ISelection targetSelection = getTargetSelection(targetPart);
    if (targetSelection instanceof ITextSelection) {
      ITextSelection ts = (ITextSelection) targetSelection;
      String text = ts.getText();
      if (textHasContent(text)) {
        return text;
      } else if (targetPart instanceof IEditorPart) {
        IEditorPart editor = (IEditorPart) targetPart;
        if (editor instanceof ITextEditor) {
          return extractSurroundingWord(ts, (ITextEditor) editor);
        }
      }
    }
    return null;
  }

  private static String extractSurroundingWord(ITextSelection targetSelection, ITextEditor editor) {
    return JavascriptUtil.extractSurroundingJsIdentifier(
        editor.getDocumentProvider().getDocument(editor.getEditorInput()),
        targetSelection.getOffset());
  }

  private static boolean textHasContent(String text) {
    return text != null && JavascriptUtil.ID_PATTERN.matcher(text).find();
  }

  private static class EvaluateCallbackImpl implements JsEvaluateContext.EvaluateCallback  {
    private final EvaluateContext evaluateContext;
    private final IEditorPart editorPart;
    private final String selectedText;

    EvaluateCallbackImpl(EvaluateContext evaluateContext, IEditorPart editorPart, String selectedText) {
      this.evaluateContext = evaluateContext;
      this.editorPart = editorPart;
      this.selectedText = selectedText;
    }

    public void success(JsVariable var) {
      if (ChromiumDebugUIPlugin.getDefault() == null) {
        return;
      }
      if (var != null) {
        if (ChromiumDebugUIPlugin.getDisplay().isDisposed()) {
          return;
        }
        displayResult(var, null);
      }
    }

    public void failure(String errorMessage) {
      displayResult(null, errorMessage);
    }

    private void displayResult(final JsVariable var, String errorMessage) {
      final StyledText styledText = getStyledText(editorPart);
      if (styledText == null) {
        return; // TODO(apavlov): fix this when adding inspected variables
      } else {
        final IExpression expression = new JsInspectExpression(evaluateContext, selectedText, var, errorMessage);
        ChromiumDebugUIPlugin.getDisplay().asyncExec(new Runnable() {
          public void run() {
            showPopup(styledText, expression);
          }
        });
      }
    }

    private void showPopup(StyledText textWidget, IExpression expression) {
      final ITextEditor textEditor;
      final ISelection originalSelection;

      IWorkbenchPart part = editorPart;
      if (part instanceof ITextEditor) {
        textEditor = (ITextEditor) part;
        originalSelection = getTargetSelection(editorPart);
      } else {
        textEditor = null;
        originalSelection = null;
      }
      Shell shell = editorPart.getSite().getShell();
      DebugPopup displayPopup =
          new InspectPopupDialog(shell, getPopupAnchor(textWidget), ACTION_DEFINITION_ID,
              expression) {
            @Override
            public boolean close() {
              boolean returnValue = super.close();
              if (textEditor != null && originalSelection != null) {
                textEditor.getSelectionProvider().setSelection(originalSelection);
              }
              return returnValue;
            }
          };
      displayPopup.open();
    }

    private StyledText getStyledText(IWorkbenchPart part) {
      ITextViewer viewer = (ITextViewer) part.getAdapter(ITextViewer.class);
      StyledText textWidget = null;
      if (viewer == null) {
        Control control = (Control) part.getAdapter(Control.class);
        if (control instanceof StyledText) {
          textWidget = (StyledText) control;
        }
      } else {
        textWidget = viewer.getTextWidget();
      }
      return textWidget;
    }
  }

  private static Point getPopupAnchor(StyledText textWidget) {
    if (textWidget != null) {
      Point docRange = textWidget.getSelectionRange();
      int midOffset = docRange.x + (docRange.y / 2);
      Point point = textWidget.getLocationAtOffset(midOffset);
      point = textWidget.toDisplay(point);
      point.y += getFontHeight(textWidget);
      return point;
    }
    return null;
  }

  private static int getFontHeight(StyledText textWidget) {
    return PluginUtil.getFontMetrics(textWidget, textWidget.getFont()).getHeight();
  }
}
