local CATEGORY_NAME = "Utility"
local CATEGORY_NAME2 = "Information"

------------------------------ Who ------------------------------
function ulx.who( calling_ply, steamid )
	if not steamid or steamid == "" or steamid == "steamid" then
		if IsValid( calling_ply ) then
			calling_ply:ChatPrint("Check your console.")
		end

		ULib.console( calling_ply, "ID Name                            Group" )

		local players = player.GetAll()
		for _, pl in ipairs( players ) do
			local id = tostring( pl:UserID() )
			local name = utf8.force( pl:Name() )
			local text = string.format( "%i%s %s%s ", id, string.rep( " ", 2 - id:len() ), name, string.rep( " ", 31 - utf8.len( name ) ) )

			text = text .. pl:GetUserGroup()

			ULib.console( calling_ply, text )
		end
	else
		data = ULib.ucl.getUserInfoFromID( steamid )

		if not data then
			if IsValid( calling_ply ) then
				calling_ply:ChatPrint( "No information for provided id exists." )
			else
				ULib.console( calling_ply, "No information for provided id exists." )
			end
		else
			if IsValid( calling_ply ) then
				calling_ply:ChatPrint( "ID: " .. steamid )
				calling_ply:ChatPrint( "Name: " .. ( data.name or "unknown" ) )
				calling_ply:ChatPrint( "Group: " .. ( data.group or "user" ) )
			else
				ULib.console( calling_ply, "ID: " .. steamid )
				ULib.console( calling_ply, "Name: " .. ( data.name or "unknown" ) )
				ULib.console( calling_ply, "Group: " .. ( data.group or "user" ) )
			end
		end

	end
end
local who = ulx.command( CATEGORY_NAME2, "ulx who", ulx.who, "!who" )
who:addParam{type=ULib.cmds.StringArg, hint="steamid", ULib.cmds.optional}
who:defaultAccess( ULib.ACCESS_ALL )
who:help( "See information about currently online users." )

------------------------------ Map ------------------------------
function ulx.map( calling_ply, map, gamemode )
	if not gamemode or gamemode == "" then
		ulx.fancyLogAdmin( calling_ply, "#A changed the map to #s", map )
	else
		ulx.fancyLogAdmin( calling_ply, "#A changed the map to #s with gamemode #s", map, gamemode )
	end
	if gamemode and gamemode ~= "" then
		game.ConsoleCommand( "gamemode " .. gamemode .. "\n" )
	end
	game.ConsoleCommand( "changelevel " .. map ..  "\n" )
end
local map = ulx.command( CATEGORY_NAME, "ulx map", ulx.map, "!map" )
map:addParam{type=ULib.cmds.StringArg, completes=ulx.maps, hint="map", error="invalid map \"%s\" specified", ULib.cmds.restrictToCompletes}
map:addParam{type=ULib.cmds.StringArg, completes=ulx.gamemodes, hint="gamemode", error="invalid gamemode \"%s\" specified", ULib.cmds.restrictToCompletes, ULib.cmds.optional}
map:defaultAccess( ULib.ACCESS_ADMIN )
map:help( "Changes map and gamemode." )

