-- LUALOCALS < ---------------------------------------------------------
local ch_draconis, ipairs, math, minetest, pairs, type
    = ch_draconis, ipairs, math, minetest, pairs, type
local math_floor
    = math.floor
-- LUALOCALS > ---------------------------------------------------------

local modname = minetest.get_current_modname()

-- Pre-create all the "white flash" tiles.
local function flashtile(i)
	return "[combine:1x1^[noalpha^[invert:rgb^[opacity:" .. i
end
do
	-- If the client doesn't have the white flash tiles pre-loaded,
	-- it has to draw them dynamically, then upload them to the GPU,
	-- which can cause a noticeable framerate drop on some systems.
	-- To work around this, register each as a node texture, so the
	-- game should preload them during the "initializing nodes" phase
	-- and have them ready to go as soon as you're in the game. This
	-- should use a minimal number of node IDs, and without an
	-- inventory we don't have to worry much about "creative mode".
	local tiles = {}
	local idx = 0
	local function flush()
		idx = idx + 1
		minetest.register_node(modname .. ":preload" .. idx, {
				tiles = tiles
			})
		tiles = {}
	end
	for i = 0, 255 do
		tiles[#tiles + 1] = flashtile(i)
		if #tiles >= 6 then flush() end
	end
	if #tiles > 0 then flush() end
end

local time = 0

local function gray(x) return {r = x, g = x, b = x, a = 255} end

-- Standard sky that the player starts with.
local basesky = {
	set_sky = {
		type = "regular",
		clouds = true,
		sky_color = {
			day_sky = gray(0x99),
			day_horizon = gray(0xaa),
			dawn_sky = gray(0xcc),
			dawn_horizon = gray(0xdd),
			night_sky = gray(0x22),
			night_horizon = gray(0x33),
			indoors = gray(0x44),
			fog_sun_tint = gray(0x33),
			fog_moon_tint = gray(0x2a),
			fog_tint_type = "custom"
		}
	},
	set_sun = {
		visible = true,
		texture = "ch_sky_sun.png",
		sunrise_visible = false,
	},
	set_moon = {
		visible = true,
		texture = "ch_sky_moon.png",
	},
	set_stars = {
		visible = true,
		count = 500,
		star_color = gray(0xcc),
	},
	set_clouds = {
		density = 0.4,
		color = gray(0xff),
		ambient = gray(0x11),
	}
}

local function deepcopy(t)
	if type(t) ~= "table" then return t end
	local u = {}
	for k, v in pairs(t) do u[k] = deepcopy(v) end
	return u
end

local function deepcompare(a, b)
	if type(a) ~= "table" or type(b) ~= "table" then
		return a == b
	end
	for k, v in pairs(a) do
		if not deepcompare(v, b[k]) then return end
	end
	for k in pairs(b) do
		if a[k] == nil then return end
	end
	return true
end

-- Colors for colorizing the sky during each boss fight.
local dragon_sky = {
	-- Marundir uses a deep blue on black.
	["ch_draconis:blue_dragon"] = {
		fg = "#000040",
		bg = "#000000",
		moon = "#000080"
	},
	-- Tyrirol uses a mix of clashing purples/plums/violets.
	["ch_draconis:purple_dragon"] = {
		fg = "#400030",
		bg = "#080010",
		moon = "#600060"
	},
	-- Nowal has a reversed/solarized sky with deep black.
	["ch_draconis:black_dragon"] = {
		fg = "#000000",
		bg = "#404040",
		moon = "#606060"
	},
}

local function checkplayer(player, data)
	local skydata = nil

	-- If a (recognized) dragon is present, override the sky.
	-- Hide unnecessary things like sun/clouds/stars, keep
	-- (but stylize) the moon as it acts like a countdown
	-- timer for the player.
	local dragon = ch_draconis.dragon and ch_draconis.dragon.name
	local dcolor = dragon and dragon_sky[dragon]
	if dcolor then
		local base = "[combine:256x256^[noalpha"
		local bg = base .. "^[colorize:" .. dcolor.bg
		local fg = base .. "^[colorize:" .. dcolor.fg
		local side = fg .. "^(" .. bg .. "^[mask:ch_sky_mask.png)"
		local top = bg .. "^(" .. fg .. "^[mask:ch_sky_swirl.png)"
		skydata = {
			set_sky = {
				type = "skybox",
				textures = {
					top, bg, side, side, side, side
				},
				clouds = false
			},
			set_stars = {visible = false},
			set_sun = {visible = false},
			set_moon = {
				visible = true,
				texture = "ch_sky_dragon_moon.png^[multiply:" .. dcolor.moon,
			}
		}
	else
		local maxcol = player:get_meta():get_int("max_col")
		if maxcol <= 3 then
			skydata = basesky
		else
			skydata = deepcopy(basesky)
			if maxcol >= 4 then
				skydata.set_sky.sky_color.day_sky = "#61b5f5"
				skydata.set_sky.sky_color.day_horizon = "#90d3f6"
				skydata.set_sky.sky_color.dawn_sky = "#b4bafa"
				skydata.set_sky.sky_color.dawn_horizon = "#bac1f0"

			end
			if maxcol >= 5 then
				skydata.set_sky.sky_color.night_sky = "#9525ff"
				skydata.set_sky.sky_color.night_horizon = "#af5bff"
				skydata.set_stars.count = 2000
			end
			if maxcol >= 6 then
				skydata.set_sun.texture = ""
				skydata.set_sun.sunrise_visible = true
				skydata.set_moon.texture = ""
				skydata.set_clouds.density = 0.3
			end
		end
	end

	-- Change to/from dragon causes screen flash effect.
	data.flashtime = data.flashtime or 0
	if not deepcompare(dcolor, data.dcolor) then
		data.dcolor = dcolor
		data.flashtime = time
	end
	local flashqty = math_floor(255 + (data.flashtime - time) * 128)
	if flashqty < 0 then flashqty = 0 end
	if flashqty ~= data.flashqty then
		data.flashqty = flashqty
		local flash = flashtile(flashqty)
		if data.hudid then
			player:hud_change(data.hudid, "text", flash)
		else
			data.hudid = player:hud_add({
					hud_elem_type = "image",
					position = {x = 0.5, y = 0.5},
					text = flash,
					direction = 0,
					scale = {x = -100, y = -100},
					offset = {x = 0, y = 0}
				})
		end
	end

	-- Apply all calculated sky effects, if they're different
	-- from previous.
	if not deepcompare(skydata, data.sky) then
		data.sky = skydata
		for k, v in pairs(skydata) do player[k](player, v) end
	end
end

do
	local cache = {}
	local function cacheget(player)
		local pname = player:get_player_name()
		local found = cache[pname]
		if found then return found end
		found = {pname = pname}
		cache[pname] = found
		return found
	end
	minetest.register_on_joinplayer(function(player)
			checkplayer(player, cacheget(player))
		end)
	minetest.register_on_leaveplayer(function(player)
			cache[player:get_player_name()] = nil
		end)
	minetest.register_globalstep(function(dtime)
			time = time + dtime
			for _, player in ipairs(minetest.get_connected_players()) do
				checkplayer(player, cacheget(player))
			end
		end)
end
