/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.xdebugger;

import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.xdebugger.breakpoints.XBreakpointHandler;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.frame.XSuspendContext;
import com.intellij.xdebugger.frame.XValueMarkerProvider;
import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
import com.intellij.xdebugger.ui.XDebugTabLayouter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.Promises;

import javax.swing.event.HyperlinkListener;

/**
 * Extends this class to provide debugging capabilities for custom language/framework.
 *
 * In order to start debugger by 'Debug' action for a specific run configuration implement {@link com.intellij.execution.runners.ProgramRunner}
 * and call {@link XDebuggerManager#startSession} from {@link com.intellij.execution.runners.ProgramRunner#execute} method
 *
 * Otherwise use method {@link XDebuggerManager#startSessionAndShowTab} to start new debugging session
 *
 * @author nik
 */
public abstract class XDebugProcess {
  private final XDebugSession mySession;
  private ProcessHandler myProcessHandler;

  /**
   * @param session pass <code>session</code> parameter of {@link XDebugProcessStarter#start} method to this constructor
   */
  protected XDebugProcess(@NotNull XDebugSession session) {
    mySession = session;
  }

  public final XDebugSession getSession() {
    return mySession;
  }

  /**
   * @return breakpoint handlers which will be used to set/clear breakpoints in the underlying debugging process
   */
  @NotNull
  public XBreakpointHandler<?>[] getBreakpointHandlers() {
    return XBreakpointHandler.EMPTY_ARRAY;
  }

  /**
   * @return editor provider which will be used to produce editors for "Evaluate" and "Set Value" actions
   */
  @NotNull
  public abstract XDebuggerEditorsProvider getEditorsProvider();

  /**
   * Called when {@link XDebugSession} is initialized and breakpoints are registered in
   * {@link XBreakpointHandler}
   */
  public void sessionInitialized() {
  }

  /**
   * Interrupt debugging process and call {@link XDebugSession#positionReached}
   * when next line in current method/function is reached.
   * Do not call this method directly. Use {@link XDebugSession#pause()} instead
   */
  public void startPausing() {
  }

  /**
   * @deprecated Use {@link #startStepOver(XSuspendContext)} instead
   */
  @Deprecated
  public void startStepOver() {
    throw new AbstractMethodError();
  }

  /**
   * Resume execution and call {@link XDebugSession#positionReached}
   * when next line in current method/function is reached.
   * Do not call this method directly. Use {@link XDebugSession#stepOver} instead
   */
  public void startStepOver(@Nullable XSuspendContext context) {
    //noinspection deprecation
    startStepOver();
  }

  /**
   * @deprecated Use {@link #startForceStepInto(XSuspendContext)} instead
   */
  @Deprecated
  public void startForceStepInto(){
    //noinspection deprecation
    startStepInto();
  }

  /**
   * Steps into suppressed call
   * <p>
   * Resume execution and call {@link XDebugSession#positionReached}
   * when next line is reached.
   * Do not call this method directly. Use {@link XDebugSession#forceStepInto} instead
   */
  public void startForceStepInto(@Nullable XSuspendContext context) {
    startStepInto(context);
  }

  /**
   * @deprecated Use {@link #startStepInto(XSuspendContext)} instead
   */
  @Deprecated
  public void startStepInto() {
    throw new AbstractMethodError();
  }

  /**
   * Resume execution and call {@link XDebugSession#positionReached}
   * when next line is reached.
   * Do not call this method directly. Use {@link XDebugSession#stepInto} instead
   */
  public void startStepInto(@Nullable XSuspendContext context) {
    //noinspection deprecation
    startStepInto();
  }

  /**
   * @deprecated Use {@link #startStepOut(XSuspendContext)} instead
   */
  @Deprecated
  public void startStepOut() {
    throw new AbstractMethodError();
  }

  /**
   * Resume execution and call {@link XDebugSession#positionReached}
   * after returning from current method/function.
   * Do not call this method directly. Use {@link XDebugSession#stepOut} instead
   */
  public void startStepOut(@Nullable XSuspendContext context) {
    //noinspection deprecation
    startStepOut();
  }

  /**
   * Implement {@link XSmartStepIntoHandler} and return its instance from this method to enable Smart Step Into action
   * @return {@link XSmartStepIntoHandler} instance
   */
  @Nullable
  public XSmartStepIntoHandler<?> getSmartStepIntoHandler() {
    return null;
  }

