local log  = require "resources.functions.log".route_to_bridge

local pcre_match

if freeswitch then
	--! @todo find better way to extract captures
	api = api or freeswitch.API()
	local unpack = unpack or table.unpack
	function pcre_match(str, pat)
		local a = str .. "|/" .. pat .."/"
		if api:execute("regex", a) == 'false' then return end
		local t = {}
		for i = 1, 5 do
			t[i] = api:execute("regex", a .. '|$' .. i)
		end
		return unpack(t)
	end
else
	local pcre = require "rex_pcre"
	function pcre_match(str, pat)
		return pcre.match(str, pat)
	end
end

local function pcre_self_test()
	local a, b, c
	a,b,c = pcre_match('abcd', '(\\d{3})(\\d{3})')
	assert(a == nil)

	a,b,c = pcre_match('123456', '(\\d{3})(\\d{3})')
	assert(a == '123', a)
	assert(b == '456', b)
end

local select_routes_sql = [[
select *
from v_dialplans
where (domain_uuid = '%s' or domain_uuid is null)
  and app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3'
  and dialplan_enabled = 'true'
order by dialplan_order asc
]]

local select_extensions_sql = [[
select * from v_dialplan_details 
where dialplan_uuid = '%s'
order by dialplan_detail_group asc, dialplan_detail_order asc
]]

local function append(t, v)
	t[#t + 1] = v
	return t
end

local function order_keys(t)
	local o = {}
	for k in pairs(t) do append(o, k) end
	table.sort(o)
	local i = 0
	return function(o)
		i = i + 1
		return o[i], t[o[i]]
	end, o
end

local function check_conditions(group, fields)
	local matches, pass, last, break_on

	for n, condition in ipairs(group.conditions) do
		last = (n == #group.conditions)

		local value = fields[condition.type]
		if (not value) and (condition.type ~= '') then -- skip unkonw fields
			log.errf('Unsupportded condition: %s', condition.type)
			matches, pass = {}, false
		else
			if condition.type == '' then
				matches, pass = {}, true
			else
				matches = {pcre_match(value, condition.data)}
				pass = #matches > 0
			end
			log.debugf('%s condition %s(%s) to `%s`', pass and 'PASS' or 'FAIL', condition.type, condition.data, value or '<NONE>')
		end

		break_on = condition.break_on
		if break_on == 'always'  then break end
		if break_on ~= 'never' then
			if pass and break_on == 'on-true' then break end
			if not pass and (break_on == 'on-false' or break_on == '') then break end
		end

		break_on = nil
	end

	-- we shuld execute action/anti-action only if we check ALL conditions
	local act
	if last then act = pass and 'action' or 'anti-action' end

	-- we shuld break
	return act, not not break_on, matches
end

local function apply_match(s, match)
	return string.gsub(s, "%$(%d)", function(i)
		return match[tonumber(i)] or ''
	end)
end

local function group_to_bridge(actions, group, fields)
	local action, do_break, matches = check_conditions(group, fields)
	if action then
		local t = (action == 'action') and group.actions or group.anti_actions
		for _, element in ipairs(t) do
			local value = element.data
			if element.type == 'export' and string.sub(value, 1, 8) == 'nolocal:' then
				value = string.sub(value, 9)
			end

			-- we only support action/export
			if element.type == 'export' or element.type == 'set' then
				value = apply_match(value, matches)
				append(actions, value)
			end

			if element.type == 'bridge' then
				actions.bridge = apply_match(value, matches)
				break
			end
		end
	end

	return do_break
end

local function extension_to_bridge(extension, actions, fields)
	for _, group in order_keys(extension) do
		local do_break = group_to_bridge(actions, group, fields)
		if do_break then break end
	end
end

local function self_test()
	pcre_self_test()

	local test_conditions = {
		{
			{conditions={
				{type='destination_number', data='100', break_on=''};
			}};
			{destination_number = 100};
			{'action', false};
		};

		{
			{conditions={
				{type='destination_number', data='100', break_on='on-true'};
			}};
			{destination_number = 100};
			{'action', true};
		};

		{
			{conditions={
				{type='destination_number', data='101', break_on=''};
			}};
			{destination_number = 100};
			{'anti-action', true};
		};

		{
			{conditions={
				{type='destination_number', data='100', break_on=''};
				{type='destination_number', data='101', break_on=''};
				{type='destination_number', data='102', break_on=''};
			}};
			{destination_number = 100};
			{nil, true};
		};

		{
			{conditions={
				{type='destination_number', data='100', break_on='never'};
				{type='destination_number', data='101', break_on='never'};
				{type='destination_number', data='102', break_on='never'};
			}};
			{destination_number = 102};
			{'action', false};
		};

		{
			{conditions={
				{type='destination_number', data='100', break_on='never'};
				{type='destination_number', data='101', break_on='never'};
				{type='destination_number', data='102', break_on='never'};
			}};
			{destination_number = 103};
			{'anti-action', false};
		};

		{
			{conditions={
				{type='destination_number', data='100', break_on=''};
				{type='destination_number', data='101', break_on=''};
				{type='destination_number', data='102', break_on=''};
			}};
			{destination_number = 102};
			{nil, true};
		};

		{
			{conditions={
				{type='', data='', break_on=''};
			}};
			{};
			{'action', false};
		};

		{
			{conditions={
				{type='caller_id_number', data='123456', break_on=''};
			}};
			{};
			{'anti-action', true};
		};

	}

	for i, test in ipairs(test_conditions) do
		io.write('Test conditions #' .. i)
		local group, fields, result = test[1], test[2], test[3]
		local action, do_break, matches = check_conditions(group, fields)
		assert(action   == result[1], tostring(action))
		assert(do_break == result[2])
		io.write(' - ok\n')
	end
end

local function outbound_route_to_bridge(dbh, domain_uuid, fields)
	local actions, dial_string = {}

	dbh:query(select_routes_sql:format(domain_uuid), function(route)
		local extension = {}
		dbh:query(select_extensions_sql:format(route.dialplan_uuid), function(ext)
			local group_no = tonumber(ext.dialplan_detail_group)
			local tag      = ext.dialplan_detail_tag
			local element = {
				type      =          ext.dialplan_detail_type;
				data      =          ext.dialplan_detail_data;
				break_on  =          ext.dialplan_detail_break;
				inline    =          ext.dialplan_detail_inline;
			}

			local group = extension[ group_no ] or {
				conditions   = {};
				actions      = {};
				anti_actions = {};
			}
			extension[ group_no ] = group

			if tag == 'condition'   then append(group.conditions,   element) end
			if tag == 'action'      then append(group.actions,      element) end
			if tag == 'anti-action' then append(group.anti_actions, element) end
		end)

		local n = #actions

		extension_to_bridge(extension, actions, fields)

		if actions.bridge or (n > #actions and route.dialplan_continue == 'false') then
			return 1
		end
	end)

	if actions.bridge then return actions end
end

return setmetatable({
	__self_test = self_test;
}, {__call = function(_, ...)
	return outbound_route_to_bridge(...)
end})