local behavior_node = require 'behavior3.behavior_node'
local behavior_ret = require 'behavior3.behavior_ret'
local json = require 'json'

local meta = {
    __newindex = function(_, k)
        error(string.format('readonly:%s', k), 2)
    end
}
local function const(t)
    setmetatable(t, meta)
    for _, v in pairs(t) do
        if type(v) == 'table' then
            const(v)
        end
    end
    return t
end

local trees = {}

local mt = {}
mt.__index = mt
function mt:init(name, path)
    path = path or './workspace/trees/'

    self.name = name
    self.tick = 0
    local file, err = io.open(string.format("%s%s%s", path, name, '.json'), 'r')
    assert(file, err)
    local str = file:read('*a')
    local data = const(json.decode(str))
    self.root = behavior_node.new(data.root, self)
end

function mt:run(env)
    -- print(string.format('===== tree:%s, tick:%s, stack:%d =====', self.name, self.tick, #env.stack))
    if #env.stack > 0 then
        local last_node = env.stack[#env.stack]
        while last_node do
            local ret = last_node:run(env)
            if ret == behavior_ret.RUNNING then
                break
            end
            last_node = env.stack[#env.stack]
        end
    else
        self.root:run(env)
    end
    self.tick = self.tick + 1
end

function mt:interrupt(env)
    if #env.stack > 0 then
        env.inner_vars = {}
        env.stack = {}
    end
end

local function new_tree(name)
    local tree = setmetatable({}, mt)
    tree:init(name)
    trees[name] = tree
    return tree
end

local function new_env(params)
    local env = {
        inner_vars = {}, -- [k.."_"..node.id] => vars
        vars = {},
        stack = {},
        last_ret = nil
    }
    for k, v in pairs(params) do
        env[k] = v
    end

    function env:get_var(k)
        return env.vars[k]
    end
    function env:set_var(k, v)
        self.vars[k] = v
    end
    function env:get_inner_var(node, k)
        return self.inner_vars[k .. '_' .. node.id]
    end
    function env:set_inner_var(node, k, v)
        self.inner_vars[k .. '_' .. node.id] = v
    end
    function env:push_stack(node)
        self.stack[#self.stack + 1] = node
    end
    function env:pop_stack()
        local node = self.stack[#self.stack]
        self.stack[#self.stack] = nil
        return node
    end
    return env
end

local M = {}
function M.new(name, env_params)
    local env = new_env(env_params)
    local tree = trees[name] or new_tree(name)
    return {
        tree = tree,
        run = function()
            tree:run(env)
        end,
        interrupt = function()
            tree:interrupt(env)
        end,
    }
end
return M