------------------------------ Spectate ------------------------------
function ulx.spectate( calling_ply, target_ply )
	if not calling_ply:IsValid() then
		Msg( "You can't spectate from dedicated server console.\n" )
		return
	end

	-- Check if player is already spectating. If so, stop spectating so we can start again
	local hookTable = hook.GetTable()["KeyPress"]
	if hookTable and hookTable["ulx_unspectate_" .. calling_ply:EntIndex()] then
		-- Simulate keypress to properly exit spectate.
		hook.Call( "KeyPress", _, calling_ply, IN_FORWARD )
	end

	if ulx.getExclusive( calling_ply, calling_ply ) then
		ULib.tsayError( calling_ply, ulx.getExclusive( calling_ply, calling_ply ) )
		return
	end

	ULib.getSpawnInfo( calling_ply )

	local pos = calling_ply:GetPos()
	local ang = calling_ply:GetAngles()

	local wasAlive = calling_ply:Alive()

	local function stopSpectate( ply )
		if ply ~= calling_ply then -- For the spawning, make sure it's them doing the spawning
			return
		end

		hook.Remove( "PlayerSpawn", "ULXUnspectate_" .. calling_ply:EntIndex() )
		hook.Remove( "KeyPress", "ULXUnspectate_" .. calling_ply:EntIndex() )
		hook.Remove( "PlayerDisconnected", "ULXUnspectate_" .. calling_ply:EntIndex() )

		if ply:HasGodMode() then
			ply:GodEnable()
		end -- Restore if player had ulx god.

		ply:UnSpectate() -- Need this for DarkRP for some reason, works fine without it in sbox
		ulx.fancyLogAdmin( calling_ply, true, "#A stopped spectating #T", target_ply )
		ulx.clearExclusive( calling_ply )
	end
	hook.Add( "PlayerSpawn", "ULXUnspectate_" .. calling_ply:EntIndex(), stopSpectate, HOOK_MONITOR_HIGH )

	local function unspectate( ply, key )
		if calling_ply ~= ply then return end -- Not the person we want
		if key ~= IN_FORWARD and key ~= IN_BACK and key ~= IN_MOVELEFT and key ~= IN_MOVERIGHT then return end -- Not a key we're interested in

		hook.Remove( "PlayerSpawn", "ULXUnspectate_" .. calling_ply:EntIndex() ) -- Otherwise spawn would cause infinite loop
		if wasAlive then -- We don't want to spawn them if they were already dead.
		    ULib.spawn( ply, true ) -- Get out of spectate.
		end
		stopSpectate( ply )
		ply:SetPos( pos )
		ply:SetAngles( ang )
	end
	hook.Add( "KeyPress", "ULXUnspectate_" .. calling_ply:EntIndex(), unspectate, HOOK_MONITOR_LOW )

	local function disconnect( ply ) -- We want to watch for spectator or target disconnect
		if ply == target_ply or ply == calling_ply then -- Target or spectator disconnecting
			unspectate( calling_ply, IN_FORWARD )
		end
	end
	hook.Add( "PlayerDisconnected", "ULXUnspectate_" .. calling_ply:EntIndex(), disconnect, HOOK_MONITOR_HIGH )

	calling_ply:Spectate( OBS_MODE_IN_EYE )
	calling_ply:SpectateEntity( target_ply )
	calling_ply:StripWeapons() -- Otherwise they can use weapons while spectating

	ULib.tsay( calling_ply, "To get out of spectate, move forward." )
	ulx.setExclusive( calling_ply, "spectating" )

	ulx.fancyLogAdmin( calling_ply, true, "#A began spectating #T", target_ply )
end
local spectate = ulx.command( CATEGORY_NAME, "ulx spectate", ulx.spectate, "!spectate", true )
spectate:addParam{type=ULib.cmds.PlayerArg, target="!^"}
spectate:defaultAccess( ULib.ACCESS_ADMIN )
spectate:help( "Spectate target.\nTrackable with hacking tools." )

------------------------------ God ------------------------------
function ulx.god( calling_ply, target_plys, should_revoke )
	if not target_plys[ 1 ]:IsValid() then
		if not should_revoke then
			Msg( "You are the console, you are already god.\n" )
		else
			Msg( "Your position of god is irrevocable; if you don't like it, leave the matrix.\n" )
		end
		return
	end

	local affected_plys = {}
	for i=1, #target_plys do
		local v = target_plys[ i ]

		if ulx.getExclusive( v, calling_ply ) then
			ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ) )
		else
			if not should_revoke then
				if v:HasGodMode() then ULib.tsayError( calling_ply, v:Name().." is already godded!" ) continue end
				v:GodEnable()
			else
				if not v:HasGodMode() then ULib.tsayError( calling_ply, v:Name().." is no god!" ) continue end
				v:GodDisable()
			end
			table.insert( affected_plys, v )
		end
	end

	if not should_revoke then
		ulx.fancyLogAdmin( calling_ply, "#A granted god mode upon #T", affected_plys )
	else
		ulx.fancyLogAdmin( calling_ply, "#A revoked god mode from #T", affected_plys )
	end
