#! /usr/bin/env lua
--
-- env_api.lua
-- Copyright (C) 2022 Shewer Lu <shewer@gmail.com>
--
-- Distributed under terms of the MIT license.
--

-- env metatable
local List = require("lib/list")
local Config_api = require("lib/config_api")

local E = {}
--
function E:Context()
	return self.engine.context
end

function E:Config()
	return self.engine.schema.config
end

-- 取得 librime 狀態 tab { always=true ....}
-- 須要 新舊版 差異  comp.empty() -->  comp:empty()
function E:get_status()
	local ctx = self.engine.context
	local stat = {}
	local comp = ctx.composition
	stat.always = true
	stat.composing = ctx:is_composing()
	stat.empty = not stat.composing
	stat.has_menu = ctx:has_menu()
	-- old version check ( Projection userdata)
	local ok, empty
	if rime_api.Version() < 100 then
		ok, empty = pcall(comp.empty)
		empty = ok and empty or comp:empty() --  empty=  ( ok ) ? empty : comp:empty()
	else
		empty = comp:empty()
	end
	stat.paging = not empty and comp:back():has_tag("paging")
	return stat
end

-- Config_get_obj(path[,type])  return obj , args: path , type( i
--    type( 1 : ConfigItem 2 : Config of Value or List or Map )
--    type 4 只能單向轉換
--
--  check base_type
--  conver ConfigItem of obj  to list { {path= string, value= string} ...}

local function select_capi(num_type)
	num_type = (type(num_type) == "number") and (num_type > 0) and (num_type <= 3) and num_type or 1
	local ar = { Config_api.get_obj, Config_api.get_item, Config_api.get_cdata }
	return ar[num_type]
end

function E:Config_get(path, _type)
	local item = self.engine.schema.config:get_item(path)
	local conv_func = select_capi(_type)

	return conv_func(item)
end

function E:Config_set(path, obj)
	if not obj  then return false end
	return self.engine.schema.config:set_item(path, Config_api.get_item(obj))
end

--  ex:
--   { 1,{a=2,b=4},2,3,4} , "test" --> { {path= "test/@0" , value = "1" } ,{path="test/@2/a", value="2" ... }

local function to_list_with_path(obj, path, tab, loopchk)
	loopchk = loopchk or {}
	tab = tab or {}
	path = path or ""
	local tp = type(obj)
	local base_type = tp == "number" or tp == "string" or tp == "boolean"
	if loopchk[obj] or base_type then
		table.insert(tab, { path = path, value = obj })
		return tab
	end
	loopchk[obj] = true
	if type(obj) == "table" then
		local is_list = #obj > 0
		local lpath = #path > 0 and path .. "/" or path
		lpath = is_list and lpath .. "@" or lpath

		for k, v in (is_list and ipairs or pairs)(obj) do
			to_list_with_path(v, lpath .. k, tab, loopchk)
		end
		return tab
	end
end

function E:Config_get_with_path(path, tpath)
	local obj = self:Config_get(path)
	tpath = tpath or path
	return to_list_with_path(obj, tpath)
end

local function chk_pdata(path, obj)
	if not obj then
		return false
	end
	path:each(function(elm)
		local ok = elm:match("^[%a_]+$") and elm:match("^@%d+$")
		if not ok then
			return false
		end
	end)
	return true
end

function E:Config_set_with_path(pdata)
	---@diagnostic disable-next-line: unused-local
	for i, v in ipairs(pdata) do
		if chk_pdata(v.path, v.obj) then
			self:Config_set(v.path, v.obj)
			-- else
			--     Log(WARN, "config set error", v.path, v.obj)
		end
	end
end

function E:config_path_to_str_list(path)
	return List(self:Config_get(path)):map(function(elm)
		return elm.path .. ": " .. elm.value
	end)
end

-- Get_tag  args :  ()  , (nil, "translator") ,("date")
function E:Get_tag(def_tag, ns)
	def_tag = def_tag or "" -- default ""
	ns = ns or self.name_space -- default env.name_space
	return self.engine.schema.config:get_string(ns .. "/tag") or def_tag
end

function E:Get_tags(ns)
    ns = ns or self.name_space or ""
    local vals = self:Config_get(ns .. "/tags")
    return vals and Set(vals) or nil
end

function E:append_value_before(path, elm)
	local obj = self:Config_get(path)
	if type(obj) ~= "table" or #obj < 1 then
		return
	end
	local list = List(self:Config_get(path))
	if list:find(elm) then return end
	-- local index = list:find(mvalue)
	-- local dpath = index and path .. "/@before " .. index - 1 or path .. "/@next"

	-- if not self:Config_set(dpath, elm) then
	--     Log(ERROR, "config set ver error", "path", path, "value", elm)
	-- end
end

-- option function

-- context warp
--[[
local C = {}
function C.Set_option(self, name)
    self:set_option(name, true)
    return self:get_option(name)
end

function C.Unset_option(self, name)
    self:set_option(name, false)
    return self:get_option(name)
end

function C.Toggle_option(self, name)
    self:set_option(name, not self:get_option(name))
    return self:get_option(name)
end
--]]

function E:Set_option(name)
	self.engine.context:set_option(name, true)
	return true
end

function E:Unset_option(name)
	self.engine.context:set_option(name, false)
	return false
end

function E:Toggle_option(name)
	local context = self.engine.context
	context:set_option(name, not context:get_option(name))
	return context:get_option(name)
end

function E:Get_option(name)
	return self.engine.context:get_option(name)
end

-- property function
function E:Get_property(name)
	return self.engine.context:get_property(name)
end

function E:Set_property(name, str)
	self.engine.context:set_property(name, str)
	return str
end

-- processor function  config
function E:get_keybinds(path)
	path = (path or self.name_space) .. "/keybinds"
	local tab = self:Config_get(path)
	tab = type(tab) == "table" and tab or {}
	for key, name in next, tab do
		tab[key] = KeyEvent(name)
	end
	return tab
end

function E:components_str()
	return List("processors", "segmentors", "translators", "filters")
		:map(function(elm)
			return "engine/" .. elm
		end)
		:reduce(function(path, list)
			return list + self:Config_get_with_path(path)
		end, List())
		:map(function(elm)
			return elm.path .. ": " .. elm.value
		end)
end

--[[
function E:print_components(out)
    Log(out, string.format("----- %s : %s ----", self.engine.schema.schema_id, self.name_space))
    self:components_str():each(function(elm)
        Log(out, elm)
    end)
end
--]]

---  delete
function E:components(path)
	return self:Config_get(path or "engine")
end

E.__index = E

-- wrap env
-- Env(env):get_status()
-- Env(env):config() -- return config with new methods
-- Env(env):context() -- return context with new methods

local function Env(env)
	return setmetatable(env, E)
end

return Env
