Code/Compiled/Extensions.cs
using System;
using System.Collections.Immutable;

namespace Sandbox.MovieMaker.Compiled;

#nullable enable

/// <summary>
/// Helper methods for working with <see cref="MovieClip"/>, <see cref="ICompiledTrack"/>, or <see cref="ICompiledBlock"/>.
/// </summary>
public static class CompiledClipExtensions
{
	/// <summary>
	/// Create a nested <see cref="ICompiledReferenceTrack"/> that targets a <see cref="Sandbox.GameObject"/> with
	/// the given <paramref name="name"/>.
	/// </summary>
	public static CompiledReferenceTrack<GameObject> GameObject( this CompiledReferenceTrack<GameObject> track, string name, Guid? id = null, Guid? referenceId = null ) =>
		new( id ?? Guid.NewGuid(), name, track, referenceId );

	/// <summary>
	/// Create a nested <see cref="ICompiledReferenceTrack"/> that targets a <see cref="Sandbox.Component"/> with
	/// the given <paramref name="type"/>.
	/// </summary>
	public static ICompiledReferenceTrack Component( this CompiledReferenceTrack<GameObject> track, Type type, Guid? id = null, Guid? referenceId = null ) =>
		TypeLibrary.GetType( typeof(CompiledReferenceTrack<>) )
			.CreateGeneric<ICompiledReferenceTrack>( [type], [id ?? Guid.NewGuid(), type.Name, track, referenceId] );

	/// <summary>
	/// Create a nested <see cref="ICompiledReferenceTrack"/> that targets a <see cref="Sandbox.Component"/> with
	/// the type <typeparamref name="T"/>.
	/// </summary>
	public static CompiledReferenceTrack<T> Component<T>( this CompiledReferenceTrack<GameObject> track, Guid? id = null )
		where T : Component => new( id ?? Guid.NewGuid(), typeof(T).Name, track );

	/// <summary>
	/// Create a nested <see cref="ICompiledPropertyTrack"/> that targets a property with the given <paramref name="name"/>
	/// in the parent track.
	/// </summary>
	public static CompiledPropertyTrack<T> Property<T>( this ICompiledTrack track, string name, IEnumerable<ICompiledPropertyBlock<T>>? blocks = null ) =>
		new( name, track, blocks?.ToImmutableArray() ?? ImmutableArray<ICompiledPropertyBlock<T>>.Empty );

	public static ICompiledPropertyTrack Property( this ICompiledTrack track, string name, Type type, IEnumerable<ICompiledPropertyBlock>? blocks = null ) =>
		TypeLibrary.GetType( typeof(CompiledPropertyTrack<>) )
			.CreateGeneric<ICompiledPropertyTrack>( [type], [name, track, blocks ?? []] );

	/// <summary>
	/// Returns a clone of <paramref name="track"/> with an appended <see cref="CompiledConstantBlock{T}"/> with the given
	/// <paramref name="timeRange"/> and <paramref name="value"/>.
	/// </summary>
	public static CompiledPropertyTrack<T> WithConstant<T>( this CompiledPropertyTrack<T> track,
		MovieTimeRange timeRange, T value )
	{
		return track with
		{
			// ReSharper disable once UseCollectionExpression
			Blocks = track.Blocks.Concat( [new CompiledConstantBlock<T>( timeRange, value )] ).ToImmutableArray()
		};
	}

	/// <summary>
	/// Returns a clone of <paramref name="track"/> with an appended <see cref="CompiledSampleBlock{T}"/> with the given
	/// <paramref name="timeRange"/>, <paramref name="sampleRate"/>, and list of sample <paramref name="values"/>.
	/// </summary>
	public static CompiledPropertyTrack<T> WithSamples<T>( this CompiledPropertyTrack<T> track,
		MovieTimeRange timeRange, int sampleRate, IEnumerable<T> values )
	{
		return track with
		{
			// ReSharper disable UseCollectionExpression
			Blocks = track.Blocks.Concat( [new CompiledSampleBlock<T>( timeRange, 0d, sampleRate, values.ToImmutableArray() )] ).ToImmutableArray()
			// ReSharper enable UseCollectionExpression
		};
	}
	
	/// <summary>
	/// Interpreting <paramref name="samples"/> as an array of samples taken at a given <paramref name="sampleRate"/>, read
	/// a sample from the array at the given <paramref name="time"/> offset from the first sample. Optionally uses <paramref name="interpolator"/>
	/// to interpolate between samples.
	/// </summary>
	public static T Sample<T>( this IReadOnlyList<T> samples, MovieTime time, int sampleRate, IInterpolator<T>? interpolator )
	{
		var i0 = time.GetFrameIndex( sampleRate, out var remainder );
		var i1 = i0 + 1;

		if ( i0 < 0 ) return samples[0];
		if ( i1 >= samples.Count ) return samples[^1];

		var x0 = samples[i0];

		if ( interpolator is null )
		{
			return x0;
		}

		var t = (float)(remainder.TotalSeconds * sampleRate);
		var x1 = samples[i1];

		return interpolator.Interpolate( x0, x1, t );
	}
}