end
local god = ulx.command( CATEGORY_NAME, "ulx god", ulx.god, "!god" )
god:addParam{type=ULib.cmds.PlayersArg, ULib.cmds.optional}
god:addParam{type=ULib.cmds.BoolArg, invisible=true}
god:defaultAccess( ULib.ACCESS_ADMIN )
god:help( "Grants god mode to target(s)." )
god:setOpposite( "ulx ungod", {_, _, true}, "!ungod" )

------------------------------ Noclip ------------------------------
function ulx.noclip( calling_ply, target_plys )
	if not target_plys[ 1 ]:IsValid() then
		Msg( "You are god, you are not constrained by walls built by mere mortals.\n" )
		return
	end

	local affected_plys = {}
	for i=1, #target_plys do
		local v = target_plys[ i ]

		if v.NoNoclip then
			ULib.tsayError( calling_ply, v:Name() .. " can't be noclipped right now." )
		else
			if v:GetMoveType() == MOVETYPE_WALK then
				v:SetMoveType( MOVETYPE_NOCLIP )
				table.insert( affected_plys, v )
			elseif v:GetMoveType() == MOVETYPE_NOCLIP then
				v:SetMoveType( MOVETYPE_WALK )
				table.insert( affected_plys, v )
			else -- Ignore if they're an observer
				ULib.tsayError( calling_ply, v:Name() .. " can't be noclipped right now." )
			end
		end
	end
end
local noclip = ulx.command( CATEGORY_NAME, "ulx noclip", ulx.noclip, "!noclip" )
noclip:addParam{type=ULib.cmds.PlayersArg, ULib.cmds.optional}
noclip:defaultAccess( ULib.ACCESS_ADMIN )
noclip:help( "Toggles noclip on target(s)." )

if SERVER then
	local function playerPickup( ply, ent )
		if ent:IsPlayer() then
			ent.physgunned_by = ent.physgunned_by or {}
			ent.physgunned_by[ ply ] = true
		end
	end
	hook.Add( "OnPhysgunPickup ", "ULXPlayerPickupJailCheck", playerPickup )

	local function playerDrop( ply, ent )
		if ent:IsPlayer() and ent.physgunned_by then
			ent.physgunned_by[ ply ] = nil
		end
	end
	hook.Add( "PhysgunDrop", "ULXPlayerDropJailCheck", playerDrop )
end

------------------------------ Hp ------------------------------
function ulx.hp( calling_ply, target_plys, amount )
	for i=1, #target_plys do
		target_plys[ i ]:SetHealth( amount )
	end
	ulx.fancyLogAdmin( calling_ply, "#A set the hp for #T to #i", target_plys, amount )
end
local hp = ulx.command( CATEGORY_NAME, "ulx hp", ulx.hp, "!hp" )
hp:addParam{type=ULib.cmds.PlayersArg, ULib.cmds.optional}
hp:addParam{type=ULib.cmds.NumArg, min=1, max=1000000, default=100, hint="hp", ULib.cmds.round, ULib.cmds.optional}
hp:defaultAccess( ULib.ACCESS_ADMIN )
hp:help( "Sets the hp for target(s)." )

------------------------------ Armor ------------------------------
function ulx.armor( calling_ply, target_plys, amount )
	for i=1, #target_plys do
		target_plys[ i ]:SetArmor( amount )
	end
	ulx.fancyLogAdmin( calling_ply, "#A set the armor for #T to #i", target_plys, amount )
end
local armor = ulx.command( CATEGORY_NAME, "ulx armor", ulx.armor, "!armor" )
armor:addParam{type=ULib.cmds.PlayersArg, ULib.cmds.optional}
armor:addParam{type=ULib.cmds.NumArg, min=0, max=100, default=100, hint="armor", ULib.cmds.round, ULib.cmds.optional}
armor:defaultAccess( ULib.ACCESS_ADMIN )
armor:help( "Sets the armor for target(s)." )

