-- Functions for use in targets/*
local F = {}

-- To be accessed by scripts using target_lib
local M = setmetatable({}, { __index = F })

local funcs = setmetatable({}, {
	__index = function(_, k)
		return F[k] or _G[k]
	end,
})

local env = setmetatable({}, {
	__index = function(_, k) return os.getenv(k) end
})
F.env = env

assert(env.GLUON_SITEDIR)
assert(env.GLUON_TARGETSDIR)
assert(env.GLUON_RELEASE)
assert(env.GLUON_DEPRECATED)


M.site_code = assert(
	dofile('scripts/site_config.lua')('site.conf').site_code, 'site_code missing in site.conf'
)
M.target_packages = {}
M.configs = {}
M.devices = {}
M.images = {}


local default_options = {
	profile = false,
	factory = '-squashfs-factory',
	factory_ext = '.bin',
	sysupgrade = '-squashfs-sysupgrade',
	sysupgrade_ext = '.bin',
	extra_images = {},
	aliases = {},
	manifest_aliases = {},
	packages = {},
	class = 'standard',
	deprecated = false,
	broken = false,
}


local gluon_devices, unknown_devices = {}, {}
for dev in string.gmatch(env.GLUON_DEVICES or '', '%S+') do
	gluon_devices[dev] = true
	unknown_devices[dev] = true
end

function F.istrue(v)
	return (tonumber(v) or 0) > 0
end

local function want_device(dev, options)
	if options.broken and not F.istrue(env.BROKEN) then
		return false
	end
	if options.deprecated and env.GLUON_DEPRECATED == '0' then
		return false
	end

	if (env.GLUON_DEVICES or '') == '' then
		return true
	end

	unknown_devices[dev] = nil
	return gluon_devices[dev]
end

local full_deprecated = env.GLUON_DEPRECATED == 'full'


local function merge(a, b)
	local ret = {}
	for k, v in pairs(a) do
		ret[k] = v
	end
	for k, v in pairs(b or {}) do
		assert(ret[k] ~= nil, string.format("unknown option '%s'", k))
		ret[k] = v
	end
	return ret
end

-- Escapes a single argument to be used in a shell command
-- The argument is surrounded by single quotes, single quotes inside the
-- argument are replaced by '\''.
-- To allow using shell wildcards, zero bytes in the arguments are replaced
-- by unquoted asterisks.
function F.escape(s)
	s = string.gsub(s, "'", "'\\''")
	s = string.gsub(s, "%z", "'*'")
	return "'" .. s .. "'"
end

local function escape_command(command, raw)
	local ret = ''
	if not raw then
		ret = 'exec'
	end
	for _, arg in ipairs(command) do
		ret = ret .. ' ' .. F.escape(arg)
	end
	if raw then
		ret = ret .. ' ' .. raw
	end
	return ret
end

function F.exec_raw(command, may_fail)
	local ret = os.execute(command)
	assert((ret == 0) or may_fail)
	return ret
end

function F.exec(command, may_fail, raw)
	return F.exec_raw(escape_command(command, raw), may_fail)
end

function F.exec_capture_raw(command)
	local f = io.popen(command)
	assert(f)

	local data = f:read('*a')
	f:close()
	return data
end

function F.exec_capture(command, raw)
	return F.exec_capture_raw(escape_command(command, raw))
end


local image_mt = {
	__index = {
		dest_name = function(image, name, site, release)
			return env.GLUON_IMAGEDIR..'/'..image.subdir,
				'gluon-'..(site or M.site_code)..'-'..(release or env.GLUON_RELEASE)..'-'..name..image.out_suffix..image.extension
		end,
	},
}

local function add_image(image)
	local device = image.image
	M.images[device] = M.images[device] or {}
	table.insert(M.images[device], setmetatable(image, image_mt))
end


local function format_config(k, v)
	local format
	if type(v) == 'string' then
		format = '%s=%q'
	elseif v == true then
		format = '%s=y'
	elseif v == nil then
		format = '%s=m'
	elseif v == false then
		format = '# %s is not set'
	else
		format = '%s=%d'
	end
	return string.format(format, 'CONFIG_' .. k, v)
end

local config_mt = {
	__index = {
		format = function(config)
			return format_config(config.key, config.value)
		end,
	}
}

local function do_config(k, v, required)
	M.configs[k] = setmetatable({
		key = k,
		value = v,
		required = required,
	}, config_mt)
end

function F.try_config(k, v)
	do_config(k, v)
end

function F.config(k, v, message)
	if not message then
		message = string.format("unable to set '%s'", format_config(k, v))
	end
	do_config(k, v, message)
end


function F.packages(pkgs)
	for _, pkg in ipairs(pkgs) do
		table.insert(M.target_packages, pkg)
	end
end
M.packages = F.packages

local function as_table(v)
	if type(v) == 'table' then
		return v
	else
		return {v}
	end
end

function F.device(image, name, options)
	options = merge(default_options, options)

	if not want_device(image, options) then
		return
	end

	table.insert(M.devices, {
		image = image,
		name = name,
		options = options,
	})

	if options.sysupgrade then
		add_image {
			image = image,
			name = name,
			subdir = 'sysupgrade',
			in_suffix = options.sysupgrade,
			out_suffix = '-sysupgrade',
			extension = options.sysupgrade_ext,
			aliases = options.aliases,
			manifest_aliases = options.manifest_aliases,
		}
	end

	if options.deprecated and not full_deprecated then
		return
	end

	if options.factory then
		for _, ext in ipairs(as_table(options.factory_ext)) do
			add_image {
				image = image,
				name = name,
				subdir = 'factory',
				in_suffix = options.factory,
				out_suffix = '',
				extension = ext,
				aliases = options.aliases,
			}
		end
	end
	for _, extra_image in ipairs(options.extra_images) do
		add_image {
			image = image,
			name = name,
			subdir = 'other',
			in_suffix = extra_image[1],
			out_suffix = extra_image[2],
			extension = extra_image[3],
			aliases = options.aliases,
		}
	end
end

function F.factory_image(image, name, ext, options)
	options = merge(default_options, options)

	if not want_device(image, options) then
		return
	end

	if options.deprecated and not full_deprecated then
		return
	end

	add_image {
		image = image,
		name = name,
		subdir = 'factory',
		in_suffix = '',
		out_suffix = '',
		extension = ext,
		aliases = options.aliases,
	}
end

function F.sysupgrade_image(image, name, ext, options)
	options = merge(default_options, options)

	if not want_device(image, options) then
		return
	end

	add_image {
		image = image,
		name = name,
		subdir = 'sysupgrade',
		in_suffix = '',
		out_suffix = '-sysupgrade',
		extension = ext,
		aliases = options.aliases,
		manifest_aliases = options.manifest_aliases,
	}
end

function F.defaults(options)
	default_options = merge(default_options, options)
end

function F.include(name)
	local f = assert(loadfile(env.GLUON_TARGETSDIR .. '/' .. name))
	setfenv(f, funcs)
	return f()
end


function M.check_devices()
	local device_list = {}
	for device in pairs(unknown_devices) do
		table.insert(device_list, device)
	end
	if #device_list ~= 0 then
		table.sort(device_list)
		io.stderr:write('Error: unknown devices given: ', table.concat(device_list, ' '), '\n')
		os.exit(1)
	end
end


return M
