--[[ ----------------------------------------------------------------------------------------------
 File       : lua_cfg.lua
 Description: парсеры данных из конфигурационных файлов
 Copyright  : 2017 © Prosectors Project
 Author(s)  : Nazgool, Karlan 
--]] ----------------------------------------------------------------------------------------------

module '_G'

-- парсит строку вида "ааа, ббб, ввв..." в таблицу { "ааа", "ббб", "ввв", ... }
function parse_names(s, vfunc, map, pattern)
	if vfunc == true then
		vfunc = tonumber
	end
    local t = {}
    if is_string(s) then
		pattern = pattern or "([%w_%-.\\]+)%p*" -- ([%w_%-.\\]+)%p*
		if map ~= nil then
			for name in s:gmatch(pattern) do
				t[name] = vfunc and vfunc(name) or map
			end
		else
			local i = 0
			for name in s:gmatch(pattern) do
				i = i + 1
				t[i] = vfunc and vfunc(name) or name
			end
		end
    end
    return t
end

-- не знаю как назвать, канонично она называется parse_spawns, пусть так и будет
function parse_spawns(s, p, c, vfunc, no_assert)
    -- проверять на nil здесь s не нужно, так как у разрабов стоит возврат пустой таблицы, а эта функция и так вернет пустую таблицу если s == nil или s == ""
    local t, i, res = parse_names(s, (vfunc or true)), 1, {}
    while i <= #t do
        local buff, str, cnt, cnd = {}, t[i], t[i+1], t[i+2]
        if not no_assert then
            assert(is_string(str), 'bad string in arguments parse_spawns (%s)',s) -- на случай str,num,num
        end
        buff.section = str
        if is_number(cnt) and is_number(cnd) then
            buff.prob = cnt
            buff.cond = cnd
            i = i+3
        elseif is_number(cnt) then
            buff.prob = cnt
            buff.cond = (c or 1)
            i = i+2
        else
            buff.prob = (p or 1)
            buff.cond = (c or 1)
            i = i+1
        end
        table.insert(res, buff)
    end
    return res
end

--[[function parse_spawns_in_list(s)
    local t = {}
    local i = 0
    if is_string(s) then
        for name in s:gmatch("([%w_%-.\\]+)%p*" ) do
            local count = tonumber(name)
            if count then
                assert(is_string(t[i]), 'is_string(t[i-1])')
                for a=1,count do
                    table.insert(t, t[i])
                end
            else
                table.insert(t, name)
            end
            i = i + 1
        end
    end
    return t
end]]

function parse_key_value(s, num)
    if not is_string(s) then
        return nil
    end
    
    local t = {}
    
    for k, v in s:gmatch('([^%s,]+)[%s,]+([^%s,]+)') do --// backup ([%w_%-.\\]+)[%s,]+([%w_%-.\\]+)
        t[k] = num and tonumber(v) or v
    end
    
    return t
end

-- парсит строку вида "n1, n2, n3..." в таблицу { n1, n2, n3, ... } где n1, n2, n3... - дробные числа
function parse_nums(s)
    local t = {}
    local i = 0
    for num in s:gmatch('[%d-.]+') do
        i = i + 1
        t[i] = tonumber(num)
    end

    return t
end

function parse_target(target)
    local a,b = target:match('([^,]+),?(.*)')
    return a, b ~= '' and b or nil
end

-- парсит строку вида 50|{=best_pistol}psy_armed,psy_pain@wounded_psy|20|{=best_pistol}psy_shoot,psy_pain@{=best_pistol}wounded_psy_shoot,wounded_psy
function parse_data(npc,s)
    local t = {}

    if is_string(s) then
        for dist, condlist in s:gmatch("([%d.]+)%|([^%|]+)" ) do
            local state, sound = condlist:match('([^@]+)%@?(.*)')
            local data = {
                dist  = tonumber(dist),
                state = parse_condlist(state),
                sound = (sound ~= '') and parse_condlist(sound)
            }
            table.insert(t, data)
        end
    end
	if #t > 1 then
		table.sort(t,function(a,b) return a.dist > b.dist end)
	end
    return t