------------------------------ Freeze ------------------------------
function ulx.freeze( calling_ply, target_plys, should_unfreeze )
	local affected_plys = {}
	for i=1, #target_plys do
		local v = target_plys[ i ]
		if not should_unfreeze and ulx.getExclusive( v, calling_ply ) then
			ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ) )
		else
			if should_unfreeze and not v.frozen and not v:IsFlagSet( FL_FROZEN ) and not v:IsFlagSet( FL_GODMODE ) then
				ULib.tsayError( calling_ply, v:Name().." is not frozen!" )
				continue
			end
			if v:InVehicle() then
				v:ExitVehicle()
			end

			hook.Run( "ULXPlayerFreeze", v, calling_ply, should_unfreeze )

			if not should_unfreeze then
				v:Lock()
				v.frozen = true
				ulx.setExclusive( v, "frozen" )
			else
				v:UnLock()
				v.frozen = nil
				ulx.clearExclusive( v )
			end

			v:DisallowSpawning( not should_unfreeze )
			ulx.setNoDie( v, not should_unfreeze )
			table.insert( affected_plys, v )

			if v.whipped then
				v.whipcount = v.whipamt -- Will make it remove
			end
		end
	end

	if not should_unfreeze then
		ulx.fancyLogAdmin( calling_ply, "#A froze #T", affected_plys )
	else
		ulx.fancyLogAdmin( calling_ply, "#A unfroze #T", affected_plys )
	end
end
local freeze = ulx.command( CATEGORY_NAME, "ulx freeze", ulx.freeze, "!freeze" )
freeze:addParam{type=ULib.cmds.PlayersArg}
freeze:addParam{type=ULib.cmds.BoolArg, invisible=true}
freeze:defaultAccess( ULib.ACCESS_ADMIN )
freeze:help( "Freezes target(s)." )
freeze:setOpposite( "ulx unfreeze", {_, _, true}, "!unfreeze" )

function ulx.debuginfo( calling_ply )
	local str = string.format( "ULX version: %s\nULib version: %s\n", ULib.pluginVersionStr( "ULX" ), ULib.pluginVersionStr( "ULib" ) )
	str = str .. string.format( "Gamemode: %s\nMap: %s\n", GAMEMODE.Name, game.GetMap() )
	str = str .. "Dedicated server: " .. tostring( game.IsDedicated() ) .. "\n\n"

	local players = player.GetAll()
	str = str .. string.format( "Currently connected players:\nNick%s steamid%s id lsh\n", str.rep( " ", 27 ), str.rep( " ", 12 ), str.rep( " ", 7 ) )
	for _, ply in ipairs( players ) do
		local id = string.format( "%i", ply:EntIndex() )
		local steamid = ply:SteamID()
		local name = utf8.force( ply:Name() )

		local plyline = name .. str.rep( " ", 32 - utf8.len( name ) ) -- Name
		plyline = plyline .. steamid .. str.rep( " ", 20 - steamid:len() ) -- Steamid
		plyline = plyline .. id .. str.rep( " ", 3 - id:len() ) -- id
		if ply:IsListenServerHost() then
			plyline = plyline .. "y	  "
		else
			plyline = plyline .. "n	  "
		end

		str = str .. plyline .. "\n"
	end

	str = str .. "\n\nULib.ucl.users (#=" .. table.Count( ULib.ucl.users ) .. "):\n" .. ulx.dumpTable( ULib.ucl.users, 1 ) .. "\n\n"
	str = str .. "ULib.ucl.groups (#=" .. table.Count( ULib.ucl.groups ) .. "):\n" .. ulx.dumpTable( ULib.ucl.groups, 1 ) .. "\n\n"
	str = str .. "ULib.ucl.authed (#=" .. table.Count( ULib.ucl.authed ) .. "):\n" .. ulx.dumpTable( ULib.ucl.authed, 1 ) .. "\n\n"

	str = str .. "Active workshop addons on this server:\n"
	local addons = engine.GetAddons()
	for i=1, #addons do
		local addon = addons[i]
		if addon.mounted then
			local name = utf8.force( addon.title )
			str = str .. string.format( "%s%s workshop ID %s\n", name, str.rep( " ", 32 - utf8.len( name ) ), addon.file:gsub( "%D", "" ) )
		end
	end
	str = str .. "\n"

	str = str .. "Active legacy addons on this server:\n"
	local _, possibleaddons = file.Find( "addons/*", "GAME" )
	for _, addon in ipairs( possibleaddons ) do
		if not ULib.findInTable( {"checkers", "chess", "common", "go", "hearts", "spades"}, addon:lower() ) then -- Not sure what these addon folders are
			local name = addon
			local author, version, date
			if ULib.fileExists( "addons/" .. addon .. "/addon.txt" ) then
				local t = ULib.parseKeyValues( ULib.stripComments( ULib.fileRead( "addons/" .. addon .. "/addon.txt" ), "//" ) )
				if t and t.AddonInfo then
					t = t.AddonInfo
					if t.name then name = t.name end
					if t.version then version = t.version end
					if t.author_name then author = t.author_name end
					if t.up_date then date = t.up_date end
				end
			end

			name = utf8.force( name )
			str = str .. name .. str.rep( " ", 32 - utf8.len( name ) )
			if author then
				str = string.format( "%s by %s%s", str, author, version and "," or "" )
			end

			if version then
				str = str .. " version " .. version
			end

			if date then
				str = string.format( "%s (%s)", str, date )
			end
			str = str .. "\n"
		end
	end

	ULib.fileWrite( "data/ulx/debugdump.txt", str )
	Msg( "Debug information written to garrysmod/data/ulx/debugdump.txt on server.\n" )
