﻿// 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.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using Microsoft.Plugin.Indexer.DriveDetection;
using Microsoft.Plugin.Indexer.SearchHelper;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.Search.Interop;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin;

namespace Microsoft.Plugin.Indexer
{
    internal class Main : ISettingProvider, IPlugin, ISavable, IPluginI18n, IContextMenu, IDisposable, IDelayedExecutionPlugin
    {
        // This variable contains metadata about the Plugin
        private PluginInitContext _context;

        // This variable contains information about the context menus
        private IndexerSettings _settings;

        // Contains information about the plugin stored in json format
        private PluginJsonStorage<IndexerSettings> _storage;

        // To access Windows Search functionalities
        private static readonly OleDBSearch _search = new OleDBSearch();
        private readonly WindowsSearchAPI _api = new WindowsSearchAPI(_search);

        // To obtain information regarding the drives that are indexed
        private readonly IndexerDriveDetection _driveDetection = new IndexerDriveDetection(new RegistryWrapper());

        // Reserved keywords in oleDB
        private readonly string reservedStringPattern = @"^[\/\\\$\%]+$|^.*[<>].*$";

        private string WarningIconPath { get; set; }

        private IContextMenu _contextMenuLoader;
        private bool disposedValue;

        // To save the configurations of plugins
        public void Save()
        {
            _storage.Save();
        }

        // This function uses the Windows indexer and returns the list of results obtained
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")]
        public List<Result> Query(Query query, bool isFullQuery)
        {
            var results = new List<Result>();

            if (!string.IsNullOrEmpty(query.Search))
            {
                var searchQuery = query.Search;
                if (_settings.MaxSearchCount <= 0)
                {
                    _settings.MaxSearchCount = 30;
                }

                var regexMatch = Regex.Match(searchQuery, reservedStringPattern);

                if (!regexMatch.Success)
                {
                    try
                    {
                        if (_driveDetection.DisplayWarning())
                        {
                            results.Add(new Result
                            {
                                Title = _context.API.GetTranslation("Microsoft_plugin_indexer_drivedetectionwarning"),
                                SubTitle = _context.API.GetTranslation("Microsoft_plugin_indexer_disable_warning_in_settings"),
                                IcoPath = WarningIconPath,
                                Action = e =>
                                {
                                    try
                                    {
                                        Process.Start(GetWindowsSearchSettingsProcessInfo());
                                    }
                                    catch (Exception ex)
                                    {
                                        Log.Exception("Microsoft.Plugin.Indexer", $"Unable to launch Windows Search Settings: {ex.Message}", ex, "Query");
                                    }

                                    return true;
                                },
                            });
                        }

                        // This uses the Microsoft.Search.Interop assembly
                        var searchManager = new CSearchManager();
                        var searchResultsList = _api.Search(searchQuery, searchManager, isFullQuery, maxCount: _settings.MaxSearchCount).ToList();

                        // If the delayed execution query is not required (since the SQL query is fast) return empty results
                        if (searchResultsList.Count == 0 && isFullQuery)
                        {
                            return new List<Result>();
                        }

                        foreach (var searchResult in searchResultsList)
                        {
                            var path = searchResult.Path;
                            var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_name"), searchResult.Title);
                            var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_path"), path);
                            string workingDir = null;
                            if (_settings.UseLocationAsWorkingDir)
                            {
                                workingDir = Path.GetDirectoryName(path);
                            }

                            Result r = new Result();
                            r.Title = searchResult.Title;
                            r.SubTitle = "Search: " + path;
                            r.IcoPath = path;
                            r.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
                            r.Action = c =>
                            {
                                bool hide;
                                try
                                {
                                    Process.Start(new ProcessStartInfo
                                    {
                                        FileName = path,
                                        UseShellExecute = true,
                                        WorkingDirectory = workingDir,
                                    });
                                    hide = true;
                                }
                                catch (Win32Exception)
                                {
                                    var name = $"Plugin: {_context.CurrentPluginMetadata.Name}";
                                    var msg = "Can't Open this file";
                                    _context.API.ShowMsg(name, msg, string.Empty);
                                    hide = false;
                                }

                                return hide;
                            };
                            r.ContextData = searchResult;

                            // If the result is a directory, then it's display should show a directory.
                            if (Directory.Exists(path))
                            {
                                r.QueryTextDisplay = path;
                            }

                            results.Add(r);
                        }
                    }
                    catch (InvalidOperationException)
                    {
                        // The connection has closed, internal error of ExecuteReader()
                        // Not showing this exception to the users
                    }
                    catch (Exception ex)
                    {
                        Log.Info(ex.ToString());
                    }
                }
            }

            return results;
        }

        // This function uses the Windows indexer and returns the list of results obtained. This version is required to implement the interface
        public List<Result> Query(Query query)
        {
            return Query(query, false);
        }

        public void Init(PluginInitContext context)
        {
            // initialize the context of the plugin
            _context = context;
            _contextMenuLoader = new ContextMenuLoader(context);
            _storage = new PluginJsonStorage<IndexerSettings>();
            _settings = _storage.Load();
            _context.API.ThemeChanged += OnThemeChanged;
            UpdateIconPath(_context.API.GetCurrentTheme());
        }

        // Todo : Update with theme based IconPath
        private void UpdateIconPath(Theme theme)
        {
            if (theme == Theme.Light || theme == Theme.HighContrastWhite)
            {
                WarningIconPath = "Images/Warning.light.png";
            }
            else
            {
                WarningIconPath = "Images/Warning.dark.png";
            }
        }

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

        // TODO: Localize the strings
        // Set the Plugin Title
        public string GetTranslatedPluginTitle()
        {
            return "Windows Indexer Plugin";
        }

        // TODO: Localize the string
        // Set the plugin Description
        public string GetTranslatedPluginDescription()
        {
            return "Returns files and folders";
        }

        public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
        {
            return _contextMenuLoader.LoadContextMenus(selectedResult);
        }

        public void UpdateSettings(PowerLauncherSettings settings)
        {
            _driveDetection.IsDriveDetectionWarningCheckBoxSelected = settings.Properties.DisableDriveDetectionWarning;
        }

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

        // Returns the Process Start Information for the new Windows Search Settings
        public static ProcessStartInfo GetWindowsSearchSettingsProcessInfo()
        {
            var ps = new ProcessStartInfo("ms-settings:cortana-windowssearch")
            {
                UseShellExecute = true,
                Verb = "open",
            };

            return ps;
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                }

                disposedValue = true;
            }
        }

        public void Dispose()
        {
            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }
}
