using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; namespace Discord.Commands { internal class CommandMapNode { private static readonly char[] WhitespaceChars = { ' ', '\r', '\n' }; private readonly ConcurrentDictionary _nodes; private readonly string _name; private readonly object _lockObj = new object(); private ImmutableArray _commands; public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; public CommandMapNode(string name) { _name = name; _nodes = new ConcurrentDictionary(); _commands = ImmutableArray.Create(); } /// Cannot add commands to the root node. public void AddCommand(CommandService service, string text, int index, CommandInfo command) { int nextSegment = NextSegment(text, index, service._separatorChar); string name; lock (_lockObj) { if (text == "") { if (_name == "") throw new InvalidOperationException("Cannot add commands to the root node."); _commands = _commands.Add(command); } else { if (nextSegment == -1) name = text.Substring(index); else name = text.Substring(index, nextSegment - index); string fullName = _name == "" ? name : _name + service._separatorChar + name; var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(fullName)); nextNode.AddCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); } } } public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) { int nextSegment = NextSegment(text, index, service._separatorChar); lock (_lockObj) { if (text == "") _commands = _commands.Remove(command); else { string name; if (nextSegment == -1) name = text.Substring(index); else name = text.Substring(index, nextSegment - index); if (_nodes.TryGetValue(name, out var nextNode)) { nextNode.RemoveCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); if (nextNode.IsEmpty) _nodes.TryRemove(name, out nextNode); } } } } public IEnumerable GetCommands(CommandService service, string text, int index, bool visitChildren = true) { var commands = _commands; for (int i = 0; i < commands.Length; i++) yield return new CommandMatch(_commands[i], _name); if (visitChildren) { string name; CommandMapNode nextNode; //Search for next segment int nextSegment = NextSegment(text, index, service._separatorChar); if (nextSegment == -1) name = text.Substring(index); else name = text.Substring(index, nextSegment - index); if (_nodes.TryGetValue(name, out nextNode)) { foreach (var cmd in nextNode.GetCommands(service, nextSegment == -1 ? "" : text, nextSegment + 1, true)) yield return cmd; } //Check if this is the last command segment before args nextSegment = NextSegment(text, index, WhitespaceChars, service._separatorChar); if (nextSegment != -1) { name = text.Substring(index, nextSegment - index); if (_nodes.TryGetValue(name, out nextNode)) { foreach (var cmd in nextNode.GetCommands(service, nextSegment == -1 ? "" : text, nextSegment + 1, false)) yield return cmd; } } } } private static int NextSegment(string text, int startIndex, char separator) { return text.IndexOf(separator, startIndex); } private static int NextSegment(string text, int startIndex, char[] separators, char except) { int lowest = int.MaxValue; for (int i = 0; i < separators.Length; i++) { if (separators[i] != except) { int index = text.IndexOf(separators[i], startIndex); if (index != -1 && index < lowest) lowest = index; } } return (lowest != int.MaxValue) ? lowest : -1; } } }