using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
/// 
/// The audio normalization task.
/// 
public partial class AudioNormalizationTask : IScheduledTask
{
    private readonly IItemRepository _itemRepository;
    private readonly ILibraryManager _libraryManager;
    private readonly IMediaEncoder _mediaEncoder;
    private readonly IApplicationPaths _applicationPaths;
    private readonly ILocalizationManager _localization;
    private readonly ILogger _logger;
    /// 
    /// Initializes a new instance of the  class.
    /// 
    /// Instance of the  interface.
    /// Instance of the  interface.
    /// Instance of the  interface.
    /// Instance of the  interface.
    /// Instance of the  interface.
    /// Instance of the  interface.
    public AudioNormalizationTask(
        IItemRepository itemRepository,
        ILibraryManager libraryManager,
        IMediaEncoder mediaEncoder,
        IApplicationPaths applicationPaths,
        ILocalizationManager localizationManager,
        ILogger logger)
    {
        _itemRepository = itemRepository;
        _libraryManager = libraryManager;
        _mediaEncoder = mediaEncoder;
        _applicationPaths = applicationPaths;
        _localization = localizationManager;
        _logger = logger;
    }
    /// 
    public string Name => _localization.GetLocalizedString("TaskAudioNormalization");
    /// 
    public string Description => _localization.GetLocalizedString("TaskAudioNormalizationDescription");
    /// 
    public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
    /// 
    public string Key => "AudioNormalization";
    [GeneratedRegex(@"^\s+I:\s+(.*?)\s+LUFS")]
    private static partial Regex LUFSRegex();
    /// 
    public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
    {
        foreach (var library in _libraryManager.RootFolder.Children)
        {
            var libraryOptions = _libraryManager.GetLibraryOptions(library);
            if (!libraryOptions.EnableLUFSScan)
            {
                continue;
            }
            // Album gain
            var albums = _libraryManager.GetItemList(new InternalItemsQuery
            {
                IncludeItemTypes = [BaseItemKind.MusicAlbum],
                Parent = library,
                Recursive = true
            });
            foreach (var a in albums)
            {
                if (a.NormalizationGain.HasValue || a.LUFS.HasValue)
                {
                    continue;
                }
                // Skip albums that don't have multiple tracks, album gain is useless here
                var albumTracks = ((MusicAlbum)a).Tracks.Where(x => x.IsFileProtocol).ToList();
                if (albumTracks.Count <= 1)
                {
                    continue;
                }
                _logger.LogInformation("Calculating LUFS for album: {Album} with id: {Id}", a.Name, a.Id);
                var tempDir = _applicationPaths.TempDirectory;
                Directory.CreateDirectory(tempDir);
                var tempFile = Path.Join(tempDir, a.Id + ".concat");
                var inputLines = albumTracks.Select(x => string.Format(CultureInfo.InvariantCulture, "file '{0}'", x.Path.Replace("'", @"'\''", StringComparison.Ordinal)));
                await File.WriteAllLinesAsync(tempFile, inputLines, cancellationToken).ConfigureAwait(false);
                try
                {
                    a.LUFS = await CalculateLUFSAsync(
                        string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
                        cancellationToken).ConfigureAwait(false);
                }
                finally
                {
                    File.Delete(tempFile);
                }
            }
            _itemRepository.SaveItems(albums, cancellationToken);
            // Track gain
            var tracks = _libraryManager.GetItemList(new InternalItemsQuery
            {
                MediaTypes = [MediaType.Audio],
                IncludeItemTypes = [BaseItemKind.Audio],
                Parent = library,
                Recursive = true
            });
            foreach (var t in tracks)
            {
                if (t.NormalizationGain.HasValue || t.LUFS.HasValue || !t.IsFileProtocol)
                {
                    continue;
                }
                t.LUFS = await CalculateLUFSAsync(string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), cancellationToken);
            }
            _itemRepository.SaveItems(tracks, cancellationToken);
        }
    }
    /// 
    public IEnumerable GetDefaultTriggers()
    {
        return
        [
            new TaskTriggerInfo
            {
                Type = TaskTriggerInfo.TriggerInterval,
                IntervalTicks = TimeSpan.FromHours(24).Ticks
            }
        ];
    }
    private async Task CalculateLUFSAsync(string inputArgs, CancellationToken cancellationToken)
    {
        var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -";
        using (var process = new Process()
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = _mediaEncoder.EncoderPath,
                Arguments = args,
                RedirectStandardOutput = false,
                RedirectStandardError = true
            },
        })
        {
            try
            {
                _logger.LogDebug("Starting ffmpeg with arguments: {Arguments}", args);
                process.Start();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error starting ffmpeg with arguments: {Arguments}", args);
                return null;
            }
            using var reader = process.StandardError;
            await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
            {
                Match match = LUFSRegex().Match(line);
                if (match.Success)
                {
                    return float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
                }
            }
            _logger.LogError("Failed to find LUFS value in output");
            return null;
        }
    }
}