/*
 * Copyright 2021 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/sksl/tracing/SkSLDebugTracePriv.h"

#include "include/core/SkRefCnt.h"
#include "include/core/SkTypes.h"
#include "src/utils/SkBitSet.h"

#include <cstddef>
#include <cstdint>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#ifdef SKSL_ENABLE_TRACING

namespace SkSL {

/**
 * Plays back a SkSL debug trace, allowing its contents to be viewed like a traditional debugger.
 */
class SkSLDebugTracePlayer {
public:
    /** Resets playback to the start of the trace. Breakpoints are not cleared. */
    void reset(sk_sp<DebugTracePriv> trace);

    /** Advances the simulation to the next Line op. */
    void step();

    /**
     * Advances the simulation to the next Line op, skipping past matched Enter/Exit pairs.
     * Breakpoints will also stop the simulation even if we haven't reached an Exit.
     */
    void stepOver();

    /**
     * Advances the simulation until we exit from the current stack frame.
     * Breakpoints will also stop the simulation even if we haven't left the stack frame.
     */
    void stepOut();

    /** Advances the simulation until we hit a breakpoint, or the trace completes. */
    void run();

    /** Breakpoints will force the simulation to stop whenever a desired line is reached. */
    void setBreakpoints(std::unordered_set<int> breakpointLines);
    void addBreakpoint(int line);
    void removeBreakpoint(int line);
    using BreakpointSet = std::unordered_set<int>;
    const BreakpointSet& getBreakpoints() { return fBreakpointLines; }

    /** Returns true if we have reached the end of the trace. */
    bool traceHasCompleted() const;

    /** Returns true if there is a breakpoint set at the current line. */
    bool atBreakpoint() const;

    /** Retrieves the cursor position. */
    size_t cursor() { return fCursor; }

    /** Retrieves the current line. */
    int32_t getCurrentLine() const;

    /** Retrieves the current line for a given stack frame. */
    int32_t getCurrentLineInStackFrame(int stackFrameIndex) const;

    /** Returns the call stack as an array of FunctionInfo indices. */
    std::vector<int> getCallStack() const;

    /** Returns the size of the call stack. */
    int getStackDepth() const;

    /**
     * Returns every line number reached inside this debug trace, along with the remaining number of
     * times that this trace will reach it. e.g. {100, 2} means line 100 will be reached twice.
     */
    using LineNumberMap = std::unordered_map<int, int>;
    const LineNumberMap& getLineNumbersReached() const { return fLineNumbers; }

    /** Returns variables from a stack frame, or from global scope. */
    struct VariableData {
        int     fSlotIndex;
        bool    fDirty;  // has this slot been written-to since the last step call?
        double  fValue;  // value in slot (with type-conversion applied)
    };
    std::vector<VariableData> getLocalVariables(int stackFrameIndex) const;
    std::vector<VariableData> getGlobalVariables() const;

private:
    /**
     * Executes the trace op at the passed-in cursor position. Returns true if we've reached a line
     * or exit trace op, which indicate a stopping point.
     */
    bool execute(size_t position);

    /**
     * Cleans up temporary state between steps, such as the dirty mask and function return values.
     */
    void tidyState();

    /** Updates fWriteTime for the entire variable at a given slot. */
    void updateVariableWriteTime(int slotIdx, size_t writeTime);

    /** Returns a vector of the indices and values of each slot that is enabled in `bits`. */
    std::vector<VariableData> getVariablesForDisplayMask(const SkBitSet& bits) const;

    struct StackFrame {
        int32_t   fFunction;     // from fFuncInfo
        int32_t   fLine;         // our current line number within the function
        SkBitSet  fDisplayMask;  // the variable slots which have been touched in this function
    };
    struct Slot {
        int32_t   fValue;        // values in each slot
        int       fScope;        // the scope value of each slot
        size_t    fWriteTime;    // when was the variable in this slot most recently written?
                                 // (by cursor position)
    };
    sk_sp<DebugTracePriv>      fDebugTrace;
    size_t                     fCursor = 0;      // position of the read head
    int                        fScope = 0;       // the current scope depth (as tracked by
                                                 // trace_scope)
    std::vector<Slot>          fSlots;           // the array of all slots
    std::vector<StackFrame>    fStack;           // the execution stack
    std::optional<SkBitSet>    fDirtyMask;       // variable slots touched during the most-recently
                                                 // executed step
    std::optional<SkBitSet>    fReturnValues;    // variable slots containing return values
    LineNumberMap              fLineNumbers;     // holds [line number, the remaining number of
                                                 // times to reach this line during the trace]
    BreakpointSet              fBreakpointLines; // all breakpoints set by setBreakpointLines
};

}  // namespace SkSL

#endif  // SKSL_ENABLE_TRACING