end
local debuginfo = ulx.command( CATEGORY_NAME, "ulx debuginfo", ulx.debuginfo )
debuginfo:help( "Dump some debug information." )

function ulx.resettodefaults( calling_ply, param )
	if param ~= "FORCE" then
		local str = "Are you SURE about this? It will remove ulx-created configs and groups!"
		local str2 = "If you're sure, type \"ulx resettodefaults FORCE\""
		if calling_ply:IsValid() then
			ULib.tsayError( calling_ply, str )
			ULib.tsayError( calling_ply, str2 )
		else
			Msg( str .. "\n" )
			Msg( str2 .. "\n" )
		end
		return
	end

	ULib.fileDelete( "data/ulx/adverts.txt" )
	ULib.fileDelete( "data/ulx/banreasons.txt" )
	ULib.fileDelete( "data/ulx/config.txt" )
	ULib.fileDelete( "data/ulx/downloads.txt" )
	ULib.fileDelete( "data/ulx/gimps.txt" )
	ULib.fileDelete( "data/ulx/sbox_limits.txt" )
	ULib.fileDelete( "data/ulx/votemaps.txt" )
	ULib.fileDelete( "data/ulib/bans.txt" )
	ULib.fileDelete( "data/ulib/groups.txt" )
	ULib.fileDelete( "data/ulib/misc_registered.txt" )
	ULib.fileDelete( "data/ulib/users.txt" )
	ULib.fileDelete( "data/ulx/gimps.txt" )
	ULib.fileDelete( "data/ulx/motd.txt" )
	ULib.fileDelete( "data/ulx/banmessage.txt" )

  	if sql.TableExists( "ulib_bans" ) then
		sql.Query( "DROP TABLE ulib_bans" )
	end

  	if sql.TableExists( "ulib_users" ) then
    	sql.Query( "DROP TABLE ulib_users" )
	end

	local str = "Please change levels to finish the reset"
	if calling_ply:IsValid() then
		ULib.tsayError( calling_ply, str )
	else
		Msg( str .. "\n" )
	end

	ulx.fancyLogAdmin( calling_ply, "#A reset all ULX and ULib configuration" )
end
local resettodefaults = ulx.command( CATEGORY_NAME2, "ulx resettodefaults", ulx.resettodefaults )
resettodefaults:addParam{type=ULib.cmds.StringArg, ULib.cmds.optional}
resettodefaults:help( "Resets ALL ULX and ULib configuration!" )

