---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by sun.
--- DateTime: 2020-05-25 19:18
---

local __remove = "__removew"
local __watch = "__watch"
local __get = "__get"
local __path = "__path"
local __ishook = "__ishook"
local __kvoname = "__kvoname"
local WATCH = "watch" -- prevew中使用
local FOREACH = "forEach"
local __b_G = "G."


local _kpathCache = {}
local _watchCache = {}
local _debugpwacths = {}
local _debugcwacths = {}


debug_preview_open = false
debug_preview_watch = false

local function bindMeta_remove(id)
    DataBinding:removew(id)
end

local function bindMeta_path(k1, k2, force)
    -- preview模式下会前面会多个 G. 所以需要删除
    if force or (debug_preview_open == false and debug_preview_watch) then
        if string.len(k1) > 2 and string.sub(k1, 1, 2) == __b_G then
            k1 = string.sub(k1, 3)
        end
    end
    if k2 then
        return k1 .. "." .. k2
    end
    return k1
end
-- __index
local function debug_bindMeta__index(t, k)
    local mt = getmetatable(t)
    if k == __kvoname or k == __ishook then
        return mt[k]
    end
    --print("to Get::" .. mt.__kvoname, k)
    if k == __get then -- get
        return DataBinding:get(bindMeta_path(mt.__kvoname))
    elseif k == __path then
        return mt.__kvoname
    elseif k == __remove then
        mt.__opname = k
        return t
    end
    if debug_preview_watch then
        if k == WATCH or k == FOREACH then
            mt.__opname = k
            return t
        end
    end
    return BindMeta(bindMeta_path(mt.__kvoname,  k))
end

-- __newindex
local function debug_bindMeta__newindex(t, k, v)
    local mt = getmetatable(t)
    if k == __watch then -- watch
        local _bTab = _watchCache[v]
        if not _bTab then
            _bTab = {}
            _watchCache[v] = _bTab
        end
        local w_id = DataBinding:watch(bindMeta_path(mt.__kvoname), v)
        if w_id then
            table.insert(_bTab, w_id)
        end
        if debug_preview_watch then
            local _k = bindMeta_path(mt.__kvoname)
            local _t = _debugcwacths[_k]
            if not _t then
                _t = {}
                _debugcwacths[_k] = _t
            end
            table.insert(_t, v)
        end
        return
    end
    if debug_preview_open then
        rawset(t, k, v)
        if type(v) == "table" then
            local meta = BindMeta(bindMeta_path(mt.__kvoname , k), v)
            for _k, _v in pairs(v) do
                debug_bindMeta__newindex(meta, _k, _v)
            end
        end
        return
    end

    if debug_preview_watch then
        -- mock顶层数据
        if mt.__kvoname == "G" then
            DataBinding:mock(k, v)
            for _k, _v in pairs(_debugcwacths) do
                for _, _f in pairs(_v) do
                    _f(DataBinding:get(_k), nil)
                end
            end
            return
        end
        if type(v) == "table" and v.__ishook then
            v = v.__get
        end
        local _pv = rawget(t, k) or t[k].__get -- 旧值
        DataBinding:update(bindMeta_path(mt.__kvoname, k), v)
        if v ~= _pv then
            for _, __f in pairs(_debugpwacths[bindMeta_path(mt.__kvoname, k)] or {}) do
                __f(v, _pv)
            end
        end
        return
    end
    DataBinding:update(bindMeta_path(mt.__kvoname, k), v)
end

-- __call
local function debug_bindMeta__call(t, ...)
    local mt = getmetatable(t)
    local op = mt.__opname
    if not op then
        assert(true, "bind meta call error...")
        return
    end
    mt.__opname = nil

    local size = select("#", ...)
    if size == 0 then
        return
    end

    local p1 = select(1, ...)
    if op == __remove then
        -- remove watch
        for _, _v in ipairs(_watchCache[p1] or {}) do
            bindMeta_remove(_v)
        end
        return
    end

    if debug_preview_watch then
        if size == 2 and op == WATCH then
            -- debug prevew watch
            local k = bindMeta_path(mt.__kvoname,  p1, true)
            local v = _debugpwacths[k]
            if not v then
                v = {}
                _debugpwacths[k] = v
            end
            table.insert(v, select(2, ...))
        elseif size == 1 and op == FOREACH then
            for __k, __v in pairs(t) do
                p1(__v, __k)
            end
        end
    end
end

local function debug_bindMeta_setmetable(o, kpath)
    _kpathCache[kpath] = o
    local meta = getmetatable(o)
    setmetatable(o, {
        __index = debug_bindMeta__index,
        __newindex = debug_bindMeta__newindex,
        __call = debug_bindMeta__call,
        __ishook = true,
        __kvoname = kpath,
        __mt = meta
    })
end

---
--- hook table insert/remove
---
if hook_table_insert == nil then
    hook_table_insert = table.insert
    table.insert = function(t, ...)
        assert(t, "insert table must not be nil ")
        if t.__ishook then
            if select('#', ...) == 1 then
                DataBinding:insert(bindMeta_path(t.__kvoname), -1, select(1, ...))
                return
            end
            DataBinding:insert(bindMeta_path(t.__kvoname), select(1, ...), select(2, ...))
            return
        end
        hook_table_insert(t, ...)
    end
    hook_table_remove = table.remove
    table.remove = function(t, ...)
        assert(t, "remove table must not be nil ")
        if t.__ishook then
            if select('#', ...) == 0 then
                DataBinding:remove(bindMeta_path(t.__kvoname), -1)
                return
            end
            DataBinding:remove(bindMeta_path(t.__kvoname), select(1, ...))
            return
        end
        hook_table_remove(t, ...)
    end
end

---
--- 初始化方法
---
function BindMeta(kpath, o)
    kpath = kpath or ""
    assert(type(kpath) == "string", "kpath must be string...")

    if o then
        if not o.__ishook then
            debug_bindMeta_setmetable(o, kpath)
        end
        return o
    end
    o = _kpathCache[kpath]
    if o == nil then
        o = {}
        debug_bindMeta_setmetable(o, kpath)
    end
    return o;
end

---
--- 清空
---
function BindMetaClear()
    _kpathCache = {}
    _debugpwacths = {}
    for _, v in pairs(_watchCache) do
        for _, _v in pairs(v) do
            bindMeta_remove(_v)
        end
    end
    _watchCache = {}
    _debugcwacths = {}
end


--- 在preview中使用
function BindMetaPreviewStart()
    debug_preview_open = true
    debug_preview_watch = true
end

function BindMetaPreviewEnd()
    debug_preview_open = false
    _kpathCache = {}
end
