/* Debugserver requests:
 *
 * Empty
 * -----
 *
 * When the kind of request is set to empty, the debugserver is
 * ready to initiate a new request from the client.
 *
 * Invoke
 * ------
 *
 * The Debugserver requests that a thread invokes a given
 * code object. The debugserver sets up as much as it can
 * on its own, then notifies the thread to wake up and do
 * the rest.
 *
 * The thread will hold the ID of the request in Special Return Data,
 * so that returning from the code or unwinding with an exception or
 * taking a continuation can be handled properly.
 *
 * Since we want the thread to function normally with respect to break
 * points and stepping, the thread will behave essentially as if
 * fully resumed.
 *
 */

typedef enum {
    MVM_DebugRequest_empty,
    MVM_DebugRequest_invoke,
} MVMDebugServerRequestKind;

typedef enum {
    MVM_DebugRequestStatus_sender_is_waiting,
    MVM_DebugRequestStatus_receiver_acknowledged,
    MVM_DebugRequestStatus_receiver_finished,
    MVM_DebugRequestStatus_sender_acknowledged
} MVMDebugServerRequestStatus;

struct MVMDebugServerHandleTableEntry {
    MVMuint64 id;
    MVMObject *target;
};

struct MVMDebugServerHandleTable {
    MVMuint32 allocated;
    MVMuint32 used;

    MVMuint64 next_id;

    MVMDebugServerHandleTableEntry *entries;
};

struct MVMDebugServerBreakpointInfo {
    MVMuint64 breakpoint_id;
    MVMuint32 line_no;

    MVMuint8 shall_suspend;
    MVMuint8 send_backtrace;
};

struct MVMDebugServerBreakpointFileTable {
    char *filename;
    MVMuint32 filename_length;
    MVMuint32 lines_active_alloc;

    MVMuint8 *lines_active;

    MVMDebugServerBreakpointInfo *breakpoints;
    MVMuint32 breakpoints_alloc;
    MVMuint32 breakpoints_used;

    /* When a breakpoint is added for a file that is not loaded yet, we create
     * an entry in the files table, but the filename is completely arbitrary
     * and is not guaranteed to ever show up.
     * Therefore, we put a flag here to store if we actually saw the file for
     * real, or were only asked to include it. */
    MVMuint8 really_loaded;
};

struct MVMDebugServerBreakpointTable {
    MVMDebugServerBreakpointFileTable *files;
    MVMuint32 files_used;
    MVMuint32 files_alloc;
};

/* This struct holds all data used for communication between
 * the Debugserver and a thread.
 *
 * * Invoke a code object
 */
struct MVMDebugServerRequestData {
    MVMDebugServerRequestKind kind;

    /* The ID of the request taken from the network packet,
     * to be used for responses to the client. */
    MVMuint64 request_id;

    MVMThreadContext *target_tc;

    AO_t status;

    union
    {
        struct {
            MVMCode *target;
            MVMArgs *args;
        } invoke;
        MVMObject *o;
    } data;
};

struct MVMDebugServerData {
    /* Debug Server thread */
    uv_thread_t thread;

    /* Protect the debugserver-related condvars */
    uv_mutex_t mutex_cond;

    /* Protect sending data on the network */
    uv_mutex_t mutex_network_send;

    /* Protect the open requests list */
    uv_mutex_t mutex_request_list;

    /* Condition variable to tell threads to check their state for changes
     * like "i should suspend" */
    uv_cond_t tell_threads;

    /* Condition variable to tell the worker to check thread states
     * for changes like "i just suspended" */
    uv_cond_t tell_worker;

    MVMDebugServerRequestData request_data;

    MVMuint32 port;
    MVMuint32 thread_id;

    MVMuint64 event_id;

    MVMDebugServerHandleTable *handle_table;

    MVMDebugServerBreakpointTable *breakpoints;
    MVMuint32 any_breakpoints_at_all;
    MVMuint32 breakpoints_alloc;
    MVMuint32 breakpoints_used;
    uv_mutex_t mutex_breakpoints;

    /* If the user asked to watch new files showing up, this is the event ID
     * to send notifications for. */
    MVMuint64 loaded_file_event_id;
    /* May also stop one or all threads when a new file is hit */
    MVMuint8  stop_on_new_file : 2;
    /* May send a backtrace along when that happens, for convenience. */
    MVMuint8  backtrace_on_new_file : 1;

    void *messagepack_data;

    MVMuint8 debugspam_network;
    MVMuint8 debugspam_protocol;
};

MVM_PUBLIC void MVM_debugserver_init(MVMThreadContext *tc, MVMuint32 port);
MVM_PUBLIC void MVM_debugserver_partial_init(MVMThreadContext *tc);
MVM_PUBLIC void MVM_debugserver_mark_handles(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMHeapSnapshotState *snapshot);

MVM_PUBLIC void MVM_debugserver_notify_thread_creation(MVMThreadContext *tc);
MVM_PUBLIC void MVM_debugserver_notify_thread_destruction(MVMThreadContext *tc);

MVM_PUBLIC void MVM_debugserver_notify_unhandled_exception(MVMThreadContext *tc, MVMException *ex);

MVM_PUBLIC MVMuint64 MVM_dump_all_backtraces(MVMThreadContext *dtc, MVMuint64 is_harmless);

MVM_PUBLIC void MVM_debugserver_register_line(MVMThreadContext *tc, char *filename, MVMuint32 filename_len, MVMuint32 line_no,  MVMuint32 *file_idx);
MVM_PUBLIC MVMint32 MVM_debugserver_breakpoint_check(MVMThreadContext *tc, MVMuint32 file_idx, MVMuint32 line_no);