if SERVER then
	function ulx.addForcedDownload( path )
		if ULib.fileIsDir( path ) then
			files = ULib.filesInDir( path )
			for _, v in ipairs( files ) do
				ulx.addForcedDownload( path .. "/" .. v )
			end
		elseif ULib.fileExists( path ) then
			resource.AddFile( path )
		else
			Msg( "[ULX] ERROR: Tried to add nonexistent or empty file to forced downloads '" .. path .. "'\n" )
		end
	end

	local ulx_kickAfterNameChanges = 			ulx.convar( "kickAfterNameChanges", "0", "<number> - Players can only change their name x times every ulx_kickAfterNameChangesCooldown seconds. 0 to disable.", ULib.ACCESS_ADMIN )
	local ulx_kickAfterNameChangesCooldown = 	ulx.convar( "kickAfterNameChangesCooldown", "60", "<time> - Players can change their name ulx_kickAfterXNameChanges times every x seconds.", ULib.ACCESS_ADMIN )
	local ulx_kickAfterNameChangesWarning = 	ulx.convar( "kickAfterNameChangesWarning", "1", "<1/0> - Display a warning to users to let them know how many more times they can change their name.", ULib.ACCESS_ADMIN )
	ulx.nameChangeTable = ulx.nameChangeTable or {}

	local function checkNameChangeLimit( ply, oldname, newname )
		local maxAttempts = ulx_kickAfterNameChanges:GetInt()
		local duration = ulx_kickAfterNameChangesCooldown:GetInt()
		local showWarning = ulx_kickAfterNameChangesWarning:GetInt()

		if maxAttempts ~= 0 then
			if not ulx.nameChangeTable[ply:SteamID()] then
				ulx.nameChangeTable[ply:SteamID()] = {}
			end

			for i=#ulx.nameChangeTable[ply:SteamID()], 1, -1 do
				if CurTime() - ulx.nameChangeTable[ply:SteamID()][i] > duration then
					table.remove( ulx.nameChangeTable[ply:SteamID()], i )
				end
			end

			table.insert( ulx.nameChangeTable[ply:SteamID()], CurTime() )

			local curAttempts = #ulx.nameChangeTable[ply:SteamID()]

			if curAttempts >= maxAttempts then
				ULib.kick( ply, "Changed name too many times" )
			else
				if showWarning == 1 then
					ULib.tsay( ply, "Warning: You have changed your name " .. curAttempts .. " out of " .. maxAttempts .. " time" .. ( maxAttempts ~= 1 and "s" ) .. " in the past " .. duration .. " second" .. ( duration ~= 1 and "s" ) )
				end
			end
		end
	end
	hook.Add( ULib.HOOK_PLAYER_NAME_CHANGED, "ULXCheckNameChangeLimit", checkNameChangeLimit )
end

--------------------
--	   Hooks	  --
--------------------
-- This cvar also exists in DarkRP (thanks, FPtje)
local cl_cvar_pickup = "cl_pickupplayers"
if CLIENT then CreateClientConVar( cl_cvar_pickup, "1", true, true ) end
local function playerPickup( ply, ent )
	local access, tag = ULib.ucl.query( ply, "ulx physgunplayer" )
	if ent:IsPlayer() and ULib.isSandbox() and access and not ent.NoNoclip and not ent.frozen and ply:GetInfoNum( cl_cvar_pickup, 1 ) == 1 then
		-- Extra restrictions! UCL wasn't designed to handle this sort of thing so we're putting it in by hand...
		local restrictions = {}
		ULib.cmds.PlayerArg.processRestrictions( restrictions, ply, {}, tag and ULib.splitArgs( tag )[ 1 ] )
		if restrictions.restrictedTargets == false or (restrictions.restrictedTargets and not table.HasValue( restrictions.restrictedTargets, ent )) then return end

		ent:SetMoveType( MOVETYPE_NONE ) -- So they don't bounce
		return true
	end
end
hook.Add( "PhysgunPickup", "ULXPlayerPickup", playerPickup, HOOK_HIGH ) -- Allow admins to move players. Call before the prop protection hooks.

local function playerDrop( ply, ent )
	if ent:IsPlayer() then
		ent:SetMoveType( MOVETYPE_WALK )
	end
end
hook.Add( "PhysgunDrop", "ULXPlayerDrop", playerDrop, HOOK_LOW ) -- Reset player move type. Call after prop protections hooks to prevent conflicts.

if SERVER then ULib.ucl.registerAccess( "ulx physgunplayer", ULib.ACCESS_ADMIN, "Ability to physgun other players", "Other" ) end
