<commit_before>/*
===========================================================================

Daemon GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.

This file is part of the Daemon GPL Source Code (Daemon Source Code).

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

Daemon Source Code 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with Daemon Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Daemon Source Code is also subject to certain additional terms.
You should have received a copy of these additional terms immediately following the
terms and conditions of the GNU General Public License which accompanied the Daemon
Source Code.  If not, please request a copy in writing from id Software at the address
below.

If you have questions concerning this license or the applicable additional terms, you
may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville,
Maryland 20850 USA.

===========================================================================
*/

#include "common/Common.h"

#include "keys.h"

#include "client.h"
#include "key_identification.h"
#include "framework/CommandSystem.h"


using Keyboard::Key;

static Cvar::Modified<Cvar::Cvar<std::string>> cl_consoleKeys(
	"cl_consoleKeys", "Keys to open or close the console", Cvar::ARCHIVE, "hw:`");

static int ClipTeamNumber(int team)
{
	return Math::Clamp(team, 0, Keyboard::NUM_TEAMS - 1);
}

static bool IsValidTeamNumber(int team)
{
	return team >= 0 && team < Keyboard::NUM_TEAMS;
}

/*
===============
Helper functions for Cmd_If_f & Cmd_ModCase_f
===============
*/
static const struct
{
	char name[ 8 ];
	unsigned short count;
	unsigned short bit;
	keyNum_t index;
} modifierKeys[] =
{
	{ "shift", 5, 1, K_SHIFT },
	{ "ctrl", 4, 2, K_CTRL },
	{ "alt", 3, 4, K_ALT },
	{ "command", 7, 8, K_COMMAND },
	{ "cmd", 3, 8, K_COMMAND },
	{ "mode", 4, 16, K_MODE },
	{ "super", 5, 32, K_SUPER },
	{ "compose", 6, 64, K_COMPOSE },
	{ "menu", 7, 128, K_MENU },
	{ },
};
// Following is no. of bits required for modifiers in the above list
// (it doesn't reflect the array length)
static const int NUM_RECOGNISED_MODIFIERS = 8;

struct modifierMask_t
{
	uint16_t down, up;
	int bits;
};

static modifierMask_t getModifierMask( const char *mods )
{
	int i;
	modifierMask_t mask;
	const char *ptr;
	static const modifierMask_t none = {0, 0, 0};

	mask = none;

	--mods;

	while ( *++mods == ' ' ) { /* skip leading spaces */; }

	ptr = mods;

	while ( *ptr )
	{
		int invert = ( *ptr == '!' );

		if ( invert )
		{
			++ptr;
		}

		for ( i = 0; modifierKeys[ i ].bit; ++i )
		{
			// is it this modifier?
			if ( !Q_strnicmp( ptr, modifierKeys[ i ].name, modifierKeys[ i ].count )
					&& ( ptr[ modifierKeys[ i ].count ] == ' ' ||
						ptr[ modifierKeys[ i ].count ] == ',' ||
						ptr[ modifierKeys[ i ].count ] == 0 ) )
			{
				if ( invert )
				{
					mask.up |= modifierKeys[ i ].bit;
				}
				else
				{
					mask.down |= modifierKeys[ i ].bit;
				}

				if ( ( mask.down & mask.up ) & modifierKeys[ i ].bit )
				{
					Log::Notice( "can't have %s both pressed and not pressed", modifierKeys[ i ].name );
					return none;
				}

				// right, parsed a word - skip it, maybe a comma, and any spaces
				ptr += modifierKeys[ i ].count - 1;

				while ( *++ptr == ' ' ) { /**/; }

				if ( *ptr == ',' )
				{
					while ( *++ptr == ' ' ) { /**/; }
				}

				// ready to parse the next one
				break;
			}
		}

		if ( !modifierKeys[ i ].bit )
		{
			Log::Notice( "unknown modifier key name in \"%s\"", mods );
			return none;
		}
	}

	for ( i = 0; i < NUM_RECOGNISED_MODIFIERS; ++i )
	{
		if ( mask.up & ( 1 << i ) )
		{
			++mask.bits;
		}

		if ( mask.down & ( 1 << i ) )
		{
			++mask.bits;
		}
	}

	return mask;
}

