Rides/TrackRides/TrackElementFilter.cs
using System;

namespace HC3.Rides;

#nullable enable

/// <summary>
/// Used by <see cref="TrackBuilder"/> to search for track elements.
/// </summary>
/// <param name="Direction">Should the track turn left / right, or go straight?</param>
/// <param name="Banking">What angle should the track bank at?</param>
/// <param name="Incline">What angle should the track climb / descend at?</param>
/// <param name="Feature">Should the track have a special feature, like a lift hill?</param>
public readonly record struct TrackElementFilter(
	TurnDirection Direction = TurnDirection.Straight,
	TrackBanking Banking = TrackBanking.None,
	TrackIncline Incline = TrackIncline.None,
	TrackFeature Feature = TrackFeature.None ) : IComparable<TrackElementFilter>
{
	/// <summary>
	/// Test if the given <paramref name="element"/> matches this filter.
	/// </summary>
	public bool IsMatch( TrackElement element ) =>
		element.Definition.Feature == Feature &&
		element.Direction == Direction &&
		element.EndBanking == Banking &&
		element.GetInclineCategory( TrackBuildKind.Append ) == Incline;

	/// <summary>
	/// Comparison for sorting track elements, ranking flat level track first.
	/// </summary>
	public int CompareTo( TrackElementFilter other )
	{
		var inclineComparison = Incline.Ordinal().CompareTo( other.Incline.Ordinal() );
		if ( inclineComparison != 0 )
		{
			return inclineComparison;
		}

		var directionComparison = Direction.Ordinal().CompareTo( other.Direction.Ordinal() );
		if ( directionComparison != 0 )
		{
			return directionComparison;
		}

		var bankingComparison = Banking.Ordinal().CompareTo( other.Banking.Ordinal() );
		if ( bankingComparison != 0 )
		{
			return bankingComparison;
		}

		return Feature.CompareTo( other.Feature );
	}

	/// <summary>
	/// How different is <paramref name="other"/> from this filter?
	/// Weighted to prefer similar inclines, then turn directions, then banking, and cares least about features.
	/// </summary>
	public int GetDistance( TrackElementFilter other )
	{
		var dist = 0;

		dist += Feature == other.Feature ? 0 : 1;
		dist += Math.Abs( Banking.CompareTo( other.Banking ) ) * 2;
		dist += Math.Abs( Direction.CompareTo( other.Direction ) ) * 4;
		dist += Math.Abs( Incline.CompareTo( other.Incline ) ) * 8;

		return dist;
	}

	/// <summary>
	/// Find the filter that would match the given <paramref name="element"/>.
	/// </summary>
	public static TrackElementFilter FromElement( TrackElement element, TrackBuildKind kind = TrackBuildKind.Append )
	{
		return kind == TrackBuildKind.Append
			? new TrackElementFilter(
				element.Direction,
				element.EndBanking,
				element.GetInclineCategory( kind ),
				element.Definition.Feature )
			: new TrackElementFilter(
				element.Direction.Inverse(),
				element.StartBanking.Inverse(),
				element.GetInclineCategory( kind ),
				element.Definition.Feature );
	}
}