using Discord;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Kerobot.Common
{
///
/// The type of entity specified in an .
///
public enum EntityType
{
/// Default value. Is never referenced in regular usage.
Unspecified,
Role,
Channel,
User
}
///
/// Helper class that holds an entity's name, ID, or both.
/// Meant to be used during configuration processing in cases where the configuration expects
/// an entity name to be defined in a certain way which may or may not include its snowflake ID.
///
public class EntityName
{
///
/// The entity's type, if specified in configuration.
///
public EntityType Type { get; private set; }
///
/// Entity's unique ID value (snowflake). May be null if the value is not known.
///
public ulong? Id { get; private set; }
///
/// Entity's name as specified in configuration. May be null if it was not specified.
/// This value is not updated during runtime.
///
public string Name { get; private set; }
// TODO elsewhere: find a way to emit a warning if the user specified a name without ID in configuration.
///
/// Creates a new object instance from the given input string.
/// Documentation for the EntityName format can be found elsewhere in this project's documentation.
///
/// Input string in EntityName format.
/// Input string is null or blank.
/// Input string cannot be resolved to an entity type.
public EntityName(string input)
{
if (string.IsNullOrWhiteSpace(input))
throw new ArgumentNullException("Specified name is null or blank.");
// Check if type prefix was specified and extract it
Type = EntityType.Unspecified;
if (input.Length >= 2)
{
if (input[0] == '&') Type = EntityType.Role;
else if (input[0] == '#') Type = EntityType.Channel;
else if (input[0] == '@') Type = EntityType.User;
}
if (Type == EntityType.Unspecified)
throw new ArgumentException("Entity type unable to be inferred by given input.");
input = input.Substring(1); // Remove prefix
// Input contains ID/Label separator?
int separator = input.IndexOf("::");
if (separator != -1)
{
Name = input.Substring(separator + 2, input.Length - (separator + 2));
if (ulong.TryParse(input.Substring(0, separator), out var parseOut))
{
// Got an ID.
Id = parseOut;
}
else
{
// It's not actually an ID. Assuming the entire string is a name.
Name = input;
Id = null;
}
}
else
{
// No separator. Input is either entirely an ID or entirely a Name.
if (ulong.TryParse(input, out var parseOut))
{
// ID without name.
Id = parseOut;
Name = null;
}
else
{
// Name without ID.
Name = input;
Id = null;
}
}
}
internal void SetId(ulong id)
{
if (!Id.HasValue) Id = id;
}
#region Name to ID resolving
///
/// Attempts to determine the corresponding ID if not already known.
/// Searches the specified guild and stores it into this instance if found.
/// Places the ID into when and if the result is known.
///
/// The entity type to which this instance corresponds to.
/// If known, outputs the ID of the corresponding entity.
/// Specifies if the internal ID value should be stored if a match is found.
/// True if the ID is known.
[Obsolete]
public bool TryResolve(SocketGuild searchGuild, out ulong id, bool keepId, EntityType searchType)
{
if (Id.HasValue)
{
id = Id.Value;
return true;
}
if (searchType != EntityType.Unspecified && Type == EntityType.Unspecified) Type = searchType;
if (string.IsNullOrWhiteSpace(Name))
{
id = default;
return false;
}
Predicate resolver;
IEnumerable collection;
switch (Type)
{
case EntityType.Role:
collection = searchGuild.Roles;
resolver = ResolveTryRole;
break;
case EntityType.Channel:
collection = searchGuild.TextChannels;
resolver = ResolveTryChannel;
break;
case EntityType.User:
collection = searchGuild.Users;
resolver = ResolveTryUser;
break;
default:
id = default;
return false;
}
foreach (var item in collection)
{
if (resolver.Invoke(item))
{
if (keepId) Id = item.Id;
id = Id.Value;
return true;
}
}
id = default;
return false;
}
private bool ResolveTryRole(ISnowflakeEntity entity)
{
var r = (SocketRole)entity;
return string.Equals(r.Name, this.Name, StringComparison.InvariantCultureIgnoreCase);
}
private bool ResolveTryChannel(ISnowflakeEntity entity)
{
var c = (SocketTextChannel)entity;
return string.Equals(c.Name, this.Name, StringComparison.InvariantCultureIgnoreCase);
}
private bool ResolveTryUser(ISnowflakeEntity entity)
{
var u = (SocketGuildUser)entity;
// Check username first, then nickname
return string.Equals(u.Username, this.Name, StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(u.Nickname, this.Name, StringComparison.InvariantCultureIgnoreCase);
}
#endregion
///
/// Returns the appropriate prefix corresponding to an EntityType.
///
public static char Prefix(EntityType t)
{
switch (t)
{
case EntityType.Role: return '&';
case EntityType.Channel: return '#';
case EntityType.User: return '@';
default: return '\0';
}
}
///
/// Returns a string representation of this item in proper EntityName format.
///
public override string ToString()
{
char pf = Prefix(Type);
if (Id.HasValue && Name != null)
return $"{pf}{Id.Value}::{Name}";
else if (Id.HasValue)
return $"{pf}{Id}";
else
return $"{pf}{Name}";
}
#region Helper methods
///
/// Attempts to find the corresponding role within the given guild.
///
/// The guild in which to search for the role.
///
/// Specifies if this EntityName instance should keep the snowflake ID of the
/// corresponding role found in this guild, if it is not already known by this instance.
///
///
public SocketRole FindRoleIn(SocketGuild guild, bool updateMissingID = false)
{
if (this.Type != EntityType.Role)
throw new ArgumentException("This EntityName instance must correspond to a Role.");
bool dirty = false; // flag to update ID if possible regardless of updateMissingID setting
if (this.Id.HasValue)
{
var role = guild.GetRole(Id.Value);
if (role != null) return role;
else dirty = true; // only set if ID already existed but is now invalid
}
var r = guild.Roles.FirstOrDefault(rq => string.Equals(rq.Name, this.Name, StringComparison.OrdinalIgnoreCase));
if (r != null && (updateMissingID || dirty)) this.Id = r.Id;
return r;
}
#endregion
}
}