-- Skins update script

local S = minetest.get_translator("skinsdb")
local _ID_ = "Lua Skins Updater"

local internal = {}
internal.errors = {}

-- Binary downloads are required
if not core.features.httpfetch_binary_data then
	internal.errors[#internal.errors + 1] =
		"Feature 'httpfetch_binary_data' is missing. Update Minetest."
end

-- Insecure environment for saving textures and meta
local ie, http = skins.ie, skins.http
if not ie or not http then
	internal.errors[#internal.errors + 1] = "Insecure environment is required. " ..
		"Please add skinsdb to `secure.trusted_mods` in minetest.conf"
end

minetest.register_chatcommand("skinsdb_download_skins", {
	params = S("<skindb start page> <amount of pages>"),
	description = S("Downloads the specified range of skins and shuts down the server"),
	privs = {server=true},
	func = function(name, param)
		if #internal.errors > 0 then
			return false, "Cannot run " .. _ID_ .. ":\n\t" ..
				table.concat(internal.errors, "\n\t")
		end

		local parts = string.split(param, " ")
		local start = tonumber(parts[1])
		local len = tonumber(parts[2])
		if not (start and len and len > 0) then
			return false, "Invalid page number or amount of pages"
		end

		internal.get_pages_count(internal.fetch_function, start, len)
		return true, "Started downloading..."
	end,
})


if #internal.errors > 0 then
	return -- Nonsense to load something that's not working
end

-- http://minetest.fensta.bplaced.net/api/apidoku.md
local root_url = "http://skinsdb.terraqueststudios.net"
local page_url = root_url .. "/api/v1/content?client=mod&page=%i" -- [1] = Page#

local download_path = skins.modpath
local meta_path = download_path .. "/meta/"
local skins_path = download_path .. "/textures/"

-- Fancy debug wrapper to download an URL
local function fetch_url(url, callback)
	http.fetch({
		url = url,
		user_agent = _ID_
	}, function(result)
		if result.succeeded then
			if result.code ~= 200 then
				core.log("warning", ("%s: STATUS=%i URL=%s"):format(
					_ID_, result.code, url))
			end
			return callback(result.data)
		end
		core.log("warning", ("%s: Failed to download URL=%s"):format(
			_ID_, url))
	end)
end

-- Insecure workaround since meta/ and textures/ cannot be written to
local function unsafe_file_write(path, contents)
	local f = ie.io.open(path, "wb")
	f:write(contents)
	f:close()
end

-- Takes a valid skin table from the Skins Database and saves it
local function save_single_skin(skin)
	local meta = {
		skin.name,
		skin.author,
		skin.license
	}

	local name = "character." .. skin.id
	do
		local legacy_name = "character_" .. skin.id
		local fh = ie.io.open(skins_path .. legacy_name .. ".png", "r")
		-- Use the old name if either the texture ...
		if fh then
			name = legacy_name
		end
	end

	-- core.safe_file_write does not work here
	unsafe_file_write(
		meta_path .. name .. ".txt",
		table.concat(meta, "\n")
	)

	unsafe_file_write(
		skins_path .. name .. ".png",
		core.decode_base64(skin.img)
	)
	core.log("action", ("%s: Completed skin %s"):format(_ID_, name))
end

-- Get total pages since it'll just return the last page all over again
internal.get_pages_count = function(callback, ...)
	local vars = {...}
	fetch_url(page_url:format(1) .. "&per_page=1", function(data)
		local list = core.parse_json(data)
		-- "per_page" defaults to 20 if left away (docs say something else, though)
		callback(math.ceil(list.pages / 20), unpack(vars))
	end)
end

-- Function to fetch a range of pages
internal.fetch_function = function(pages_total, start_page, len)
	start_page = math.max(start_page, 1)
	local end_page = math.min(start_page + len - 1, pages_total)

	for page_n = start_page, end_page do
		local page_cpy = page_n
		fetch_url(page_url:format(page_n), function(data)
			core.log("action", ("%s: Page %i"):format(_ID_, page_cpy))

			local list = core.parse_json(data)
			for i, skin in pairs(list.skins) do
				assert(skin.type == "image/png")
				assert(skin.id ~= "")

				if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb
					save_single_skin(skin)
				end
			end

			if page_cpy == end_page then
				local log = _ID_ .. " finished downloading all skins. " ..
					"Shutting down server to reload media cache"
				core.log("action", log)
				core.request_shutdown(log, true, 3 --[[give some time for pending requests]])
			end
		end)
	end
end