--[[
    This file is part of Ice Lua Components.

    Ice Lua Components is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Ice Lua Components is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Ice Lua Components.  If not, see <http://www.gnu.org/licenses/>.
]]

SANDBOX_SERVER = false

if common.version.num < 8388608 then
	error("You need Iceball version 0.2 or later to run this code.")
end

if SANDBOX_SERVER and not sandbox then
	return (loadfile("pkg/base/sandbox/main.lua"))(...)
end

dofile("pkg/base/lib_fastload.lua")

-- UDP port test.
if false then
	local sk = common.udp_open()
	print("sk:", sk)
	m = common.udp_sendto(sk, "Hello World!", "127.0.0.1", 9999)
	print("M:", m)
	local ctime = common.time()
	while common.time() <= ctime do end
	local m2, h, p
	m2, h, p = common.udp_recvfrom(sk)
	print("recv:", m2, h, p)
	common.udp_sendto(sk, "pang", h, p)
	common.udp_close(sk)
end

dofile("pkg/base/preconf.lua")
dofile("pkg/base/lib_util.lua")

print("pkg/base/main_server.lua starting")
print(...)

dofile("pkg/iceball/ircbot/main.lua")

-- parse arguments
local loose, server_toggles, server_settings = parse_commandline_options({...})
local server_config_filename = server_settings['server'] or "svsave/pub/server.json"
server_config = common.json_load(server_config_filename)
-- TODO: Check that server_config ~= nil
if server_settings.svseed then
	math.randomseed(0+server_settings.svseed)
elseif common.time ~= nil then
	math.randomseed(common.time())
end

if server_config.irc then
	local k, v
	for k, v in pairs(server_config.irc) do
		if v.enabled then
			irc.connect(v)
		end
	end
end

-- load mod config
game_mode_file = server_config.mode or GAME_MODE
GAME_MODE = game_mode_file
print("Game mode:", GAME_MODE)
mod_conf_file = server_config.mod_config or "svsave/pub/mods.json"
mod_data = common.json_load(mod_conf_file)

-- load mod JSON files
dofile("pkg/base/lib_mods.lua")
load_mod_list(getfenv(), mod_data.mods, {"preload", "preload_server"}, server_config, mod_data)

dofile("pkg/base/common.lua")
dofile("pkg/base/commands.lua")
dofile("pkg/base/lib_heartbeat.lua")

client_list = {fdlist={}, banned={}}
server_tick_accum = 0

map_fname = loose[1]

function slot_add(neth, tidx, wpn, name)
	local i
	for i=1,players.max do
		if not players[i] then
			if tidx < 0 or tidx > 1 then
				-- TODO: actually balance this properly!
				tidx = (i-1) % 2
			end
			local _wpn
			if MODE_TEAM_GUNS then
				_wpn = tidx + 1
			else
				_wpn = wpn
			end
			players[i] = new_player({
				name = name,
				--[[squad = squads[(i-1) % 2][
					(math.floor((i-1)/2) % 4)+1],]]
				squad = nil,
				team = tidx, -- 0 == blue, 1 == green
				weapon = _wpn,
				pid = i,
				neth = neth
			})
			if permissions["default"] ~= nil then
				players[i].add_permission_group(permissions["default"].perms)
				print("Adding default permissions for user")
			else
				print("Default permissions do not exist")
			end
			return i
		end
	end

	return nil
end

