using System; using SabreTools.Data.Models.Metadata; using static SabreTools.Metadata.Filter.Constants; namespace SabreTools.Metadata.Filter { /// /// Represents a single filter key /// public class FilterKey { #region Properties /// /// Item name associated with the filter /// public readonly string ItemName; /// /// Field name associated with the filter /// public readonly string FieldName; #endregion #region Constants /// /// Cached item type names for filter selection /// #if NET5_0_OR_GREATER private static readonly string[] _datItemTypeNames = Enum.GetNames(); #else private static readonly string[] _datItemTypeNames = Enum.GetNames(typeof(ItemType)); #endif #endregion /// /// Validating combined key constructor /// public FilterKey(string? key) { if (!ParseFilterId(key, out string itemName, out string fieldName)) throw new ArgumentException($"{nameof(key)} could not be parsed", nameof(key)); ItemName = itemName; FieldName = fieldName; } /// /// Validating discrete value constructor /// public FilterKey(string itemName, string fieldName) { if (!ParseFilterId(ref itemName, ref fieldName)) throw new ArgumentException($"{nameof(itemName)} was not recognized", nameof(itemName)); ItemName = itemName; FieldName = fieldName; } /// public override string ToString() => $"{ItemName}.{FieldName}"; /// /// Parse a filter ID string into the item name and field name, if possible /// private static bool ParseFilterId(string? itemFieldString, out string itemName, out string fieldName) { // Set default values itemName = string.Empty; fieldName = string.Empty; // If we don't have a filter ID, we can't do anything if (string.IsNullOrEmpty(itemFieldString)) return false; // If we only have one part, we can't do anything string[] splitFilter = itemFieldString!.Split('.'); if (splitFilter.Length < 2) return false; // Set and sanitize the filter ID itemName = splitFilter[0]; fieldName = string.Join(".", splitFilter, 1, splitFilter.Length - 1); return ParseFilterId(ref itemName, ref fieldName); } /// /// Parse a filter ID string into the item name and field name, if possible /// private static bool ParseFilterId(ref string itemName, ref string fieldName) { // If we don't have a filter ID, we can't do anything if (string.IsNullOrEmpty(itemName) || string.IsNullOrEmpty(fieldName)) return false; // Return santized values based on the split ID return itemName.ToLowerInvariant() switch { // Header "header" => ParseHeaderFilterId(ref itemName, ref fieldName), // Machine "game" => ParseMachineFilterId(ref itemName, ref fieldName), "machine" => ParseMachineFilterId(ref itemName, ref fieldName), "resource" => ParseMachineFilterId(ref itemName, ref fieldName), "set" => ParseMachineFilterId(ref itemName, ref fieldName), // DatItem "datitem" => ParseDatItemFilterId(ref itemName, ref fieldName), "item" => ParseDatItemFilterId(ref itemName, ref fieldName), _ => ParseDatItemFilterId(ref itemName, ref fieldName), }; } /// /// Parse and validate header fields /// private static bool ParseHeaderFilterId(ref string itemName, ref string fieldName) { // Get if there's a match to a property string localFieldName = fieldName; string? propertyMatch = Array.Find(HeaderKeys, c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); if (propertyMatch is null) return false; // Return the sanitized ID itemName = "header"; fieldName = propertyMatch.ToLowerInvariant(); return true; } /// /// Parse and validate machine/game fields /// private static bool ParseMachineFilterId(ref string itemName, ref string fieldName) { // Get if there's a match to a property string localFieldName = fieldName; string? propertyMatch = Array.Find(MachineKeys, c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); if (propertyMatch is null) return false; // Return the sanitized ID itemName = "machine"; fieldName = propertyMatch.ToLowerInvariant(); return true; } /// /// Parse and validate item fields /// private static bool ParseDatItemFilterId(ref string itemName, ref string fieldName) { // Special case if the item name is reserved if (string.Equals(itemName, "datitem", StringComparison.OrdinalIgnoreCase) || string.Equals(itemName, "item", StringComparison.OrdinalIgnoreCase)) { // Handle item type if (string.Equals(fieldName, "type", StringComparison.OrdinalIgnoreCase)) { itemName = "item"; fieldName = "type"; return true; } // If we get any matches string localFieldName = fieldName; string? matchedType = Array.Find(_datItemTypeNames, t => DatItemContainsField(t, localFieldName)); if (matchedType is not null) { // Check for a matching field string? matchedField = GetMatchingField(matchedType, fieldName); if (matchedField is null) return false; itemName = "item"; fieldName = matchedField; return true; } } else { // Check for a matching field string? matchedField = GetMatchingField(itemName, fieldName); if (matchedField is null) return false; itemName = itemName.ToLowerInvariant(); fieldName = matchedField; return true; } // Nothing was found return false; } /// /// Determine if an item type contains a field /// private static bool DatItemContainsField(string itemName, string fieldName) => GetMatchingField(itemName, fieldName) is not null; /// /// Determine if an item type contains a field /// private static string? GetMatchingField(string itemName, string fieldName) { // Get the set of properties string[]? properties = itemName.ToLowerInvariant() switch { "adjuster" => AdjusterKeys, "archive" => ArchiveKeys, "biosset" => BiossetKeys, "chip" => ChipKeys, "configuration" => ConfigurationKeys, "conflocation" => ConfLocationKeys, "confsetting" => ConfSettingKeys, "control" => ControlKeys, "dataarea" => DataAreaKeys, "device" => DeviceKeys, "deviceref" => DeviceRefKeys, "diplocation" => DipLocationKeys, "dipswitch" => DipSwitchKeys, "dipvalue" => DipValueKeys, "disk" => DiskKeys, "diskarea" => DiskAreaKeys, "display" => DisplayKeys, "driver" => DriverKeys, "feature" or "partfeature" => FeatureKeys, "game" or "machine" or "resource" or "set" => MachineKeys, "header" => HeaderKeys, "info" => InfoKeys, "input" => InputKeys, "media" => MediaKeys, "original" => OriginalKeys, "part" => PartKeys, "port" => PortKeys, "ramoption" => RamOptionKeys, "release" => ReleaseKeys, "releasedetails" => ReleaseDetailsKeys, "rom" => RomKeys, "sample" => SampleKeys, "serials" => SerialsKeys, "sharedfeat" => SharedFeatKeys, "slot" => SlotKeys, "slotoption" => SlotOptionKeys, "softwarelist" => SoftwareListKeys, "sound" => SoundKeys, "sourcedetails" => SourceDetailsKeys, "video" => VideoKeys, _ => null, }; if (properties is null) return null; // Get if there's a match to a property string? propertyMatch = Array.Find(properties, c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); return propertyMatch?.ToLowerInvariant(); } } }