static int checkKeysDown( modifierMask_t mask )
{
	int i;

	for ( i = 0; modifierKeys[ i ].bit; ++i )
	{
		if ( ( mask.down & modifierKeys[ i ].bit ) && keys[ Keyboard::Key(modifierKeys[ i ].index) ].down == 0 )
		{
			return 0; // should be pressed, isn't pressed
		}

		if ( ( mask.up & modifierKeys[ i ].bit ) && keys[ Keyboard::Key(modifierKeys[ i ].index) ].down )
		{
			return 0; // should not be pressed, is pressed
		}
	}

	return 1; // all (not) pressed as requested
}



void CL_ClearKeyBinding()
{
	for ( auto& kv: keys )
	{
		for ( auto& binding: kv.second.binding )
		{
			binding = {};
		}
	}
}

namespace Keyboard {

static BindTeam bindTeam = BIND_TEAM_SPECTATORS; // Should never be BIND_TEAM_DEFAULT

void SetTeam( int newTeam )
{
	BindTeam newBindTeam;
	if (newTeam > BIND_TEAM_DEFAULT && newTeam < NUM_TEAMS) {
		newBindTeam = Util::enum_cast<BindTeam>(newTeam);
	} else {
		newBindTeam = BIND_TEAM_SPECTATORS;
	}

	if ( bindTeam != newBindTeam )
	{
		Log::Debug( "%sSetting binding team index to %d",
			Color::ToString( Color::Green ),
			Util::ordinal(newBindTeam) );
	}

	bindTeam = newBindTeam;
}

BindTeam GetTeam()
{
	return bindTeam;
}

// Sets a key binding, or clears it given an empty string.
// team == -1 clears all bindings for the key, then sets the default binding
void SetBinding(Key key, int team, std::string binding)
{
	// so name toggle scripts work again: bind x name BzZIfretn?
	// resulted into bzzifretn?

	if ( !key.IsBindable() )
	{
		return;
	}

	// free old bindings
	if ( team == -1 )
	{
		// just the team-specific ones here
		for ( team = NUM_TEAMS - 1; team; --team )
		{
			keys[ key ].binding[ team ] = {};
		}
		// team == 0...
	}

	team = ClipTeamNumber( team );

	// set the new binding, if not null/empty
	if ( !binding.empty() )
	{
		keys[ key ].binding[ team ] = std::move(binding);
	}
	else
	{
		keys[ key ].binding[ team ] = Util::nullopt;
	}

	bindingsModified = true;
}

const std::vector<Key>& GetConsoleKeys()
{
	static std::vector<Keyboard::Key> consoleKeys;

	// Only parse the variable when it changes
	Util::optional<std::string> modifiedString = cl_consoleKeys.GetModifiedValue();
	if ( modifiedString )
	{
		const char* text_p = modifiedString.value().c_str();
		consoleKeys.clear();
		while ( true )
		{
			const char* token = COM_Parse( &text_p );
			if ( !token[ 0 ] )
			{
				break;
			}
			Keyboard::Key k = Keyboard::StringToKey(token);
			if (k.IsBindable()) {
				consoleKeys.push_back(k);
			}
		}
	}

	return consoleKeys;
}

void SetConsoleKeys(const std::vector<Key>& keys)
{
	std::string text;
	for (Key key : keys) {
		if (key.IsBindable()) {
			if (!text.empty()) {
				text += ' ';
			}
			text += Cmd::Escape(KeyToString(key));
		}
	}
	cl_consoleKeys.Set(text);
}

Util::optional<std::string> GetBinding(Key key, BindTeam team, bool useDefault)
{
	if (!key.IsBindable() || !IsValidTeamNumber(team)) {
		return {};
	}
	auto it = keys.find(key);
	if (it == keys.end()) {
		return {};
	}
	const auto& bind = it->second.binding[team];
	if (!bind && useDefault) {
		return it->second.binding[BIND_TEAM_DEFAULT];
	} else {
		return bind;
	}
}

std::vector<Key> GetKeysBoundTo(int team, Str::StringRef command)
{
	if (!IsValidTeamNumber(team)) {
		return {};
	}
	std::vector<Key> result;
	for (const auto& key : keys) {
		for (BindTeam t: {Util::enum_cast<BindTeam>(team), BIND_TEAM_DEFAULT}) {
			const auto& binding = key.second.binding[t];
			if (binding) {
				if (Str::IsIEqual(command, binding.value())) {
					result.push_back(key.first);
				}
				break;
			}
		}
	}
	return result;
}

void WriteBindings( fileHandle_t f )
{
	FS_Printf( f,"%s", "unbindall\n" );

	std::vector<std::string> lines;
	for (const auto& kv: keys)
	{
		for (int team = 0; team < NUM_TEAMS; ++team) {
			if (!kv.second.binding[ team ]) {
				continue;
			}
			std::string escapedKeyName = Cmd::Escape( KeyToString( kv.first ) );
			std::string escapedBind = Cmd::Escape( kv.second.binding[ team ].value() );
			if (team == BIND_TEAM_DEFAULT) {
				lines.push_back( Str::Format( "bind       %s %s\n", escapedKeyName, escapedBind ) );
			} else {
				lines.push_back( Str::Format( "teambind %d %s %s\n", team, escapedKeyName, escapedBind ) );
			}
		}
	}
	std::sort( lines.begin(), lines.end() );
	for (const auto& line: lines)
	{
		FS_Printf( f, "%s", line.c_str() );
	}
}

namespace { // Key binding commands

// Assumes 'three' teams: spectators, aliens, humans
static const char *const teamName[] = { "default", "aliens", "humans", "spectators" };
int GetTeam(Str::StringRef arg)
{
	int t, l;

	if ( arg.empty() )
	{
		return -1;
	}

	for ( t = 0; arg[ t ]; ++t )
	{
		if ( !Str::cisdigit( arg[ t ] ) )
		{
			break;
		}
	}

	if ( !arg[ t ] )
	{
		t = atoi( arg.c_str() );

		if ( !IsValidTeamNumber( t ) )
		{
			return -1;
		}

		return Util::enum_cast<BindTeam>(t);
	}

	l = arg.size();

	for ( unsigned t = 0; t < ARRAY_LEN( teamName ); ++t )<commit_msg>Refactor string to team for keybinds conversion<commit_after>/*
===========================================================================

Daemon GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.

This file is part of the Daemon GPL Source Code (Daemon Source Code).

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

Daemon Source Code 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with Daemon Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Daemon Source Code is also subject to certain additional terms.
You should have received a copy of these additional terms immediately following the
terms and conditions of the GNU General Public License which accompanied the Daemon
Source Code.  If not, please request a copy in writing from id Software at the address
below.

If you have questions concerning this license or the applicable additional terms, you
may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville,
Maryland 20850 USA.

===========================================================================
*/

#include "common/Common.h"

#include "keys.h"

#include "client.h"
#include "key_identification.h"
#include "framework/CommandSystem.h"


using Keyboard::Key;

static Cvar::Modified<Cvar::Cvar<std::string>> cl_consoleKeys(
	"cl_consoleKeys", "Keys to open or close the console", Cvar::ARCHIVE, "hw:`");

static int ClipTeamNumber(int team)
{
	return Math::Clamp(team, 0, Keyboard::NUM_TEAMS - 1);
}

static bool IsValidTeamNumber(int team)
{
	return team >= 0 && team < Keyboard::NUM_TEAMS;
}

/*
===============
Helper functions for Cmd_If_f & Cmd_ModCase_f
===============
*/
static const struct
{
	char name[ 8 ];
	unsigned short count;
	unsigned short bit;
	keyNum_t index;
} modifierKeys[] =
{
	{ "shift", 5, 1, K_SHIFT },
	{ "ctrl", 4, 2, K_CTRL },
	{ "alt", 3, 4, K_ALT },
	{ "command", 7, 8, K_COMMAND },
	{ "cmd", 3, 8, K_COMMAND },
	{ "mode", 4, 16, K_MODE },
	{ "super", 5, 32, K_SUPER },
	{ "compose", 6, 64, K_COMPOSE },
	{ "menu", 7, 128, K_MENU },
	{ },
};
// Following is no. of bits required for modifiers in the above list
// (it doesn't reflect the array length)
static const int NUM_RECOGNISED_MODIFIERS = 8;

struct modifierMask_t
{
	uint16_t down, up;
	int bits;
};

static modifierMask_t getModifierMask( const char *mods )
{
	int i;
	modifierMask_t mask;
	const char *ptr;
	static const modifierMask_t none = {0, 0, 0};

	mask = none;

	--mods;

	while ( *++mods == ' ' ) { /* skip leading spaces */; }

	ptr = mods;

	while ( *ptr )
	{
		int invert = ( *ptr == '!' );

		if ( invert )
		{
			++ptr;
		}

		for ( i = 0; modifierKeys[ i ].bit; ++i )
		{
			// is it this modifier?
			if ( !Q_strnicmp( ptr, modifierKeys[ i ].name, modifierKeys[ i ].count )
					&& ( ptr[ modifierKeys[ i ].count ] == ' ' ||
						ptr[ modifierKeys[ i ].count ] == ',' ||
						ptr[ modifierKeys[ i ].count ] == 0 ) )
			{
				if ( invert )
				{
					mask.up |= modifierKeys[ i ].bit;
				}
				else
				{
					mask.down |= modifierKeys[ i ].bit;
				}

				if ( ( mask.down & mask.up ) & modifierKeys[ i ].bit )
				{
					Log::Notice( "can't have %s both pressed and not pressed", modifierKeys[ i ].name );
					return none;
				}

				// right, parsed a word - skip it, maybe a comma, and any spaces
				ptr += modifierKeys[ i ].count - 1;

				while ( *++ptr == ' ' ) { /**/; }

				if ( *ptr == ',' )
				{
					while ( *++ptr == ' ' ) { /**/; }
				}

				// ready to parse the next one
				break;
			}
		}

		if ( !modifierKeys[ i ].bit )
		{
			Log::Notice( "unknown modifier key name in \"%s\"", mods );
			return none;
		}
	}

	for ( i = 0; i < NUM_RECOGNISED_MODIFIERS; ++i )
	{
		if ( mask.up & ( 1 << i ) )
		{
			++mask.bits;
		}

		if ( mask.down & ( 1 << i ) )
		{
			++mask.bits;
		}
	}

	return mask;
}

static int checkKeysDown( modifierMask_t mask )
{
	int i;

	for ( i = 0; modifierKeys[ i ].bit; ++i )
	{
		if ( ( mask.down & modifierKeys[ i ].bit ) && keys[ Keyboard::Key(modifierKeys[ i ].index) ].down == 0 )
		{
			return 0; // should be pressed, isn't pressed
		}

		if ( ( mask.up & modifierKeys[ i ].bit ) && keys[ Keyboard::Key(modifierKeys[ i ].index) ].down )
		{
			return 0; // should not be pressed, is pressed
		}
	}

	return 1; // all (not) pressed as requested
}



void CL_ClearKeyBinding()
{
	for ( auto& kv: keys )
	{
		for ( auto& binding: kv.second.binding )
		{
			binding = {};
		}
	}
}

namespace Keyboard {

static BindTeam bindTeam = BIND_TEAM_SPECTATORS; // Should never be BIND_TEAM_DEFAULT

void SetTeam( int newTeam )
{
	BindTeam newBindTeam;
	if (newTeam > BIND_TEAM_DEFAULT && newTeam < NUM_TEAMS) {
		newBindTeam = Util::enum_cast<BindTeam>(newTeam);
	} else {
		newBindTeam = BIND_TEAM_SPECTATORS;
	}

	if ( bindTeam != newBindTeam )
	{
		Log::Debug( "%sSetting binding team index to %d",
			Color::ToString( Color::Green ),
			Util::ordinal(newBindTeam) );
	}

	bindTeam = newBindTeam;
}

BindTeam GetTeam()
{
	return bindTeam;
}

// Sets a key binding, or clears it given an empty string.
// team == -1 clears all bindings for the key, then sets the default binding
void SetBinding(Key key, int team, std::string binding)
{
	// so name toggle scripts work again: bind x name BzZIfretn?
	// resulted into bzzifretn?

	if ( !key.IsBindable() )
	{
		return;
	}

	// free old bindings
	if ( team == -1 )
	{
		// just the team-specific ones here
		for ( team = NUM_TEAMS - 1; team; --team )
		{
			keys[ key ].binding[ team ] = {};
		}
		// team == 0...
	}

	team = ClipTeamNumber( team );

	// set the new binding, if not null/empty
	if ( !binding.empty() )
	{
		keys[ key ].binding[ team ] = std::move(binding);
	}
	else
	{
		keys[ key ].binding[ team ] = Util::nullopt;
	}

	bindingsModified = true;
}

const std::vector<Key>& GetConsoleKeys()
{
	static std::vector<Keyboard::Key> consoleKeys;

	// Only parse the variable when it changes
	Util::optional<std::string> modifiedString = cl_consoleKeys.GetModifiedValue();
	if ( modifiedString )
	{
		const char* text_p = modifiedString.value().c_str();
		consoleKeys.clear();
		while ( true )
		{
			const char* token = COM_Parse( &text_p );
			if ( !token[ 0 ] )
			{
				break;
			}
			Keyboard::Key k = Keyboard::StringToKey(token);
			if (k.IsBindable()) {
				consoleKeys.push_back(k);
			}
		}
	}

	return consoleKeys;
}

void SetConsoleKeys(const std::vector<Key>& keys)
{
	std::string text;
	for (Key key : keys) {
		if (key.IsBindable()) {
			if (!text.empty()) {
				text += ' ';
			}
			text += Cmd::Escape(KeyToString(key));
		}
	}
	cl_consoleKeys.Set(text);
}

Util::optional<std::string> GetBinding(Key key, BindTeam team, bool useDefault)
{
	if (!key.IsBindable() || !IsValidTeamNumber(team)) {
		return {};
	}
	auto it = keys.find(key);
	if (it == keys.end()) {
		return {};
	}
	const auto& bind = it->second.binding[team];
	if (!bind && useDefault) {
		return it->second.binding[BIND_TEAM_DEFAULT];
	} else {
		return bind;
	}
}

std::vector<Key> GetKeysBoundTo(int team, Str::StringRef command)
{
	if (!IsValidTeamNumber(team)) {
		return {};
	}
	std::vector<Key> result;
	for (const auto& key : keys) {
		for (BindTeam t: {Util::enum_cast<BindTeam>(team), BIND_TEAM_DEFAULT}) {
			const auto& binding = key.second.binding[t];
			if (binding) {
				if (Str::IsIEqual(command, binding.value())) {
					result.push_back(key.first);
				}
				break;
			}
		}
	}
	return result;
}

void WriteBindings( fileHandle_t f )
{
	FS_Printf( f,"%s", "unbindall\n" );

	std::vector<std::string> lines;
	for (const auto& kv: keys)
	{
		for (int team = 0; team < NUM_TEAMS; ++team) {
			if (!kv.second.binding[ team ]) {
				continue;
			}
			std::string escapedKeyName = Cmd::Escape( KeyToString( kv.first ) );
			std::string escapedBind = Cmd::Escape( kv.second.binding[ team ].value() );
			if (team == BIND_TEAM_DEFAULT) {
				lines.push_back( Str::Format( "bind       %s %s\n", escapedKeyName, escapedBind ) );
			} else {
				lines.push_back( Str::Format( "teambind %d %s %s\n", team, escapedKeyName, escapedBind ) );
			}
		}
	}
	std::sort( lines.begin(), lines.end() );
	for (const auto& line: lines)
	{
		FS_Printf( f, "%s", line.c_str() );
	}
}

namespace { // Key binding commands

// Assumes 'three' teams: spectators, aliens, humans
static const char *const teamName[] = { "default", "aliens", "humans", "spectators" };
int GetTeam(Str::StringRef arg)
{
	if ( arg.empty() )
	{
		return -1;
	}

	int team;
	if ( Str::ParseInt( team, arg ) )
	{
		return IsValidTeamNumber( team ) ? team : -1;
	}

	size_t l = arg.size();

	for ( unsigned t = 0; t < ARRAY_LEN( teamName ); ++t )
{'fraction_non_alphanumeric': 0.164680687496568, 'fraction_numerical': 0.00774257317006205, 'mean_word_length': 3.299338999055713, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 38, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1191139', 'n_tokens_mistral': 7205, 'n_tokens_neox': 6400, 'n_words': 1933}