// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UserSettingService.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 User Setting Service
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace HandBrakeWPF.Services
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text.Json;

    using HandBrake.App.Core.Utilities;
    using HandBrake.Interop.Interop;
    using HandBrake.Interop.Utilities;

    using HandBrakeWPF.Model;
    using HandBrakeWPF.Model.Options;
    using HandBrakeWPF.Model.Video;
    using HandBrakeWPF.Properties;
    using HandBrakeWPF.Services.Interfaces;
    using HandBrakeWPF.Services.Logging.Interfaces;
    using HandBrakeWPF.Utilities;

    using GeneralApplicationException = HandBrake.App.Core.Exceptions.GeneralApplicationException;
    using SettingChangedEventArgs = EventArgs.SettingChangedEventArgs;
    using SystemInfo = Utilities.SystemInfo;

    /// <summary>
    /// The User Setting Service
    /// </summary>
    public class UserSettingService : IUserSettingService
    {
        private readonly ILog logService;

        private readonly string settingsFile = Path.Combine(DirectoryUtilities.GetUserStoragePath(HandBrakeVersionHelper.IsNightly()), "settings.json");
        private readonly string releaseSettingsFile = Path.Combine(DirectoryUtilities.GetUserStoragePath(false), "settings.json");
        private readonly string nightlySettingsFile = Path.Combine(DirectoryUtilities.GetUserStoragePath(true), "settings.json");
        private Dictionary<string, object> userSettings;

        /// <summary>
        /// Initializes a new instance of the <see cref="UserSettingService"/> class.
        /// </summary>
        public UserSettingService(ILog logService)
        {
            this.logService = logService;
            this.Load();
        }

        /// <summary>
        /// The setting changed.
        /// </summary>
        public event SettingEventHandler SettingChanged;

        /// <summary>
        /// Set the specified user setting.
        /// </summary>
        /// <param name="name">
        /// Name of the property
        /// </param>
        /// <param name="value">
        /// The value to store.
        /// </param>
        public void SetUserSetting(string name, object value)
        {
            this.userSettings[name] = value;
            this.Save();

            this.OnSettingChanged(new SettingChangedEventArgs { Key = name, Value = value });
        }

        /// <summary>
        /// Get user setting for a given key.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <typeparam name="T">
        /// The Type of the setting
        /// </typeparam>
        /// <returns>
        /// The user setting
        /// </returns>
        public T GetUserSetting<T>(string name)
        {
            try
            {
                if (this.userSettings.ContainsKey(name))
                {
                    object settingValue = this.userSettings[name];
                    if (settingValue is JsonElement)
                    {
                        T rawValue = JsonSerializer.Deserialize<T>(
                            ((JsonElement)settingValue).GetRawText(),
                            JsonSettings.Options);
                        return rawValue;
                    }

                    T objectElement = (T)this.userSettings[name];
                    return objectElement;
                }
            }
            catch (Exception e)
            {
                this.logService.LogMessage("Unable to fetch user setting:  " + name + " - " + e);
                return GetDefaultValue<T>(name);
            }

            return default(T);
        }

        public void ResetSettingsToDefaults()
        {
            this.userSettings.Clear();

            // Set Defaults
            Dictionary<string, object> defaults = this.GetDefaults();
            foreach (var item in defaults.Where(item => !this.userSettings.Keys.Contains(item.Key)))
            {
                this.userSettings.Add(item.Key, item.Value);
                this.Save();
            }
        }

        /// <summary>
        /// The on setting changed.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected virtual void OnSettingChanged(SettingChangedEventArgs e)
        {
            SettingEventHandler handler = this.SettingChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Save the User Settings
        /// </summary>
        private void Save()
        {
            try
            {
                string directory = Path.GetDirectoryName(this.settingsFile);
                if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                using (StreamWriter file = new StreamWriter(new FileStream(this.settingsFile, FileMode.Create, FileAccess.Write)))
                {
                    string appSettings = JsonSerializer.Serialize(this.userSettings, JsonSettings.Options);
                    file.Write(appSettings);
                }
            }
            catch (Exception exc)
            {
                throw new GeneralApplicationException(
                    Resources.UserSettings_AnErrorOccurred,
                    Resources.SettingService_SaveErrorReset,
                    exc);
            }
        }

        /// <summary>
        /// Load the User Settings
        /// </summary>
        private void Load()
        {
            try
            {
                // Load up the users current settings file.
                if (File.Exists(this.settingsFile))
                {
                    using (StreamReader reader = new StreamReader(this.settingsFile))
                    {
                        string appSettings = reader.ReadToEnd();
                        Dictionary<string, object> deserializedSettings = JsonSerializer.Deserialize<Dictionary<string, object>>(appSettings, JsonSettings.Options);

                        this.userSettings = deserializedSettings;
                    }
                }
                else if (HandBrakeVersionHelper.IsNightly() && File.Exists(this.releaseSettingsFile))
                {
                    // Port the release versions config to the nightly.
                    if (!Directory.Exists(DirectoryUtilities.GetUserStoragePath(true)))
                    {
                        Directory.CreateDirectory(DirectoryUtilities.GetUserStoragePath(true));
                    }

                    File.Copy(this.releaseSettingsFile, this.nightlySettingsFile);

                    using (StreamReader reader = new StreamReader(this.settingsFile))
                    {
                        string appSettings = reader.ReadToEnd();
                        Dictionary<string, object> deserializedSettings = JsonSerializer.Deserialize<Dictionary<string, object>>(appSettings, JsonSettings.Options);
                        this.userSettings = deserializedSettings;
                    }
                }
                else
                {
                    this.userSettings = new Dictionary<string, object>();
                }

                // Add any missing / new settings
                Dictionary<string, object> defaults = this.GetDefaults();
                foreach (var item in defaults.Where(item => !this.userSettings.Keys.Contains(item.Key)))
                {
                    this.userSettings.Add(item.Key, item.Value);
                    this.Save();
                }

                this.ResetUnsupportedSettings();
            }
            catch (Exception exc)
            {
                try
                {
                    this.userSettings = this.GetDefaults();
                    if (File.Exists(this.settingsFile))
                    {
                        File.Delete(this.settingsFile);
                    }

                    this.Save();

                    throw new GeneralApplicationException(Resources.UserSettings_YourSettingsHaveBeenReset, Resources.UserSettings_YourSettingsAreCorrupt, exc);
                }
                catch (Exception)
                {
                    throw new GeneralApplicationException(string.Format(Resources.UserSettings_UnableToLoad, this.settingsFile), Resources.UserSettings_UnableToLoadSolution, exc);
                }
            }
        }

        private void ResetUnsupportedSettings()
        {
            // Legacy Settings forced Reset.
            this.userSettings[UserSettingConstants.ScalingMode] = VideoScaler.Lanczos;

            if (SystemInfo.MaximumSimultaneousInstancesSupported < 2)
            {
                this.userSettings[UserSettingConstants.ProcessIsolationEnabled] = false;
                this.userSettings[UserSettingConstants.SimultaneousEncodes] = 1;
            }

            // Handle change of language code zh to zh-CN
            object language;
            if (this.userSettings.TryGetValue(UserSettingConstants.UiLanguage, out language))
            {
                if (language is string and "zh")
                {
                    // Reset to use system language if we have the old zh stored in settings.
                    this.userSettings[UserSettingConstants.UiLanguage] = InterfaceLanguageUtilities.UseSystemLanguage;
                }
            }
        }
        
        /// <summary>
        /// Load Default Settings
        /// </summary>
        /// <returns>
        /// The get defaults.
        /// </returns>
        private Dictionary<string, object> GetDefaults()
        {
            Dictionary<string, object> defaults = new Dictionary<string, object>();

            defaults.Add(UserSettingConstants.Verbosity, 1);

            // General
            defaults.Add(UserSettingConstants.UpdateStatus, false);
            defaults.Add(UserSettingConstants.LastUpdateCheckDate, DateTime.Now.Date.AddDays(-30));
            defaults.Add(UserSettingConstants.DaysBetweenUpdateCheck, 1);
            defaults.Add(UserSettingConstants.DarkThemeMode, AppThemeMode.System);
            defaults.Add(UserSettingConstants.ShowPreviewOnSummaryTab, true);
            defaults.Add(UserSettingConstants.MainWindowMinimize, false);
            defaults.Add(UserSettingConstants.ClearCompletedFromQueue, false);
            defaults.Add(UserSettingConstants.ShowStatusInTitleBar, false);
            defaults.Add(UserSettingConstants.ShowAddAllToQueue, false);
            defaults.Add(UserSettingConstants.ShowAddSelectionToQueue, false);
            defaults.Add(UserSettingConstants.MediaPlayerPath, @"C:\Program Files\VideoLAN\vlc\vlc.exe");
            defaults.Add(UserSettingConstants.PresetMenuDisplayMode, 0);
            defaults.Add(UserSettingConstants.RightToLeftUi, 0); 
            defaults.Add(UserSettingConstants.PresetUiType, 1);
            
            // Output Files
            defaults.Add(UserSettingConstants.AutoNaming, true);
            defaults.Add(UserSettingConstants.AutoNamePath, string.Empty);
            defaults.Add(UserSettingConstants.AutoNameFormat, "{source}");
            defaults.Add(UserSettingConstants.AutonameFilePrePostString, "output_");
            defaults.Add(UserSettingConstants.AutoNameTitleCase, true);
            defaults.Add(UserSettingConstants.AutoNameRemoveUnderscore, true);
            defaults.Add(UserSettingConstants.AutonameFileCollisionBehaviour, 0);
            defaults.Add(UserSettingConstants.UseIsoDateFormat, false);
            defaults.Add(UserSettingConstants.AlwaysUseDefaultPath, true);
            defaults.Add(UserSettingConstants.RemovePunctuation, false);
            defaults.Add(UserSettingConstants.FileOverwriteBehaviour, 0);
            defaults.Add(UserSettingConstants.UseM4v, 0);

            // When Done
            defaults.Add(UserSettingConstants.SendFile, false);
            defaults.Add(UserSettingConstants.WhenCompleteAction, 0);
            defaults.Add(UserSettingConstants.WhenDonePerformActionImmediately, false);
            defaults.Add(UserSettingConstants.PlaySoundWhenDone, false);
            defaults.Add(UserSettingConstants.PlaySoundWhenQueueDone, false);
            defaults.Add(UserSettingConstants.WhenDoneAudioFile, string.Empty);

            // Video
            bool intelDefaultSetting = HandBrakeHardwareEncoderHelper.IsQsvAvailable;
            bool nvidiaDefaultSetting = HandBrakeHardwareEncoderHelper.IsNVEncH264Available;

            defaults.Add(UserSettingConstants.EnableQuickSyncDecoding, intelDefaultSetting);
            defaults.Add(UserSettingConstants.EnableQuickSyncHyperEncode, false);
            defaults.Add(UserSettingConstants.UseQSVDecodeForNonQSVEnc, false);
            defaults.Add(UserSettingConstants.EnableNvDecSupport, nvidiaDefaultSetting);
            defaults.Add(UserSettingConstants.EnableQuickSyncLowPower, true);
            defaults.Add(UserSettingConstants.EnableDirectXDecoding, SystemInfo.IsArmDevice);

            // Advanced
            defaults.Add(UserSettingConstants.PreventSleep, true);
            defaults.Add(UserSettingConstants.PauseEncodingOnLowBattery, true);
            defaults.Add(UserSettingConstants.LowBatteryLevel, 15);

            defaults.Add(UserSettingConstants.DisableLibDvdNav, false);
            defaults.Add(UserSettingConstants.PauseOnLowDiskspace, true);
            defaults.Add(UserSettingConstants.PauseQueueOnLowDiskspaceLevel, 2000000000L);
            defaults.Add(UserSettingConstants.PreviewScanCount, 10);
            defaults.Add(UserSettingConstants.MinScanDuration, 10);
            defaults.Add(UserSettingConstants.MaxScanDuration, 0);
            defaults.Add(UserSettingConstants.KeepDuplicateTitles, false);
            defaults.Add(UserSettingConstants.ProcessPriorityInt, 2);
            defaults.Add(UserSettingConstants.X264Step, 0.5);
            defaults.Add(UserSettingConstants.SaveLogToCopyDirectory, false);
            defaults.Add(UserSettingConstants.SaveLogWithVideo, false);
            defaults.Add(UserSettingConstants.ClearOldLogs, true);

            defaults.Add(UserSettingConstants.ExcludedExtensions, new List<string> { "png", "jpg", "srt", "ass", "ssa", "txt" });
            defaults.Add(UserSettingConstants.RecursiveFolderScan, false);
            defaults.Add(UserSettingConstants.DefaultRangeMode, DefaultRangeMode.Chapters);
            
            // Preview
            defaults.Add(UserSettingConstants.LastPreviewDuration, 30);
            defaults.Add(UserSettingConstants.UseExternalPlayer, false);

            // Experimental
            defaults.Add(UserSettingConstants.ProcessIsolationEnabled, true);
            defaults.Add(UserSettingConstants.ProcessIsolationPort, 8037);
            defaults.Add(UserSettingConstants.SimultaneousEncodes, 1);

            // Misc
            defaults.Add(UserSettingConstants.ScalingMode, 0);
            defaults.Add(UserSettingConstants.ForcePresetReset, 3);
            defaults.Add(UserSettingConstants.PreviewShowPictureSettingsOverlay, false);
            defaults.Add(UserSettingConstants.RunCounter, 0);
            defaults.Add(UserSettingConstants.ForceSoftwareRendering, false);
            defaults.Add(UserSettingConstants.IsUpdateAvailableBuild, 0);
            defaults.Add(UserSettingConstants.ExtendedQueueDisplay, true);
            defaults.Add(UserSettingConstants.HardwareDetectTimeoutSeconds, 12);
            defaults.Add(UserSettingConstants.SimpleQueueView, false);
            defaults.Add(UserSettingConstants.ShowPresetDesc, true);
            defaults.Add(UserSettingConstants.IsLegacyMenuShown, true);

            return defaults;
        }

        private T GetDefaultValue<T>(string name)
        {
            try
            {
                // If the current setting is corrupt, return the default and log it.
                Dictionary<string, object> defaults = this.GetDefaults();
                if (defaults.ContainsKey(name))
                {
                    return (T)this.userSettings[name];
                }
            }
            catch (Exception e)
            {
                this.logService.LogMessage("Unable to fetch default setting:  " + name + " - " + e);
                return default(T);
            }

            return default(T);
        }
    }
}