using QFSW.QC.Comparators;
using System;
using System.Collections.Generic;
using System.Linq;

namespace QFSW.QC
{
    /// <summary>
    /// Provides a filtered and sorted list of suggestions for a given context using IQcSuggestors and IQcSuggestionFilter 
    /// </summary>
    public class QuantumSuggestor
    {
        private readonly IQcSuggestor[] _suggestors;
        private readonly IQcSuggestionFilter[] _suggestionFilters;
        private readonly List<IQcSuggestion> _suggestionBuffer = new List<IQcSuggestion>();

        /// <summary>
        /// Creates a Quantum Suggestor with a custom set of suggestors an suggestion filters.
        /// </summary>
        /// <param name="suggestors">The IQcSuggestors to use in this Quantum Suggestor.</param>
        /// /// <param name="suggestionFilters">The IQcSuggestionFilters to use in this Quantum Suggestor.</param>
        public QuantumSuggestor(IEnumerable<IQcSuggestor> suggestors, IEnumerable<IQcSuggestionFilter> suggestionFilters)
        {
            _suggestors = suggestors.ToArray();
            _suggestionFilters = suggestionFilters.ToArray();
        }

        /// <summary>
        /// Creates a Quantum Suggestor with the default injected suggestors and suggestion filters.
        /// </summary>
        public QuantumSuggestor() : this(
            new InjectionLoader<IQcSuggestor>().GetInjectedInstances(),
            new InjectionLoader<IQcSuggestionFilter>().GetInjectedInstances())
        {

        }

        /// <summary>
        /// Gets suggestions for a given context.
        /// </summary>
        /// <param name="context">The context to get suggestions for.</param>
        /// <param name="options">Options for the suggestor.</param>
        /// <returns>The sorted and filtered suggestions for the provided context.</returns>
        public IEnumerable<IQcSuggestion> GetSuggestions(SuggestionContext context, SuggestorOptions options)
        {
            PreprocessContext(ref context);

            // Get and filter suggestions
            IEnumerable<IQcSuggestion> suggestions = 
                _suggestors
                    .SelectMany(x => x.GetSuggestions(context, options))
                    .Where(x => IsSuggestionPermitted(x, context));

            _suggestionBuffer.Clear();
            _suggestionBuffer.AddRange(suggestions);

            // Sort suggestions
            AlphanumComparator comparator = new AlphanumComparator();
            IOrderedEnumerable<IQcSuggestion> sortedSuggestions =
                _suggestionBuffer
                    .OrderBy(x => x.PrimarySignature.Length)
                    .ThenBy(x => x.PrimarySignature, comparator)
                    .ThenBy(x => x.SecondarySignature.Length)
                    .ThenBy(x => x.SecondarySignature, comparator);

            if (options.Fuzzy)
            {
                StringComparison comparisonType = options.CaseSensitive
                    ? StringComparison.CurrentCulture
                    : StringComparison.CurrentCultureIgnoreCase;

                sortedSuggestions = sortedSuggestions
                        .OrderBy(x => x.PrimarySignature.IndexOf(context.Prompt, comparisonType));
            }

            // Return suggestions to user
            return sortedSuggestions;
        }

        private void PreprocessContext(ref SuggestionContext context)
        {
            // Strip the scope on the provided prompt in the context to improve suggestions
            // We want to allow incomplete scope reduction on the prompt so that you get suggestions
            // as if you had finished the scope properly
            TextProcessing.ReduceScopeOptions options = TextProcessing.ReduceScopeOptions.Default;
            options.ReduceIncompleteScope = true;

            context.Prompt = context.Prompt.ReduceScope(options);
        }

        private bool IsSuggestionPermitted(IQcSuggestion suggestion, SuggestionContext context)
        {
            // LINQ alternative produces too much garbage
            // ReSharper disable once LoopCanBeConvertedToQuery
            foreach (IQcSuggestionFilter filter in _suggestionFilters)
            {
                if (!filter.IsSuggestionPermitted(suggestion, context))
                {
                    return false;
                }
            }

            return true;
        }
    }
}
