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();
}
}
}