// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Encoders.cs" company="HandBrake Project (http://handbrake.fr)">
//   This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
// </copyright>
// <summary>
//   The encoders.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace HandBrake.Interop
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using HandBrake.Interop.HbLib;
    using HandBrake.Interop.Helpers;
    using HandBrake.Interop.Model;
    using HandBrake.Interop.Model.Encoding;
    using HandBrake.Interop.Model.Scan;

    /// <summary>
	/// The encoders.
	/// </summary>
	public static class HandBrakeEncoderHelpers
	{
		/// <summary>
		/// The audio encoders.
		/// </summary>
		private static List<HBAudioEncoder> audioEncoders;

		/// <summary>
		/// The video encoders.
		/// </summary>
		private static List<HBVideoEncoder> videoEncoders;

		/// <summary>
		/// Video framerates in pts.
		/// </summary>
		private static List<HBRate> videoFramerates; 

		/// <summary>
		/// List of HandBrake mixdowns.
		/// </summary>
		private static List<HBMixdown> mixdowns;

		/// <summary>
		/// List of HandBrake containers.
		/// </summary>
		private static List<HBContainer> containers; 

		/// <summary>
		/// The audio bitrates.
		/// </summary>
		private static List<int> audioBitrates;

		/// <summary>
		/// Audio sample rates in Hz.
		/// </summary>
		private static List<HBRate> audioSampleRates; 

		/// <summary>
        /// Initializes static members of the HandBrakeEncoderHelpers class.
		/// </summary>
        static HandBrakeEncoderHelpers()
		{
			HandBrakeUtils.EnsureGlobalInit();
		}

		/// <summary>
		/// Gets a list of supported audio encoders.
		/// </summary>
		public static List<HBAudioEncoder> AudioEncoders
		{
			get
			{
				if (audioEncoders == null)
				{
                    audioEncoders = InteropUtilities.ToListFromIterator<hb_encoder_s, HBAudioEncoder>(HBFunctions.hb_audio_encoder_get_next, HandBrakeUnitConversionHelpers.NativeToAudioEncoder);
				}

				return audioEncoders;
			}
		}

		/// <summary>
		/// Gets a list of supported video encoders.
		/// </summary>
		public static List<HBVideoEncoder> VideoEncoders
		{
			get
			{
				if (videoEncoders == null)
				{
                    videoEncoders = InteropUtilities.ToListFromIterator<hb_encoder_s, HBVideoEncoder>(HBFunctions.hb_video_encoder_get_next, HandBrakeUnitConversionHelpers.NativeToVideoEncoder);
				}

				return videoEncoders;
			}
		}

		/// <summary>
		/// Gets a list of supported video framerates (in pts).
		/// </summary>
		public static List<HBRate> VideoFramerates
		{
			get
			{
				if (videoFramerates == null)
				{
                    videoFramerates = InteropUtilities.ToListFromIterator<hb_rate_s, HBRate>(HBFunctions.hb_video_framerate_get_next, HandBrakeUnitConversionHelpers.NativeToRate);
				}

				return videoFramerates;
			}
		} 

		/// <summary>
		/// Gets a list of supported mixdowns.
		/// </summary>
		public static List<HBMixdown> Mixdowns
		{
			get
			{
				if (mixdowns == null)
				{
                    mixdowns = InteropUtilities.ToListFromIterator<hb_mixdown_s, HBMixdown>(HBFunctions.hb_mixdown_get_next, HandBrakeUnitConversionHelpers.NativeToMixdown);
				}

				return mixdowns;
			}
		}

		/// <summary>
		/// Gets a list of supported audio bitrates.
		/// </summary>
		public static List<int> AudioBitrates
		{
			get
			{
				if (audioBitrates == null)
				{
					audioBitrates = InteropUtilities.ToListFromIterator<hb_rate_s, int>(HBFunctions.hb_audio_bitrate_get_next, b => b.rate);
				}

				return audioBitrates;
			}
		}

		/// <summary>
		/// Gets a list of supported audio sample rates (in Hz).
		/// </summary>
		public static List<HBRate> AudioSampleRates
		{
			get
			{
				if (audioSampleRates == null)
				{
                    audioSampleRates = InteropUtilities.ToListFromIterator<hb_rate_s, HBRate>(HBFunctions.hb_audio_samplerate_get_next, HandBrakeUnitConversionHelpers.NativeToRate);
				}

				return audioSampleRates;
			}
		}

		/// <summary>
		/// Gets a list of supported containers.
		/// </summary>
		public static List<HBContainer> Containers
		{
			get
			{
				if (containers == null)
				{
                    containers = InteropUtilities.ToListFromIterator<hb_container_s, HBContainer>(HBFunctions.hb_container_get_next, HandBrakeUnitConversionHelpers.NativeToContainer);
				}

				return containers;
			}
		}

		/// <summary>
		/// Gets a value indicating whether SRT subtitles can be burnt in.
		/// </summary>
		public static bool CanBurnSrt
		{
			get
			{
				return HBFunctions.hb_subtitle_can_burn((int)hb_subtitle_s_subsource.SRTSUB) > 0;
			}
		}

		/// <summary>
		/// Gets the audio encoder with the specified short name.
		/// </summary>
		/// <param name="shortName">
		/// The name of the audio encoder.
		/// </param>
		/// <returns>
		/// The requested audio encoder.
		/// </returns>
		public static HBAudioEncoder GetAudioEncoder(string shortName)
		{
			return AudioEncoders.SingleOrDefault(e => e.ShortName == shortName);
		}

		/// <summary>
		/// Gets the audio encoder with the specified codec ID.
		/// </summary>
		/// <param name="codecId">
		/// The ID of the audio encoder.
		/// </param>
		/// <returns>
		/// The requested audio encoder.
		/// </returns>
		public static HBAudioEncoder GetAudioEncoder(int codecId)
		{
			return AudioEncoders.SingleOrDefault(e => e.Id == codecId);
		}

		/// <summary>
		/// Gets the video encoder with the specified short name.
		/// </summary>
		/// <param name="shortName">
		/// The name of the video encoder.
		/// </param>
		/// <returns>
		/// The requested video encoder.
		/// </returns>
		public static HBVideoEncoder GetVideoEncoder(string shortName)
		{
			return VideoEncoders.SingleOrDefault(e => e.ShortName == shortName);
		}

		/// <summary>
		/// Gets the mixdown with the specified short name.
		/// </summary>
		/// <param name="shortName">
		/// The name of the mixdown.
		/// </param>
		/// <returns>
		/// The requested mixdown.
		/// </returns>
		public static HBMixdown GetMixdown(string shortName)
		{
			return Mixdowns.SingleOrDefault(m => m.ShortName == shortName);
		}

		/// <summary>
		/// Gets the container with the specified short name.
		/// </summary>
		/// <param name="shortName">
		/// The name of the container.
		/// </param>
		/// <returns>
		/// The requested container.
		/// </returns>
		public static HBContainer GetContainer(string shortName)
		{
			return Containers.SingleOrDefault(c => c.ShortName == shortName);
		}

		/// <summary>
		/// Determines if the given encoder is compatible with the given track.
		/// </summary>
		/// <param name="track">
		/// The audio track to examine.
		/// </param>
		/// <param name="encoder">
		/// The encoder to examine.
		/// </param>
		/// <returns>
		/// True if the given encoder is comatible with the given audio track.
		/// </returns>
		/// <remarks>
		/// Only works with passthrough encoders.
		/// </remarks>
		public static bool AudioEncoderIsCompatible(AudioTrack track, HBAudioEncoder encoder)
		{
			return (track.CodecId & encoder.Id) > 0;
		}

		/// <summary>
		/// Determines if the given mixdown supports the given channel layout.
		/// </summary>
		/// <param name="mixdown">
		/// The mixdown to evaluate.
		/// </param>
		/// <param name="layout">
		/// The channel layout to evaluate.
		/// </param>
		/// <returns>
		/// True if the mixdown supports the given channel layout.
		/// </returns>
		public static bool MixdownHasRemixSupport(HBMixdown mixdown, ulong layout)
		{
			return HBFunctions.hb_mixdown_has_remix_support(mixdown.Id, layout) > 0;
		}

		/// <summary>
		/// Determines if the given encoder supports the given mixdown.
		/// </summary>
		/// <param name="mixdown">
		/// The mixdown to evaluate.
		/// </param>
		/// <param name="encoder">
		/// The encoder to evaluate.
		/// </param>
		/// <returns>
		/// True if the encoder supports the mixdown.
		/// </returns>
		public static bool MixdownHasCodecSupport(HBMixdown mixdown, HBAudioEncoder encoder)
		{
			return HBFunctions.hb_mixdown_has_codec_support(mixdown.Id, (uint) encoder.Id) > 0;
		}

		/// <summary>
		/// Determines if DRC can be applied to the given track with the given encoder.
		/// </summary>
		/// <param name="track">
		/// The track to apply DRC to.
		/// </param>
		/// <param name="encoder">
		/// The encoder to use for DRC.
		/// </param>
		/// <param name="title">
		/// The title.
		/// </param>
		/// <returns>
		/// True if DRC can be applied to the track with the given encoder.
		/// </returns>
		public static bool CanApplyDrc(AudioTrack track, HBAudioEncoder encoder, int title)
		{
		    return HBFunctions.hb_audio_can_apply_drc2(HandBrakeInstanceManager.LastScanHandle, title, track.TrackNumber, encoder.Id) > 0;
		}

		/// <summary>
		/// Determines if the given input audio codec can be passed through.
		/// </summary>
		/// <param name="codecId">
		/// The input codec to consider.
		/// </param>
		/// <returns>
		/// True if the codec can be passed through.
		/// </returns>
		public static bool CanPassthroughAudio(int codecId)
		{
			return (codecId & NativeConstants.HB_ACODEC_PASS_MASK) > 0;
		}

		/// <summary>
		/// Sanitizes a mixdown given the output codec and input channel layout.
		/// </summary>
		/// <param name="mixdown">
		/// The desired mixdown.
		/// </param>
		/// <param name="encoder">
		/// The output encoder to be used.
		/// </param>
		/// <param name="layout">
		/// The input channel layout.
		/// </param>
		/// <returns>
		/// A sanitized mixdown value.
		/// </returns>
		public static HBMixdown SanitizeMixdown(HBMixdown mixdown, HBAudioEncoder encoder, ulong layout)
		{
			int sanitizedMixdown = HBFunctions.hb_mixdown_get_best((uint)encoder.Id, layout, mixdown.Id);
			return Mixdowns.Single(m => m.Id == sanitizedMixdown);
		}

		/// <summary>
		/// Gets the default mixdown for the given audio encoder and channel layout.
		/// </summary>
		/// <param name="encoder">
		/// The output codec to be used.
		/// </param>
		/// <param name="layout">
		/// The input channel layout.
		/// </param>
		/// <returns>
		/// The default mixdown for the given codec and channel layout.
		/// </returns>
		public static HBMixdown GetDefaultMixdown(HBAudioEncoder encoder, ulong layout)
		{
			int defaultMixdown = HBFunctions.hb_mixdown_get_default((uint)encoder.Id, layout);
			return Mixdowns.Single(m => m.Id == defaultMixdown);
		}

		/// <summary>
		/// Gets the bitrate limits for the given audio codec, sample rate and mixdown.
		/// </summary>
		/// <param name="encoder">
		/// The audio encoder used.
		/// </param>
		/// <param name="sampleRate">
		/// The sample rate used (Hz).
		/// </param>
		/// <param name="mixdown">
		/// The mixdown used.
		/// </param>
		/// <returns>
		/// Limits on the audio bitrate for the given settings.
		/// </returns>
		public static BitrateLimits GetBitrateLimits(HBAudioEncoder encoder, int sampleRate, HBMixdown mixdown)
		{
			int low = 0;
			int high = 0;

			HBFunctions.hb_audio_bitrate_get_limits((uint)encoder.Id, sampleRate, mixdown.Id, ref low, ref high);

			return new BitrateLimits { Low = low, High = high };
		}

		/// <summary>
		/// Gets the video quality limits for the given video codec.
		/// </summary>
		/// <param name="encoder">
		/// The video encoder to check.
		/// </param>
		/// <returns>
		/// Limits on the video quality for the encoder.
		/// </returns>
		public static VideoQualityLimits GetVideoQualityLimits(HBVideoEncoder encoder)
		{
			float low = 0;
			float high = 0;
			float granularity = 0;
			int direction = 0;

			HBFunctions.hb_video_quality_get_limits((uint)encoder.Id, ref low, ref high, ref granularity, ref direction);

			return new VideoQualityLimits
				{
					Low = low, 
					High = high, 
					Granularity = granularity, 
					Ascending = direction == 0
				};
		}

		/// <summary>
		/// Sanitizes an audio bitrate given the output codec, sample rate and mixdown.
		/// </summary>
		/// <param name="audioBitrate">
		/// The desired audio bitrate.
		/// </param>
		/// <param name="encoder">
		/// The output encoder to be used.
		/// </param>
		/// <param name="sampleRate">
		/// The output sample rate to be used.
		/// </param>
		/// <param name="mixdown">
		/// The mixdown to be used.
		/// </param>
		/// <returns>
		/// A sanitized audio bitrate.
		/// </returns>
		public static int SanitizeAudioBitrate(int audioBitrate, HBAudioEncoder encoder, int sampleRate, HBMixdown mixdown)
		{
			return HBFunctions.hb_audio_bitrate_get_best((uint)encoder.Id, audioBitrate, sampleRate, mixdown.Id);
		}

		/// <summary>
		/// Gets the default audio bitrate for the given parameters.
		/// </summary>
		/// <param name="encoder">
		/// The encoder to use.
		/// </param>
		/// <param name="sampleRate">
		/// The sample rate to use.
		/// </param>
		/// <param name="mixdown">
		/// The mixdown to use.
		/// </param>
		/// <returns>
		/// The default bitrate for these parameters.
		/// </returns>
		public static int GetDefaultBitrate(HBAudioEncoder encoder, int sampleRate, HBMixdown mixdown)
		{
			return HBFunctions.hb_audio_bitrate_get_default((uint) encoder.Id, sampleRate, mixdown.Id);
		}

		/// <summary>
		/// Gets limits on audio quality for a given encoder.
		/// </summary>
		/// <param name="encoderId">
		/// The audio encoder ID.
		/// </param>
		/// <returns>
		/// Limits on the audio quality for the given encoder.
		/// </returns>
		internal static RangeLimits GetAudioQualityLimits(int encoderId)
		{
			float low = 0, high = 0, granularity = 0;
			int direction = 0;
			HBFunctions.hb_audio_quality_get_limits((uint)encoderId, ref low, ref high, ref granularity, ref direction);

			return new RangeLimits
			{
				Low = low, 
				High = high, 
				Granularity = granularity, 
				Ascending = direction == 0
			};
		}

		/// <summary>
		/// Gets limits on audio compression for a given encoder.
		/// </summary>
		/// <param name="encoderId">
		/// The audio encoder ID.
		/// </param>
		/// <returns>
		/// Limits on the audio compression for the given encoder.
		/// </returns>
		internal static RangeLimits GetAudioCompressionLimits(int encoderId)
		{
			float low = 0, high = 0, granularity = 0;
			int direction = 0;
			HBFunctions.hb_audio_compression_get_limits((uint)encoderId, ref low, ref high, ref granularity, ref direction);

			return new RangeLimits
			{
				Low = low, 
				High = high, 
				Granularity = granularity, 
				Ascending = direction == 0
			};
		}
	}
}