// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Controls;

using ManagedCommon;
using Microsoft.PowerToys.Run.Plugin.Calculator.Properties;
using Microsoft.PowerToys.Settings.UI.Library;
using Wox.Plugin;
using Wox.Plugin.Logger;

namespace Microsoft.PowerToys.Run.Plugin.Calculator
{
    public class Main : IPlugin, IPluginI18n, IDisposable, ISettingProvider
    {
        private const string InputUseEnglishFormat = nameof(InputUseEnglishFormat);
        private const string OutputUseEnglishFormat = nameof(OutputUseEnglishFormat);
        private const string ReplaceInput = nameof(ReplaceInput);
        private const string TrigMode = nameof(TrigMode);

        private static readonly CalculateEngine CalculateEngine = new CalculateEngine();

        private PluginInitContext Context { get; set; }

        private string IconPath { get; set; }

        private bool _inputUseEnglishFormat;
        private bool _outputUseEnglishFormat;
        private bool _replaceInput;
        private static CalculateEngine.TrigMode _trigMode;

        public string Name => Resources.wox_plugin_calculator_plugin_name;

        public string Description => Resources.wox_plugin_calculator_plugin_description;

        public static string PluginID => "CEA0FDFC6D3B4085823D60DC76F28855";

        private bool _disposed;

        private static readonly CompositeFormat WoxPluginCalculatorInEnFormatDescription = System.Text.CompositeFormat.Parse(Properties.Resources.wox_plugin_calculator_in_en_format_description);
        private static readonly CompositeFormat WoxPluginCalculatorOutEnFormatDescription = System.Text.CompositeFormat.Parse(Properties.Resources.wox_plugin_calculator_out_en_format_description);

        public IEnumerable<PluginAdditionalOption> AdditionalOptions => new List<PluginAdditionalOption>()
        {
            // The number examples has to be created at runtime to prevent translation.
            new PluginAdditionalOption
            {
                Key = InputUseEnglishFormat,
                DisplayLabel = Resources.wox_plugin_calculator_in_en_format,
                DisplayDescription = string.Format(CultureInfo.CurrentCulture, WoxPluginCalculatorInEnFormatDescription, 1000.55.ToString("N2", new CultureInfo("en-us"))),
                Value = false,
            },
            new PluginAdditionalOption
            {
                Key = OutputUseEnglishFormat,
                DisplayLabel = Resources.wox_plugin_calculator_out_en_format,
                DisplayDescription = string.Format(CultureInfo.CurrentCulture, WoxPluginCalculatorOutEnFormatDescription, 1000.55.ToString("G", new CultureInfo("en-us"))),
                Value = false,
            },
            new PluginAdditionalOption
            {
                Key = ReplaceInput,
                DisplayLabel = Resources.wox_plugin_calculator_replace_input,
                DisplayDescription = Resources.wox_plugin_calculator_replace_input_description,
                Value = true,
            },
            new PluginAdditionalOption
            {
                Key = TrigMode,
                DisplayLabel = Resources.wox_plugin_calculator_trig_unit_mode,
                DisplayDescription = Resources.wox_plugin_calculator_trig_unit_mode_description,
                PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
                ComboBoxValue = (int)CalculateEngine.TrigMode.Radians,
                ComboBoxItems =
                [
                    new KeyValuePair<string, string>(Resources.wox_plugin_calculator_trig_unit_radians, "0"),
                    new KeyValuePair<string, string>(Resources.wox_plugin_calculator_trig_unit_degrees, "1"),
                    new KeyValuePair<string, string>(Resources.wox_plugin_calculator_trig_unit_gradians, "2"),
                ],
            },
        };