  /**
   * Stop debugging and dispose resources.
   * Do not call this method directly. Use {@link XDebugSession#stop} instead
   */
  public void stop() {
    throw new AbstractMethodError();
  }

  @NotNull
  public Promise stopAsync() {
    stop();
    return Promises.resolvedPromise();
  }

  /**
   * @deprecated Use {@link #resume(XSuspendContext)} instead
   */
  @Deprecated
  public void resume() {
    throw new AbstractMethodError();
  }

  /**
   * Resume execution.
   * Do not call this method directly. Use {@link XDebugSession#resume} instead
   */
  public void resume(@Nullable XSuspendContext context) {
    //noinspection deprecation
    resume();
  }

  /**
   * @deprecated Use {@link #runToPosition(XSourcePosition, XSuspendContext)} instead
   */
  @Deprecated
  public void runToPosition(@NotNull XSourcePosition position) {
    throw new AbstractMethodError();
  }

  /**
   * Resume execution and call {@link XDebugSession#positionReached(XSuspendContext)}
   * when <code>position</code> is reached.
   * Do not call this method directly. Use {@link XDebugSession#runToPosition} instead
   *
   * @param position position in source code
   */
  public void runToPosition(@NotNull XSourcePosition position, @Nullable XSuspendContext context) {
    //noinspection deprecation
    runToPosition(position);
  }

  /**
   * Check is it is possible to perform commands such as resume, step etc. And notify user if necessary
   * @return {@code true} if process can actually perform user requests at this moment
   */
  public boolean checkCanPerformCommands() {
    return true;
  }

  /**
   * Check is it is possible to init breakpoints. Otherwise you should call {@link XDebugSession#initBreakpoints()} at the appropriate time
   */
  public boolean checkCanInitBreakpoints() {
    return true;
  }

  @Nullable
  protected ProcessHandler doGetProcessHandler() {
    return null;
  }

  @NotNull
  public final ProcessHandler getProcessHandler() {
    if (myProcessHandler == null) {
      myProcessHandler = doGetProcessHandler();
      if (myProcessHandler == null) {
        myProcessHandler = new DefaultDebugProcessHandler();
      }
    }
    return myProcessHandler;
  }

  @NotNull
  public ExecutionConsole createConsole() {
    return TextConsoleBuilderFactory.getInstance().createBuilder(getSession().getProject()).getConsole();
  }

  /**
   * Override this method to enable 'Mark Object' action
   * @return new instance of {@link XValueMarkerProvider}'s implementation or {@code null} if 'Mark Object' feature isn't supported
   */
  @Nullable
  public XValueMarkerProvider<?,?> createValueMarkerProvider() {
    return null;
  }

  /**
   * Override this method to provide additional actions in 'Debug' tool window
   */
  public void registerAdditionalActions(@NotNull DefaultActionGroup leftToolbar, @NotNull DefaultActionGroup topToolbar, @NotNull DefaultActionGroup settings) {
  }

  /**
   * @return message to show in Variables View when debugger isn't paused
   */
  public String getCurrentStateMessage() {
    return mySession.isStopped() ? XDebuggerBundle.message("debugger.state.message.disconnected") : XDebuggerBundle.message("debugger.state.message.connected");
  }

  @Nullable
  public HyperlinkListener getCurrentStateHyperlinkListener() {
    return null;
  }

  /**
   * Override this method to customize content of tab in 'Debug' tool window
   */
  @NotNull
  public XDebugTabLayouter createTabLayouter() {
    return new XDebugTabLayouter() {
    };
  }

  /**
   * Add or not SortValuesAction (alphabetically sort)
   * @todo this action should be moved to "Variables" as gear action
   */
  public boolean isValuesCustomSorted() {
    return false;
  }

  @Nullable
  public XDebuggerEvaluator getEvaluator() {
    XStackFrame frame = getSession().getCurrentStackFrame();
    return frame == null ? null : frame.getEvaluator();
  }

  /**
   * Is "isShowLibraryStackFrames" setting respected. If true, ShowLibraryFramesAction will be shown, for example.
   */
  public boolean isLibraryFrameFilterSupported() {
    return false;
  }
}