end

-- парсит строку вида bar_dolg_general_warn_zone|{+bar_dolg_base_pass}guard,threat@{+bar_dolg_base_pass}talk_hello,bar_dolg_guard_stop
function parse_zone_data(npc,s)
    local t = {}

    if is_string(s) then
        for dist, condlist in s:gmatch("([^%|]+)%|([^%|]+)" ) do
            local state, sound = condlist:match('([^@]+)%@?(.*)')
            local data = {
                zone  = dist,
                state = parse_condlist(state),
                sound = (sound ~= '') and parse_condlist(sound)
            }
            table.insert(t, data)
        end
    end
    
    return t
end

-- парсит строку вида "state@sound"
function parse_syn_data(npc,s)
    local t = {}
    for state,sound in string.gmatch(s, "([%w%_]+)%s*%@*%s*([%w%_]*)") do
        local dat = {}
        dat.state = state
        dat.sound = sound
        table.insert(t, dat)
    end
    return t
end

-- парсит строку вида "wp01|outr=pri_wave1_restr1|starti=pri_wave1_start|deathi=pri_wave1_monolith1_dead|ib"
-- аргумент "pathname" можно и убрать. Он использовался в отчетах об ощибках
function parse_waypoint_data(pathname, wpflags, wpname)
    local t = {flags = wpflags}
    
    if is_string(wpname) then
        wpname = wpname:gsub('^.-|', '') -- отрезать название точки пути (name00, wp01 и пр.)
        log('wpname [%s] gsub', wpname)
        for param in string.gmatch(wpname, "[^|]+") do -- "([%w_\\%=%-%,%*]+)|*"
            local k,v = param:match('^([^%s=]+)%s*%=?%s*(%S*)') -- ищем ключ=значение
            t[k] = (v == '') and true or v
        end
    end
    
    return t
end

-- esc_corps_esc_wounded_walk ; esc_corps_esc_wounded_look
function path_parse_waypoints(pathname)
    
    if not pathname then
        return nil
    end

    printf("_bp: path_parse_waypoints: pathname='%s'", pathname)
    local ptr = patrol(pathname)
    local cnt = ptr:count()
    local rslt = {}

    for pt = 0, cnt - 1 do
        printf("_bp: %s", ptr:name(pt))
        rslt[pt] = parse_waypoint_data(pathname, ptr:flags(pt), ptr:name(pt))
        if not rslt[pt] then
            error("error while parsing point %d of path '%s'", pt, pathname)
        end

    end

    return rslt
end

function path_parse_waypoints_from_arglist(pathname, num_points, ...)
    local arg = {...}

    if not pathname then
        return nil
    end

    local ptr = patrol(pathname)
    local cnt = ptr:count()

    if cnt ~= num_points then
        error("path '%s' has %d points, but %d points were expected", pathname, cnt, num_points)
    end

    local rslt = {}

    local cur_arg
    local fl
    for pt = 0, cnt-1 do
        cur_arg = arg[pt + 1]
        if not cur_arg then
            error("script error [1] while processing point %d of path '%s'", pt, pathname)
        end
        fl = flags32()
        fl:assign(cur_arg[1])
        rslt[pt] = parse_waypoint_data(pathname, fl, cur_arg[2])
        if not rslt[pt] then
            error("script error [2] while processing point %d of path '%s'", pt, pathname)
        end
    end

    return rslt
end

-- a | b | c  ==>  { 1 = "a", 2 = "b", 3 = "c" }
function parse_params(params)
    --printf("_bp: parse_params: params=%s", params)
    local rslt = {}
    local n = 1
    for fld in string.gmatch(params, "%s*([^|]+)%s*") do
        --printf("_bp: parse_params iter=%d, fld=%s", n, fld)
        rslt[n] = fld
        n = n + 1
    end
    return rslt
end

