// Copyright 2015 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.

#ifndef V8DebuggerAgentImpl_h
#define V8DebuggerAgentImpl_h

#include "platform/inspector_protocol/InspectorProtocol.h"
#include "platform/v8_inspector/JavaScriptCallFrame.h"
#include "platform/v8_inspector/protocol/Debugger.h"

#include <vector>

namespace v8_inspector {

struct ScriptBreakpoint;
class JavaScriptCallFrame;
class PromiseTracker;
class V8Debugger;
class V8DebuggerScript;
class V8InspectorImpl;
class V8InspectorSessionImpl;
class V8Regex;
class V8StackTraceImpl;

namespace protocol = blink::protocol;
using blink::protocol::Maybe;

class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
    PROTOCOL_DISALLOW_COPY(V8DebuggerAgentImpl);
public:
    enum SkipPauseRequest {
        RequestNoSkip,
        RequestContinue,
        RequestStepInto,
        RequestStepOut,
        RequestStepFrame
    };

    enum BreakpointSource {
        UserBreakpointSource,
        DebugCommandBreakpointSource,
        MonitorCommandBreakpointSource
    };

    V8DebuggerAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*, protocol::DictionaryValue* state);
    ~V8DebuggerAgentImpl() override;
    void restore();

    // Part of the protocol.
    void enable(ErrorString*) override;
    void disable(ErrorString*) override;
    void setBreakpointsActive(ErrorString*, bool active) override;
    void setSkipAllPauses(ErrorString*, bool skip) override;
    void setBreakpointByUrl(ErrorString*,
        int lineNumber,
        const Maybe<String16>& optionalURL,
        const Maybe<String16>& optionalURLRegex,
        const Maybe<int>& optionalColumnNumber,
        const Maybe<String16>& optionalCondition,
        String16*,
        std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) override;
    void setBreakpoint(ErrorString*,
        std::unique_ptr<protocol::Debugger::Location>,
        const Maybe<String16>& optionalCondition,
        String16*,
        std::unique_ptr<protocol::Debugger::Location>* actualLocation) override;
    void removeBreakpoint(ErrorString*, const String16& breakpointId) override;
    void continueToLocation(ErrorString*,
        std::unique_ptr<protocol::Debugger::Location>) override;
    void searchInContent(ErrorString*,
        const String16& scriptId,
        const String16& query,
        const Maybe<bool>& optionalCaseSensitive,
        const Maybe<bool>& optionalIsRegex,
        std::unique_ptr<protocol::Array<protocol::Debugger::SearchMatch>>*) override;
    void setScriptSource(ErrorString*,
        const String16& inScriptId,
        const String16& inScriptSource,
        const Maybe<bool>& dryRun,
        Maybe<protocol::Array<protocol::Debugger::CallFrame>>* optOutCallFrames,
        Maybe<bool>* optOutStackChanged,
        Maybe<protocol::Runtime::StackTrace>* optOutAsyncStackTrace,
        Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) override;
    void restartFrame(ErrorString*,
        const String16& callFrameId,
        std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames,
        Maybe<protocol::Runtime::StackTrace>* asyncStackTrace) override;
    void getScriptSource(ErrorString*, const String16& scriptId, String16* scriptSource) override;
    void pause(ErrorString*) override;
    void resume(ErrorString*) override;
    void stepOver(ErrorString*) override;
    void stepInto(ErrorString*) override;
    void stepOut(ErrorString*) override;
    void setPauseOnExceptions(ErrorString*, const String16& pauseState) override;
    void evaluateOnCallFrame(ErrorString*,
        const String16& callFrameId,
        const String16& expression,
        const Maybe<String16>& objectGroup,
        const Maybe<bool>& includeCommandLineAPI,
        const Maybe<bool>& silent,
        const Maybe<bool>& returnByValue,
        const Maybe<bool>& generatePreview,
        std::unique_ptr<protocol::Runtime::RemoteObject>* result,
        Maybe<protocol::Runtime::ExceptionDetails>*) override;
    void setVariableValue(ErrorString*,
        int scopeNumber,
        const String16& variableName,
        std::unique_ptr<protocol::Runtime::CallArgument> newValue,
        const String16& callFrame) override;
    void setAsyncCallStackDepth(ErrorString*, int depth) override;
    void setBlackboxPatterns(ErrorString*,
        std::unique_ptr<protocol::Array<String16>> patterns) override;
    void setBlackboxedRanges(ErrorString*,
        const String16& scriptId,
        std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>> positions) override;

    bool enabled();

    void setBreakpointAt(const String16& scriptId, int lineNumber, int columnNumber, BreakpointSource, const String16& condition = String16());
    void removeBreakpointAt(const String16& scriptId, int lineNumber, int columnNumber, BreakpointSource);
    void schedulePauseOnNextStatement(const String16& breakReason, std::unique_ptr<protocol::DictionaryValue> data);
    void cancelPauseOnNextStatement();
    void breakProgram(const String16& breakReason, std::unique_ptr<protocol::DictionaryValue> data);
    void breakProgramOnException(const String16& breakReason, std::unique_ptr<protocol::DictionaryValue> data);

    void reset();

    // Interface for V8InspectorImpl
    SkipPauseRequest didPause(v8::Local<v8::Context>, v8::Local<v8::Value> exception, const std::vector<String16>& hitBreakpoints, bool isPromiseRejection);
    void didContinue();
    void didParseSource(std::unique_ptr<V8DebuggerScript>, bool success);
    void willExecuteScript(int scriptId);
    void didExecuteScript();

    v8::Isolate* isolate() { return m_isolate; }

