local breakpoints = {}
local dbg_steps_mode = false
local dbg_step_into_func = false
local hook_lock = false
local current_func
local current_func_stack_size

local __parse_path = parse_path
local _debug_getinfo = debug.getinfo
local _debug_getlocal = debug.getlocal
local __pause = debug.pause
local __error = error
local __sethook = debug.sethook

-- 'return' hook not called for some functions
-- todo: speedup
local function calc_stack_size()
    local s = debug.traceback("", 2)
    local count = 0
    for i in s:gmatch("\n") do
        count = count + 1
    end
    return count
end

local is_debugging = debug.is_debugging()
if is_debugging then
    __sethook(function (e, line)
        if e == "return" then
            local info = _debug_getinfo(2)
            if info.func == current_func then
                current_func = nil
            end
        end
        if dbg_steps_mode and not hook_lock then
            hook_lock = true

            if not dbg_step_into_func then
                local func = _debug_getinfo(2).func
                if func ~= current_func then
                    return
                end
                if current_func_stack_size ~= calc_stack_size() then
                    return
                end
            end
            current_func = func
            __pause("step")
            debug.pull_events()
        end
        hook_lock = false
        local bps = breakpoints[line]
        if not bps then
            return
        end
        local source = _debug_getinfo(2).source
        if not bps[source] then
            return
        end
        current_func = _debug_getinfo(2).func
        current_func_stack_size = calc_stack_size()
        __pause("breakpoint")
        debug.pull_events()
    end, "lr")
end

local DBG_EVENT_SET_BREAKPOINT = 1
local DBG_EVENT_RM_BREAKPOINT = 2
local DBG_EVENT_STEP = 3
local DBG_EVENT_STEP_INTO_FUNCTION = 4
local DBG_EVENT_RESUME = 5
local DBG_EVENT_GET_VALUE = 6
local __pull_events = debug.__pull_events
local __sendvalue = debug.__sendvalue
debug.__pull_events = nil
debug.__sendvalue = nil

function debug.get_pack_by_frame(func)
    local prefix, _ = __parse_path(_debug_getinfo(func, "S").source)
    return prefix
end

function debug.pull_events()
    if not is_debugging then
        return
    end
    if not debug.is_debugging() then
        is_debugging = false
        __sethook()
    end
    local events = __pull_events()
    if not events then
        return
    end
    for i, event in ipairs(events) do
        if event[1] == DBG_EVENT_SET_BREAKPOINT then
            debug.set_breakpoint(event[2], event[3])
        elseif event[1] == DBG_EVENT_RM_BREAKPOINT then
            debug.remove_breakpoint(event[2], event[3])
        elseif event[1] == DBG_EVENT_STEP then
            dbg_steps_mode = true
            dbg_step_into_func = false
        elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then
            dbg_steps_mode = true
            dbg_step_into_func = true
        elseif event[1] == DBG_EVENT_RESUME then
            dbg_steps_mode = false
            dbg_step_into_func = false
        elseif event[1] == DBG_EVENT_GET_VALUE then
            local _, value = _debug_getlocal(event[2] + 3, event[3])
            for _, key in ipairs(event[4]) do
                if value == nil then
                    value = "error: index nil value"
                    break
                end
                value = value[key]
            end
            __sendvalue(value, event[2], event[3], event[4])
            __pause()
        end
    end
end

function debug.set_breakpoint(source, line)
    local bps = breakpoints[line]
    if not bps then
        bps = {}
        breakpoints[line] = bps
    end
    bps[source] = true
end

function debug.remove_breakpoint(source, line)
    local bps = breakpoints[line]
    if not bps then
        return
    end
    bps[source] = nil
end

function error(message, level)
    if is_debugging then
        __pause("exception", message)
    end
    __error(message, level)
end