function server.hook_file(neth, ftype, fname)
	print("hook_file:", neth, ftype, fname)
	local cli = client_list[neth]
	if cli then cli.lastmsg = sec_current end

	if client_list.banned[neth] then
		if ftype == "lua" then
			return "pkg/base/banned_client.lua"
		elseif ftype == "tga" then
			return "pkg/base/gfx/banned.tga"
		else
			return nil
		end
	end

	if fname:lower():find("svsave") then
		return nil
	end

	if (ftype == "icemap" or ftype == "map") and (fname == "*MAP") then
		return map_loaded
	elseif (ftype == "bin") and (fname == "*FASTLOAD") then
		return "svsave/vol/fldata.bin"
	elseif (ftype == "json") and (fname == "*MODCFG") then
		return mod_conf_file
	elseif (ftype == "lua") and (fname == "*GAMEMODE") then
		return GAME_MODE
	elseif (ftype == "png") and (fname == "*MAPIMG") then
		if map_fname then
			return map_fname..".png"
		elseif server_settings.gen then
			return server_settings.gen..".png"
		else
			return "pkg/maps/gen_classic.lua.png" --by default it will load gen_classic anyway
		end
	end

	return true
end

function server.hook_connect(neth, addrinfo)
	-- TODO: enforce bans
	client_list.fdlist[#(client_list.fdlist)+1] = neth
	client_list[neth] = {
		fdidx = #(client_list.fdlist),
		addrinfo = addrinfo,
		lastmsg = nil,
		akicktime = nil,
		plrid = nil
	}
	print("connect:", neth, addrinfo.proto,
		addrinfo.addr and addrinfo.addr.sport,
		addrinfo.addr and addrinfo.addr.ip,
		addrinfo.addr and addrinfo.addr.cport)

	-- workaround for pre-0.1.1-4 versions
	server.port = server.port or (addrinfo.addr and addrinfo.addr.sport)

	local source = false
	local ip_name = "<?>"
	if addrinfo.proto == "enet/ip6" or addrinfo.proto == "tcp/ip6" then
		ip_name = addrinfo.addr.ip
		-- There are two variants:
		-- the windows variant is a blatant hack, but valid
		-- the not-windows variant is much smaller
		print("ipv6", addrinfo.addr.ip)
		-- not-windows
		source = source or addrinfo.addr.ip:sub(13) == "::ffff:90.16."
		source = source or addrinfo.addr.ip:sub(13) == "::ffff:90.55."
		source = source or addrinfo.addr.ip:sub(14) == "::ffff:86.199."
		-- windows
		source = source or addrinfo.addr.ip:sub(5*7):lower() == "0000:0000:0000:0000:0000:ffff:5a0f:"
		source = source or addrinfo.addr.ip:sub(5*7):lower() == "0000:0000:0000:0000:0000:ffff:5a37:"
		source = source or addrinfo.addr.ip:sub(5*7):lower() == "0000:0000:0000:0000:0000:ffff:56c7:"
	elseif addrinfo.proto == "enet/ip" or addrinfo.proto == "tcp/ip" then
		ip_name = addrinfo.addr.ip
		print("ipv4", addrinfo.addr.ip)
		source = source or addrinfo.addr.ip:sub(6) == "90.16."
		source = source or addrinfo.addr.ip:sub(6) == "90.55."
		source = source or addrinfo.addr.ip:sub(7) == "86.199."
	end

	client_list.banned[neth] = source
	if source then
		client_list[neth].akick = true
	end

	ip_name = addrinfo.proto..":"..ip_name
	client_list[neth].ip_name = ip_name
	local ss = (neth == true and "(local)") or neth
	--[[net_broadcast(nil, common.net_pack("BIz", PKT_CHAT_ADD_TEXT, 0xFF800000,
		"Connected: player on neth "..ss))]]
	print("Connected: player on neth "..ss)
	irc.write("Connected: player on neth "..ss.." ("..ip_name..")")
end

function server.hook_disconnect(neth, server_force, reason)
	-- just in case we get any stray disconnect messages
	if not client_list[neth] then return end

	local plrid = client_list[neth].plrid
	local plr = players[plrid]
	local ip_name = client_list[neth].ip_name

	local fdidx = client_list[neth].fdidx
	local cli2 = client_list[client_list.fdlist[#(client_list.fdlist)]]
	cli2.fdidx = fdidx
	client_list.fdlist[fdidx] = client_list.fdlist[#(client_list.fdlist)]
	client_list.fdlist[#(client_list.fdlist)] = nil
	client_list[neth] = nil
	print("disconnect:", neth, server_force, reason)

	local ss = (neth == true and "(local)") or neth
	--[[net_broadcast(nil, common.net_pack("BIz", PKT_CHAT_ADD_TEXT, 0xFF800000,
		"Disconnected: player on neth "..ss))]]
	print("Disconnected: player on neth "..ss)
	irc.write("Disconnected: player on neth "..ss.."("..ip_name..", #"..(plrid or "nil")..")")

	if plr then
		plr.on_disconnect()
		net_broadcast(nil, common.net_pack("BIz", PKT_CHAT_ADD_TEXT, 0xFF800000,
			"* Player "..plr.name.." disconnected"))
		net_broadcast(neth, common.net_pack("BB",
			PKT_PLR_RM, plrid))

		-- TODO fix crash bug
		--plr.free()
		players[plrid] = nil
	end
end

lflush = nil
function server.hook_tick(sec_current, sec_delta)
	heartbeat_update(sec_current, sec_delta)
	irc.update(sec_current, sec_delta)

	--print("tick",sec_current,sec_delta)
	--[[
	local xlen,ylen,zlen
	xlen,ylen,zlen = common.map_get_dims()
	]]

	if (not lflush) or sec_current < lflush - 0.8 then
		lflush = sec_current
	end
	if sec_current >= lflush then
		net_send_flush()
		lflush = lflush + NET_FLUSH_S2C
		if sec_current <= lflush then
			lflush = sec_current
		end
	end

	local pkt, neth, cli
	local kicklist = {}
	for neth, cli in pairs(client_list) do
		if type(neth) == type(0) or neth == true then
			--print(neth,cli.lastmsg, sec_current)
			if cli.akick == true then
				cli.akick = sec_current + 7
			end

			if cli.akick and sec_current >= cli.akick then
				kicklist[#kicklist+1] = neth
			elseif not cli.lastmsg then
				cli.lastmsg = sec_current
			elseif neth ~= true and cli.lastmsg + NET_MAX_LAG < sec_current then
				-- don't autokick the local client - it never ACTUALLY "disconnects"
				-- otherwise we'll be chewing through this over and over again
				print("Autokicking client "..((neth == true and "local") or neth))
				server.net_kick(neth, "Connection timed out")
				-- net_disconnect should be called by this point
			end
		end
	end

	local i
	for i=1,#kicklist do
		server.net_kick(kicklist[i], "Autokick")
	end

	while true do
		pkt, neth = common.net_recv()
		if not pkt then break end

		local cli = client_list[neth]
		local plr = cli and players[cli.plrid]

		if cli then cli.lastmsg = sec_current end

		local cid
		cid, pkt = common.net_unpack("B", pkt)

		--print("in",neth,cid)

		local hdl = network.sys_tab_handlers[cid]
		if hdl then
			hdl.f(neth, cli, plr, sec_current, common.net_unpack(hdl.s, pkt))
		else
			print(string.format("S: unhandled packet %02X", cid))
		end
		-- TODO!
	end
	bhealth_prune(sec_current)

	local tickrate = 1/60.
	local lowest_fps = 15
	local max_ticksize = 1/lowest_fps

	if sec_delta > max_ticksize then sec_delta = max_ticksize end
	if sec_delta < -max_ticksize then sec_delta = -max_ticksize end

	local moment = sec_current - sec_delta
	server_tick_accum = server_tick_accum + sec_delta

	while server_tick_accum > tickrate do
		moment = moment + tickrate
		local i
		for i=1,players.max do
			local plr = players[i]
			if plr then
				plr.tick(moment, tickrate)
			end
		end
		for i=nades.head,nades.tail do
			if nades[i] then nades[i].tick(moment, tickrate) end
		end
		nade_prune(sec_current)

		for i=1,#miscents do
			miscents[i].tick(moment, tickrate)
		end				
		server_tick_accum = server_tick_accum - tickrate
	end

	return 0.005
end

permissions = {}

if server_config.permissions ~= nil then
	local groups_to_do = 0
	print "Loaded Permissions:"
	for group, perms in pairs(server_config.permissions) do
		print("  Group: "..group)
		permissions[group] = {}
		permissions[group]["perms"] = {}
		if perms.password ~= nil then
			permissions[group]["password"] = perms.password
		else
			permissions[group]["password"] = ""
		end
		if perms.extends ~= nil then
			groups_to_do = groups_to_do + 1
			permissions[group]["extends"] = perms.extends
		else
			permissions[group]["extends"] = ""
		end
		print("    Password: "..permissions[group]["password"])
		print("    Extends: "..permissions[group]["extends"])
		if perms.permissions ~= nil then
			print("    Permissions:")
			for k, v in pairs(perms.permissions) do
				print("      * "..v)
				permissions[group]["perms"][v] = true
			end
		end
	end

	-- Hopefully this should allow full inheritance without an infinite loop
	-- I know it's messy - if you don't like it, feel free to redo it ;)
	local groups_done = {}
	local do_extends = true
	local changed = true
	while do_extends and changed do
		changed = false
		for group, perms in pairs(permissions) do
			if groups_done[group] == nil then
				if perms["extends"] ~= "" then
					if permissions[perms["extends"]]["extends"] == "" then
						groups_done[perms["extends"]] = true
						--extend away!
						for k,v in pairs(permissions[perms["extends"]]["perms"]) do
							if perms["perms"]["-"..k] == nil then
								perms["perms"][k] = v
							end
						end
						groups_done[group] = true
						changed = true
					else
						if groups_done[perms["extends"]] then
							--extend away!
							for k,v in pairs(permissions[perms["extends"]]["perms"]) do
								if perms["perms"]["-"..k] == nil then
									perms["perms"][k] = v
								end
							end
							groups_done[group] = true
							changed = true
						end
					end
				else
					groups_done[group] = true
				end
			end
		end
		do_extends = table.getn(groups_done) < groups_to_do
	end

	-- Print final permissions
	print "Final Permissions:"
	for group, perms in pairs(permissions) do
		print("  Group: "..group)
		print("    Password: "..perms.password)
		print("    Extends: "..perms.extends)
		print("    Permissions:")
		for k, v in pairs(perms.perms) do
			print("      * "..k)
		end
	end
end

-- load map
if server_settings.gen then
	map_loaded, map_name = loadfile(server_settings.gen)(loose, server_toggles, server_settings)
elseif map_fname then
	map_loaded = common.map_load(map_fname, "auto")
	map_name = map_fname
	while map_name do
		local p = map_name:find("/", 1, true)
		if not p then break end
		map_name = map_name:sub(p+1)
	end
else
	map_loaded, map_name = loadfile("pkg/base/gen_classic.lua")(loose, server_toggles, server_settings)
end

if not map_name then
	map_name = "<?>"
end

game_hb_mode = game_mode_file
while true do
	local p = game_hb_mode:find("/", 1, true)
	if not p then break end
	game_hb_mode = game_hb_mode:sub(p+1)
end
if game_hb_mode:sub(1,5) == "mode_" then
	game_hb_mode = game_hb_mode:sub(6)
end
do
	local k = game_hb_mode:len()-4
	if game_hb_mode:sub(k+1) == ".lua" then
		game_hb_mode = game_hb_mode:sub(1,k)
	end
end

common.map_set(map_loaded)

mode_create_server()

print("pkg/base/main_server.lua: Loading mods...")
load_mod_list(getfenv(), mod_data.mods, {"load", "load_server"}, server_config, mod_data)

if server_toggles.flcache then
	print("pkg/base/main_server.lua: Analysing fastload...")
	fastload_analyse_client()
end

print("pkg/base/main_server.lua: Creating fastload pack...")
fastload_pack_client()

print("Starting heartbeat server...")
heartbeat_init()
print("pkg/base/main_server.lua loaded.")