private:
    bool checkEnabled(ErrorString*);
    void enable();

    SkipPauseRequest shouldSkipExceptionPause(JavaScriptCallFrame* topCallFrame);
    SkipPauseRequest shouldSkipStepPause(JavaScriptCallFrame* topCallFrame);

    void schedulePauseOnNextStatementIfSteppingInto();

    std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>> currentCallFrames(ErrorString*);
    std::unique_ptr<protocol::Runtime::StackTrace> currentAsyncStackTrace();

    void changeJavaScriptRecursionLevel(int step);

    void setPauseOnExceptionsImpl(ErrorString*, int);

    std::unique_ptr<protocol::Debugger::Location> resolveBreakpoint(const String16& breakpointId, const String16& scriptId, const ScriptBreakpoint&, BreakpointSource);
    void removeBreakpoint(const String16& breakpointId);
    bool assertPaused(ErrorString*);
    void clearBreakDetails();

    bool isCurrentCallStackEmptyOrBlackboxed();
    bool isTopPausedCallFrameBlackboxed();
    bool isCallFrameWithUnknownScriptOrBlackboxed(JavaScriptCallFrame*);

    void internalSetAsyncCallStackDepth(int);
    void increaseCachedSkipStackGeneration();

    bool setBlackboxPattern(ErrorString*, const String16& pattern);

    using ScriptsMap = protocol::HashMap<String16, std::unique_ptr<V8DebuggerScript>>;
    using BreakpointIdToDebuggerBreakpointIdsMap = protocol::HashMap<String16, std::vector<String16>>;
    using DebugServerBreakpointToBreakpointIdAndSourceMap = protocol::HashMap<String16, std::pair<String16, BreakpointSource>>;
    using MuteBreakpoins = protocol::HashMap<String16, std::pair<String16, int>>;

    enum DebuggerStep {
        NoStep = 0,
        StepInto,
        StepOver,
        StepOut
    };

    V8InspectorImpl* m_inspector;
    V8Debugger* m_debugger;
    V8InspectorSessionImpl* m_session;
    bool m_enabled;
    protocol::DictionaryValue* m_state;
    protocol::Debugger::Frontend m_frontend;
    v8::Isolate* m_isolate;
    v8::Global<v8::Context> m_pausedContext;
    JavaScriptCallFrames m_pausedCallFrames;
    ScriptsMap m_scripts;
    BreakpointIdToDebuggerBreakpointIdsMap m_breakpointIdToDebuggerBreakpointIds;
    DebugServerBreakpointToBreakpointIdAndSourceMap m_serverBreakpoints;
    String16 m_continueToLocationBreakpointId;
    String16 m_breakReason;
    std::unique_ptr<protocol::DictionaryValue> m_breakAuxData;
    DebuggerStep m_scheduledDebuggerStep;
    bool m_skipNextDebuggerStepOut;
    bool m_javaScriptPauseScheduled;
    bool m_steppingFromFramework;
    bool m_pausingOnNativeEvent;

    int m_skippedStepFrameCount;
    int m_recursionLevelForStepOut;
    int m_recursionLevelForStepFrame;
    bool m_skipAllPauses;

    std::unique_ptr<V8Regex> m_blackboxPattern;
    protocol::HashMap<String16, std::vector<std::pair<int, int>>> m_blackboxedPositions;
};

} // namespace v8_inspector

#endif // V8DebuggerAgentImpl_h
