/*
 * Copyright (C) 2021 Ant Group.  All rights reserved.
 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 */

#ifndef _DEBUG_ENGINE_H
#define _DEBUG_ENGINE_H

#include "bh_list.h"
#include "gdbserver.h"
#include "thread_manager.h"

typedef enum WASMDebugControlThreadStatus {
    RUNNING,
    DETACHED,
    STOPPED,
} WASMDebugControlThreadStatus;

struct WASMDebugEngine;
struct WASMDebugInstance;

typedef struct WASMDebugControlThread {
    WASMGDBServer *server;
    korp_tid tid;
    korp_mutex wait_lock;
    char ip_addr[128];
    int port;
    WASMDebugControlThreadStatus status;
    struct WASMDebugEngine *debug_engine;
    struct WASMDebugInstance *debug_instance;
} WASMDebugControlThread;

typedef struct WASMDebugBreakPoint {
    struct WASMDebugBreakPoint *next;
    uint64 addr;
    uint64 orignal_data;
} WASMDebugBreakPoint;

typedef struct WASMDebugWatchPoint {
    bh_list_link next;
    uint64 addr;
    uint64 length;
} WASMDebugWatchPoint;

typedef enum debug_state_t {
    /* Debugger state conversion sequence:
     *   DBG_LAUNCHING ---> APP_STOPPED <---> APP_RUNNING
     */
    DBG_LAUNCHING,
    APP_RUNNING,
    APP_STOPPED,
    DBG_ERROR
} debug_state_t;

typedef struct WASMDebugExecutionMemory {
    uint64 start_offset;
    uint64 current_pos;
    uint32 size;
} WASMDebugExecutionMemory;

struct WASMDebugInstance {
    struct WASMDebugInstance *next;
    WASMDebugControlThread *control_thread;
    bh_list break_point_list;
    bh_list watch_point_list_read;
    bh_list watch_point_list_write;
    WASMCluster *cluster;
    uint32 id;
    korp_tid current_tid;
    korp_mutex wait_lock;
    korp_cond wait_cond;
    /* Last stopped thread, it should be set to NULL when sending
     * out the thread stop reply */
    WASMExecEnv *volatile stopped_thread;
    /* Currently status of the debug instance, it will be set to
     * RUNNING when receiving STEP/CONTINUE commands, and set to
     * STOPPED when any thread stopped */
    volatile debug_state_t current_state;
    /* Execution memory info. During debugging, the debug client may request to
     * malloc a memory space to evaluate user expressions. We preserve a buffer
     * during creating debug instance, and use a simple bump pointer allocator
     * to serve lldb's memory request */
    WASMDebugExecutionMemory exec_mem_info;
};

typedef enum WASMDebugEventKind {
    BREAK_POINT_ADD,
    BREAK_POINT_REMOVE
} WASMDebugEventKind;

typedef struct WASMDebugEvent {
    WASMDebugEventKind kind;
    unsigned char metadata[0];
} WASMDebugEvent;

typedef struct WASMDebugMemoryInfo {
    uint64 start;
    uint64 size;
    char name[128];
    char permisson[4];
} WASMDebugMemoryInfo;

typedef enum WasmAddressType {
    WasmMemory = 0x00,
    WasmObj = 0x01,
    WasmInvalid = 0x03
} WasmAddressType;

#define WASM_ADDR(type, id, offset) \
    (((uint64)type << 62) | ((uint64)0 << 32) | ((uint64)offset << 0))

#define WASM_ADDR_TYPE(addr) (((addr)&0xC000000000000000) >> 62)
#define WASM_ADDR_OFFSET(addr) (((addr)&0x00000000FFFFFFFF))

#define INVALIED_ADDR (0xFFFFFFFFFFFFFFFF)

void
on_thread_stop_event(WASMDebugInstance *debug_inst, WASMExecEnv *exec_env);

void
on_thread_exit_event(WASMDebugInstance *debug_inst, WASMExecEnv *exec_env);

WASMDebugInstance *
wasm_debug_instance_create(WASMCluster *cluster, int32 port);

void
wasm_debug_instance_destroy(WASMCluster *cluster);

