﻿using System;

using Ghul.Collections;


namespace Ghul.Timelines
{
	/// <summary>
	/// A timeline that caches accessed values of another.
	/// </summary>
	/// <typeparam name="T">The type of the timeline values.</typeparam>
	internal class CachedTimeline<T> : ITimeline<T>
	{
		/// <summary>
		/// The fixed positive duration, null if the timeline is not bounded.
		/// </summary>
		public TimeSpan? Duration => timeline.Duration;


		/// <summary>
		/// The cached timeline.
		/// </summary>
		private ITimeline<T> timeline;

		/// <summary>
		/// The value cache to use.
		/// </summary>
		private ICache<TimeSpan, T> cache;


		/// <summary>
		/// Creates a new timeline that caches accessed values of another.
		/// </summary>
		/// <param name="timeline">The cached timeline.</param>
		/// <param name="cache">The value cache to use.</param>
		/// <exception cref="ArgumentNullException">If timeline is null.</exception>
		/// <exception cref="ArgumentNullException">If cache is null.</exception>
		public CachedTimeline(ITimeline<T> timeline, ICache<TimeSpan, T> cache)
		{
			Ensure.IsNotNull(timeline, nameof(timeline));
			Ensure.IsNotNull(cache, nameof(cache));

			this.timeline = timeline;
			this.cache = cache;
		}


		/// <summary>
		/// Retrieves the timeline value associated with the specified elapsed time.
		/// </summary>
		/// <param name="elapsedTime">The elapsed timeline time.</param>
		/// <returns>The timeline value associated with the specified elapsed time.</returns>
		/// <remarks>
		/// This method must correctly handle elapsedTime outside the range from <see cref="TimeSpan.Zero"/> to
		/// <see cref="ITimeline{T}.Duration"/>.
		/// </remarks>
		public T GetValue(TimeSpan elapsedTime)
		{
			// Use cached value if present
			if(cache.TryGetValue(elapsedTime, out T value))
			{
				return value;
			}

			// Otherwise, get it from the cached timeline and store it
			value = timeline.GetValue(elapsedTime);
			cache[elapsedTime] = value;

			return value;
		}
	}
}