-- парсит строку вида "1|ph_door@free|2|ph_door@free"
function parse_data_1v(npc,s)
    local t = {}

    if is_string(s) then
        for dist, state in s:gmatch("(%d+)%|([^%|]+)" ) do
            dist = tonumber(dist)
            t[dist] = {
                dist  = dist,
                state = parse_condlist(state),
            }
        end
    end

    return t
end

-- парсит заданную строку рангов в заданную таблицу. возвращает название и значение максимального ранга
function parse_ranks( s, tbl )
    s = "0," .. s .. ",10000"

    local t = parse_names( s )
    local i = 2

    while i < #t do
        tbl[t[i]] = { tonumber(t[i-1]), tonumber(t[i+1]) }
        i = i + 2
    end

    return t[i-2],t[i-3]
end

-- На входе имеем пустой массив и строку вида "  +infop1  -infop2 +infop3 ... "
-- Заполнить массив: { "infop_name" = true/false }.
function parse_infop(tab, str)
    if is_string(str) then
        local i = 1
        for sign, infop_name in string.gmatch(str, "([%-%+%~%=%!])([^%-%+%~%=%!%s]+)") do
            -- парсим параметры функций
            local func, param = infop_name:match('^(.-)(%b())')
            if param then
                param = parse_func_params(param:match('%((.-)%)'))
                infop_name = func or infop_name
            end
            
            if sign == "+" or sign == "-" then
                tab[i] = { name = infop_name, r = (sign == "+") }
            elseif sign == "~" then
                tab[i] = { prob = tonumber(infop_name) }
            elseif sign == "=" or sign == "!" then
                tab[i] = { func = infop_name, r = (sign == "="), params = param}
            else
                error([[object [%s]: section '%s': field '%s': syntax error in switch condition]], npc, section, field)
            end
            i = i + 1
        end
    end
end

function parse_func_params(str)
    local t = {}
    local i = 0

    for v in str:gmatch('[^%s:]+') do
        i = i + 1
		if v:sub(1,1) == '_' and tonumber(v:sub(2,v:len())) then	--// (_100) = -100
			t[i] = -tonumber(v:sub(2,v:len()))
		else
			t[i] = tonumber(v) or v
		end
    end
    
    return t
end

--// Karlan: парсит шаблон вида module.function(param1, param2, param3), возвращает имя модуля, имя функции и таблицу с аргументами
function parse_func(s)
    --// Karlan->Nazgool: я ничего скоростнее придумать не смог :)
    local module, func, arg = s:match('^([^%.]+)%.*(.-)(%b())')
    if #func == 0 then
        func = module
        module = 'xr_actions'
    end
    arg = parse_names(arg:sub(2,-2))
    return module, func, arg
end

-- Распарсивает строку src вида:
-- {+infop1} section1 %-infop2%, {+infop3 -infop4} section2 ...
-- в таблицу:
-- {
--   1 = { chk = { 1 = {"infop1" = true} }, set = { 1 = {"infop2" = false } }, section = "section1" },
--   2 = { chk = { 1 = {"infop3" = true}, 2 = {"infop4" = false} }, set = {}, section = "section2" },
-- }
function parse_condlist(src)
    local t = {}
    local n = 0

    for w in string.gmatch(src,'[^,]+') do
        n = n + 1
        local ct = {}

        local before, infop, after = w:match('^(.-)(%b{})(.-)$')

        if infop then
            ct.chk = {}
            parse_infop(ct.chk, infop:match('{(.-)}'))
            w = before .. after
        end

        before, infop, after = w:match('^(.-)(%b%%)(.-)$')

        if infop then
            ct.set = {}
            parse_infop(ct.set, infop:match('%%(.-)%%'))
            w = before .. after
        end

        ct.section = w:match('%S+') or ''
		if ct.chk == nil and ct.set == nil then
			ct = ct.section
		end
        t[n] = ct
    end

    return t
end

-- Кэшируем конфиг файлы.
local _ini_cache = {}
function ini_cache(path)
	local ini = _ini_cache[path]
	if ini then
		return ini
	end
	_ini_cache[path] = ini_file(path)
	return _ini_cache[path]
end