WASMDebugInstance *
wasm_exec_env_get_instance(WASMExecEnv *exec_env);

bool
wasm_debug_engine_init(char *ip_addr, int32 process_port);

void
wasm_debug_engine_destroy(void);

WASMExecEnv *
wasm_debug_instance_get_current_env(WASMDebugInstance *instance);

uint64
wasm_debug_instance_get_pid(WASMDebugInstance *instance);

korp_tid
wasm_debug_instance_get_tid(WASMDebugInstance *instance);

uint32
wasm_debug_instance_get_tids(WASMDebugInstance *instance, korp_tid tids[],
                             uint32 len);

void
wasm_debug_instance_set_cur_thread(WASMDebugInstance *instance, korp_tid tid);

uint64
wasm_debug_instance_get_pc(WASMDebugInstance *instance);

uint64
wasm_debug_instance_get_load_addr(WASMDebugInstance *instance);

WASMDebugMemoryInfo *
wasm_debug_instance_get_memregion(WASMDebugInstance *instance, uint64 addr);

void
wasm_debug_instance_destroy_memregion(WASMDebugInstance *instance,
                                      WASMDebugMemoryInfo *mem_info);

bool
wasm_debug_instance_get_obj_mem(WASMDebugInstance *instance, uint64 addr,
                                char *buf, uint64 *size);

bool
wasm_debug_instance_get_linear_mem(WASMDebugInstance *instance, uint64 addr,
                                   char *buf, uint64 *size);

bool
wasm_debug_instance_get_mem(WASMDebugInstance *instance, uint64 addr, char *buf,
                            uint64 *size);

bool
wasm_debug_instance_set_mem(WASMDebugInstance *instance, uint64 addr, char *buf,
                            uint64 *size);

uint32
wasm_debug_instance_get_call_stack_pcs(WASMDebugInstance *instance,
                                       korp_tid tid, uint64 buf[], uint64 size);

bool
wasm_debug_instance_add_breakpoint(WASMDebugInstance *instance, uint64 addr,
                                   uint64 length);

bool
wasm_debug_instance_remove_breakpoint(WASMDebugInstance *instance, uint64 addr,
                                      uint64 length);

bool
wasm_debug_instance_watchpoint_write_add(WASMDebugInstance *instance,
                                         uint64 addr, uint64 length);

bool
wasm_debug_instance_watchpoint_write_remove(WASMDebugInstance *instance,
                                            uint64 addr, uint64 length);

bool
wasm_debug_instance_watchpoint_read_add(WASMDebugInstance *instance,
                                        uint64 addr, uint64 length);

bool
wasm_debug_instance_watchpoint_read_remove(WASMDebugInstance *instance,
                                           uint64 addr, uint64 length);

bool
wasm_debug_instance_on_failure(WASMDebugInstance *instance);

bool
wasm_debug_instance_interrupt_all_threads(WASMDebugInstance *instance);

bool
wasm_debug_instance_continue(WASMDebugInstance *instance);

bool
wasm_debug_instance_detach(WASMDebugInstance *instance);

bool
wasm_debug_instance_kill(WASMDebugInstance *instance);

uint32
wasm_debug_instance_get_thread_status(WASMDebugInstance *instance,
                                      korp_tid tid);

bool
wasm_debug_instance_singlestep(WASMDebugInstance *instance, korp_tid tid);

bool
wasm_debug_instance_get_local(WASMDebugInstance *instance, int32 frame_index,
                              int32 local_index, char buf[], int32 *size);

bool
wasm_debug_instance_get_global(WASMDebugInstance *instance, int32 frame_index,
                               int32 global_index, char buf[], int32 *size);

#if WASM_ENABLE_LIBC_WASI != 0
bool
wasm_debug_instance_get_current_object_name(WASMDebugInstance *instance,
                                            char name_buffer[], uint32 len);
#endif

uint64
wasm_debug_instance_mmap(WASMDebugInstance *instance, uint32 size,
                         int32 map_prot);

bool
wasm_debug_instance_ummap(WASMDebugInstance *instance, uint64 addr);
#endif