        public List<Result> Query(Query query)
        {
            ArgumentNullException.ThrowIfNull(query);

            bool isGlobalQuery = string.IsNullOrEmpty(query.ActionKeyword);
            bool replaceInput = _replaceInput && !isGlobalQuery && query.Search.EndsWith('=');
            CultureInfo inputCulture = _inputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
            CultureInfo outputCulture = _outputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;

            // Happens if the user has only typed the action key so far
            if (string.IsNullOrEmpty(query.Search))
            {
                return new List<Result>();
            }

            NumberTranslator translator = NumberTranslator.Create(inputCulture, new CultureInfo("en-US"));
            var input = translator.Translate(query.Search.Normalize(NormalizationForm.FormKC));

            if (replaceInput)
            {
                input = input[..^1];
            }

            if (!CalculateHelper.InputValid(input))
            {
                return new List<Result>();
            }

            try
            {
                // Using CurrentUICulture since this is user facing
                var result = CalculateEngine.Interpret(input, outputCulture, out string errorMessage);

                // This could happen for some incorrect queries, like pi(2)
                if (result.Equals(default(CalculateResult)))
                {
                    // If errorMessage is not default then do error handling
                    return errorMessage == default ? new List<Result>() : ErrorHandler.OnError(IconPath, isGlobalQuery, query.RawQuery, errorMessage);
                }
                else if (replaceInput)
                {
                    var pluginResult = ResultHelper.CreateResult(result.RoundedResult, IconPath, inputCulture, outputCulture);
                    Context.API.ChangeQuery($"{query.ActionKeyword} {pluginResult.QueryTextDisplay}");
                    return new List<Result>();
                }

                return new List<Result>
                {
                    ResultHelper.CreateResult(result.RoundedResult, IconPath, inputCulture, outputCulture),
                };
            }
            catch (Mages.Core.ParseException)
            {
                // Invalid input
                return ErrorHandler.OnError(IconPath, isGlobalQuery, query.RawQuery, Properties.Resources.wox_plugin_calculator_expression_not_complete);
            }
            catch (OverflowException)
            {
                // Result to big to convert to decimal
                return ErrorHandler.OnError(IconPath, isGlobalQuery, query.RawQuery, Properties.Resources.wox_plugin_calculator_not_covert_to_decimal);
            }
            catch (Exception e)
            {
                // Any other crash occurred
                // We want to keep the process alive if any the mages library throws any exceptions.
                return ErrorHandler.OnError(IconPath, isGlobalQuery, query.RawQuery, default, e);
            }
        }

        public void Init(PluginInitContext context)
        {
            Context = context ?? throw new ArgumentNullException(paramName: nameof(context));

            Context.API.ThemeChanged += OnThemeChanged;
            UpdateIconPath(Context.API.GetCurrentTheme());
        }

        private void UpdateIconPath(Theme theme)
        {
            if (theme == Theme.Light || theme == Theme.HighContrastWhite)
            {
                IconPath = "Images/calculator.light.png";
            }
            else
            {
                IconPath = "Images/calculator.dark.png";
            }
        }

        private void OnThemeChanged(Theme currentTheme, Theme newTheme)
        {
            UpdateIconPath(newTheme);
        }

        public string GetTranslatedPluginTitle()
        {
            return Resources.wox_plugin_calculator_plugin_name;
        }

        public string GetTranslatedPluginDescription()
        {
            return Resources.wox_plugin_calculator_plugin_description;
        }

        public Control CreateSettingPanel()
        {
            throw new NotImplementedException();
        }

        public void UpdateSettings(PowerLauncherPluginSettings settings)
        {
            var inputUseEnglishFormat = false;
            var outputUseEnglishFormat = false;
            var replaceInput = true;
            var trigMode = CalculateEngine.TrigMode.Radians;

            if (settings != null && settings.AdditionalOptions != null)
            {
                var optionInputEn = settings.AdditionalOptions.FirstOrDefault(x => x.Key == InputUseEnglishFormat);
                inputUseEnglishFormat = optionInputEn?.Value ?? inputUseEnglishFormat;

                var optionOutputEn = settings.AdditionalOptions.FirstOrDefault(x => x.Key == OutputUseEnglishFormat);
                outputUseEnglishFormat = optionOutputEn?.Value ?? outputUseEnglishFormat;

                var optionReplaceInput = settings.AdditionalOptions.FirstOrDefault(x => x.Key == ReplaceInput);
                replaceInput = optionReplaceInput?.Value ?? replaceInput;

                try
                {
                    var optionTrigMode = settings.AdditionalOptions.FirstOrDefault(x => x.Key == TrigMode);
                    if (optionTrigMode != null)
                    {
                        trigMode = (CalculateEngine.TrigMode)int.Parse(optionTrigMode.ComboBoxValue.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
                    }
                }
                catch (Exception ex)
                {
                    Log.Exception("Error while trying to load Trigonometry Mode setting: {ex.Message}", ex, GetType());
                }
            }

            _inputUseEnglishFormat = inputUseEnglishFormat;
            _outputUseEnglishFormat = outputUseEnglishFormat;
            _replaceInput = replaceInput;
            _trigMode = trigMode;
        }

        public static CalculateEngine.TrigMode GetTrigMode()
        {
            return _trigMode;
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (Context != null && Context.API != null)
                    {
                        Context.API.ThemeChanged -= OnThemeChanged;
                    }

                    _disposed = true;
                }
            }
        }
    }
}
