using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Library
{
    /// 
    /// Class LuceneSearchEngine
    /// http://www.codeproject.com/Articles/320219/Lucene-Net-ultra-fast-search-for-MVC-or-WebForms
    /// 
    public class SearchEngine : ISearchEngine
    {
        private readonly ILibraryManager _libraryManager;
        private readonly IUserManager _userManager;
        private readonly ILogger _logger;
        public SearchEngine(ILogManager logManager, ILibraryManager libraryManager, IUserManager userManager)
        {
            _libraryManager = libraryManager;
            _userManager = userManager;
            _logger = logManager.GetLogger("Lucene");
        }
        public async Task> GetSearchHints(SearchQuery query)
        {
            var user = _userManager.GetUserById(new Guid(query.UserId));
            var inputItems = user.RootFolder.GetRecursiveChildren(user, null).Where(i => !(i is ICollectionFolder));
            var results = await GetSearchHints(inputItems, query).ConfigureAwait(false);
            var searchResultArray = results.ToArray();
            results = searchResultArray;
            var count = searchResultArray.Length;
            if (query.StartIndex.HasValue)
            {
                results = results.Skip(query.StartIndex.Value);
            }
            if (query.Limit.HasValue)
            {
                results = results.Take(query.Limit.Value);
            }
            return new QueryResult
            {
                TotalRecordCount = count,
                Items = results.ToArray()
            };
        }
        /// 
        /// Gets the search hints.
        /// 
        /// The input items.
        /// The query.
        /// IEnumerable{SearchHintResult}.
        /// searchTerm
        private Task> GetSearchHints(IEnumerable inputItems, SearchQuery query)
        {
            var searchTerm = query.SearchTerm;
            if (string.IsNullOrEmpty(searchTerm))
            {
                throw new ArgumentNullException("searchTerm");
            }
            var terms = GetWords(searchTerm);
            var hints = new List>();
            var items = inputItems.Where(i => !(i is MusicArtist)).ToList();
            if (query.IncludeMedia)
            {
                // Add search hints based on item name
                hints.AddRange(items.Where(i => !string.IsNullOrEmpty(i.Name)).Select(item =>
                {
                    var index = GetIndex(item.Name, searchTerm, terms);
                    return new Tuple(item, index.Item1, index.Item2);
                }));
            }
            if (query.IncludeArtists)
            {
                // Find artists
                var artists = _libraryManager.GetAllArtists(items)
                    .ToList();
                foreach (var item in artists)
                {
                    var index = GetIndex(item, searchTerm, terms);
                    if (index.Item2 != -1)
                    {
                        try
                        {
                            var artist = _libraryManager.GetArtist(item);
                            hints.Add(new Tuple(artist, index.Item1, index.Item2));
                        }
                        catch (Exception ex)
                        {
                            _logger.ErrorException("Error getting {0}", ex, item);
                        }
                    }
                }
            }
            if (query.IncludeGenres)
            {
                // Find genres, from non-audio items
                var genres = items.Where(i => !(i is IHasMusicGenres) && !(i is Game))
                    .SelectMany(i => i.Genres)
                    .Where(i => !string.IsNullOrEmpty(i))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();
                foreach (var item in genres)
                {
                    var index = GetIndex(item, searchTerm, terms);
                    if (index.Item2 != -1)
                    {
                        try
                        {
                            var genre = _libraryManager.GetGenre(item);
                            hints.Add(new Tuple(genre, index.Item1, index.Item2));
                        }
                        catch (Exception ex)
                        {
                            _logger.ErrorException("Error getting {0}", ex, item);
                        }
                    }
                }
                // Find music genres
                var musicGenres = items.Where(i => i is IHasMusicGenres)
                    .SelectMany(i => i.Genres)
                    .Where(i => !string.IsNullOrEmpty(i))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();
                foreach (var item in musicGenres)
                {
                    var index = GetIndex(item, searchTerm, terms);
                    if (index.Item2 != -1)
                    {
                        try
                        {
                            var genre = _libraryManager.GetMusicGenre(item);
                            hints.Add(new Tuple(genre, index.Item1, index.Item2));
                        }
                        catch (Exception ex)
                        {
                            _logger.ErrorException("Error getting {0}", ex, item);
                        }
                    }
                }
                // Find music genres
                var gameGenres = items.OfType()
                    .SelectMany(i => i.Genres)
                    .Where(i => !string.IsNullOrEmpty(i))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();
                foreach (var item in gameGenres)
                {
                    var index = GetIndex(item, searchTerm, terms);
                    if (index.Item2 != -1)
                    {
                        try
                        {
                            var genre = _libraryManager.GetGameGenre(item);
                            hints.Add(new Tuple(genre, index.Item1, index.Item2));
                        }
                        catch (Exception ex)
                        {
                            _logger.ErrorException("Error getting {0}", ex, item);
                        }
                    }
                }
            }
            if (query.IncludeStudios)
            {
                // Find studios
                var studios = items.SelectMany(i => i.Studios)
                    .Where(i => !string.IsNullOrEmpty(i))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();
                foreach (var item in studios)
                {
                    var index = GetIndex(item, searchTerm, terms);
                    if (index.Item2 != -1)
                    {
                        try
                        {
                            var studio = _libraryManager.GetStudio(item);
                            hints.Add(new Tuple(studio, index.Item1, index.Item2));
                        }
                        catch (Exception ex)
                        {
                            _logger.ErrorException("Error getting {0}", ex, item);
                        }
                    }
                }
            }
            if (query.IncludePeople)
            {
                // Find persons
                var persons = items.SelectMany(i => i.People)
                    .Select(i => i.Name)
                    .Where(i => !string.IsNullOrEmpty(i))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();
                foreach (var item in persons)
                {
                    var index = GetIndex(item, searchTerm, terms);
                    if (index.Item2 != -1)
                    {
                        try
                        {
                            var person = _libraryManager.GetPerson(item);
                            hints.Add(new Tuple(person, index.Item1, index.Item2));
                        }
                        catch (Exception ex)
                        {
                            _logger.ErrorException("Error getting {0}", ex, item);
                        }
                    }
                }
            }
            var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).Select(i => new SearchHintInfo
            {
                Item = i.Item1,
                MatchedTerm = i.Item2
            });
            return Task.FromResult(returnValue);
        }
        /// 
        /// Gets the index.
        /// 
        /// The input.
        /// The search input.
        /// The search input.
        /// System.Int32.
        private Tuple GetIndex(string input, string searchInput, List searchWords)
        {
            if (string.IsNullOrEmpty(input))
            {
                throw new ArgumentNullException("input");
            }
            if (string.Equals(input, searchInput, StringComparison.OrdinalIgnoreCase))
            {
                return new Tuple(searchInput, 0);
            }
            var index = input.IndexOf(searchInput, StringComparison.OrdinalIgnoreCase);
            if (index == 0)
            {
                return new Tuple(searchInput, 1);
            }
            if (index > 0)
            {
                return new Tuple(searchInput, 2);
            }
            var items = GetWords(input);
            for (var i = 0; i < searchWords.Count; i++)
            {
                var searchTerm = searchWords[i];
                for (var j = 0; j < items.Count; j++)
                {
                    var item = items[j];
                    if (string.Equals(item, searchTerm, StringComparison.OrdinalIgnoreCase))
                    {
                        return new Tuple(searchTerm, 3 + (i + 1) * (j + 1));
                    }
                    index = item.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase);
                    if (index == 0)
                    {
                        return new Tuple(searchTerm, 4 + (i + 1) * (j + 1));
                    }
                    if (index > 0)
                    {
                        return new Tuple(searchTerm, 5 + (i + 1) * (j + 1));
                    }
                }
            }
            return new Tuple(null, -1);
        }
        /// 
        /// Gets the words.
        /// 
        /// The term.
        /// System.String[][].
        private List GetWords(string term)
        {
            return term.Split().Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
        }
    }
}