﻿//This file is part of DictCCApp.
//
//DictCCApp is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//DictCCApp is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with DictCCApp.  If not, see <http://www.gnu.org/licenses/>.
using DictCCApp.Domain;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using SharpEnd.Pcl.Application.Contracts.Storage;
using System.Diagnostics;
using DictCCApp.Domain.Settings;

namespace DictCCApp.Services
{
    public class SearchService : ISearchService
    {
        private readonly ISettingsService _settingsService;
        private readonly IStorageService _storageService;
        private readonly IndexCache _cache = new IndexCache();
        
        private async Task<WordSearchIndex> GetIndex(String filename)
        {
            var index = _cache.GetIndex(filename);
            if (index == null)
            {
                index = await new BaseIndexFilesystemWriter(_storageService).DerializeDataContractBin<WordSearchIndex>(filename);
                _cache.AddIndex(filename, index);
            }
            return index;
        }

        public SearchService(ISettingsService settingsService, IStorageService storageService)
        {
            if (settingsService == null)
            {
                throw new ArgumentNullException("settingsService");
            }

            if (storageService == null)
            {
                throw new ArgumentNullException("storageService");
            }

            _settingsService = settingsService;
            _storageService = storageService;
        }
                
        private async Task<IEnumerable<IIdWord>> Search(string input, SerializableDictionaryInfo selectedDictionaryInfo)
        {
            Stopwatch sw = new Stopwatch();

            var searchTokens = input.Split(' ');
            if (searchTokens.Length > 1)
            {
                List<SerializableWord> result = new List<SerializableWord>();
                //ConcurrentBag<SerializableWord> result = new ConcurrentBag<SerializableWord>();
                foreach (var token in searchTokens)
                {
                    if ((!string.IsNullOrWhiteSpace(token) && (token.Length > 1)))
                    {
                        var filename = GetFilename(token, selectedDictionaryInfo);
                        if (!string.IsNullOrWhiteSpace(filename))
                        {
                            sw.Restart();
                            var index = await GetIndex(filename);
                            sw.Stop();
                            //Debug.WriteLine("GetIndex: " + sw.ElapsedMilliseconds);

                            sw.Restart();
                            var words = index.SearchToken(token);

                            sw.Stop();
                            //Debug.WriteLine("SearchToken: " + sw.ElapsedMilliseconds);


                            sw.Restart();

                            foreach (var singleWord in words.Where(w => IsMatch(w, searchTokens)))
                            {
                                if (!result.Any(w => w.WordName.Equals(singleWord.WordName)))
                                {
                                    SetRating(singleWord, token, input);
                                    result.Add(singleWord);
                                }
                            }

                            //just a test if it's faster
                            //Parallel.ForEach<SerializableWord>(words,
                            //    new ParallelOptions() { MaxDegreeOfParallelism = 4 },
                            //    (w) =>
                            //{
                            //    if (IsMatch(w, searchTokens))
                            //    {
                            //        if (!result.Any(p => p.WordName.Equals(w.WordName)))
                            //        {
                            //            result.Add(w);
                            //        }
                            //    }
                            //});

                            sw.Stop();
                            //Debug.WriteLine("======= SW (ms): " + sw.ElapsedMilliseconds);

                        }
                    }
                }
                return result.OrderByDescending(w => w.WordType).OrderByDescending(w => w.Rating);
            }
            else
            {
                var filename = GetFilename(input, selectedDictionaryInfo);
                if (!string.IsNullOrWhiteSpace(filename))
                {
                    var index = await GetIndex(filename);

                    var words = index.SearchToken(input);
                    List<SerializableWord> result = new List<SerializableWord>();
                    foreach (var singleWord in words.Where(w => IsMatch(w, searchTokens)))
                    {
                        SetRating(singleWord, input, input);
                        result.Add(singleWord);
                    }
                    return result.OrderByDescending(w => w.WordType).OrderByDescending(p => p.Rating);
                }
            }

            return Enumerable.Empty<IIdWord>();
        }

        public Task<IEnumerable<IIdWord>> SearchAsync(string input)
        {
            return Task.Run<IEnumerable<IIdWord>>(async () =>
            {
                input = input.Trim();
                if ((string.IsNullOrWhiteSpace(input) || (input.Length < 2)))
                {
                    return Enumerable.Empty<IIdWord>();
                }

                input = input.ToLower();

                //first try, use selected dictionary
                var selectedDictionaryInfo = _settingsService.GetSelectedDictionary();
                bool autoSwitchDictinary = _settingsService.AutoSwitchDictionary;
                //var filename = GetFilename(input, selectedDictionaryInfo);
                //var index = await GetIndex(filename);
                var result = await Search(input, selectedDictionaryInfo);
                if (result.Any())
                {
                    return result;
                }

                //second try ... switch dictionaries
                if (autoSwitchDictinary)
                {
                    var dictionaries = _settingsService.GetAvailableDictionaries()
                                        .Where(d => (!d.Name.Equals(selectedDictionaryInfo.Name)) 
                                                     && (d.AutoSearch));

                    foreach (var dictionary in dictionaries)
                    {
                        //filename = GetFilename(input, dictionary);
                        //index = await GetIndex(filename);
                        result = await Search(input, dictionary);
                        if (result.Any())
                        {
                            _settingsService.SetSelectedDictionary(dictionary);
                            return result;
                        }
                    }
                }

                return Enumerable.Empty<IIdWord>();
            });
        }

        private bool IsMatch(SerializableWord word, string[] searchTokens)
        {
            //return word.WordName.ToLower().Contains(string.Join(" ", searchTokens));

            return searchTokens.All(token => word.WordName.ToLower().Contains(token));
        }

        private void SetRating(SerializableWord word,string searchToken,string completeSearchtext)
        {
            var compareToken = word.WordName.ToLower();

            if (compareToken.Contains('['))
            {
                compareToken = compareToken.Substring(0, compareToken.IndexOf('[')).Trim();
            }

            var lowerSerachToken = searchToken.ToLower();
            var lowerSearchText = completeSearchtext.ToLower();

            if (compareToken.Equals(lowerSearchText))
            {
                word.Rating = 10;
            } else if (compareToken.StartsWith(lowerSearchText))
            {
                word.Rating = 5;
            } else if (compareToken.Equals(lowerSerachToken))
            {
                word.Rating = 3;
            } else if (compareToken.StartsWith(lowerSerachToken))
            {
                word.Rating = 1;
            }
        }

        private string GetFilename(string input, SerializableDictionaryInfo dictionaryInfo)
        {
            if (dictionaryInfo == null)
            {
                throw new ArgumentNullException("dictionaryInfo");
            }

            string grouping = input.Substring(0, 2);
            
            var mapping = dictionaryInfo.Mappings.FirstOrDefault(m => m.Grouping == grouping);
            if (mapping != null)
            {
                return string.Format("{0}\\{1}.bin", dictionaryInfo.Language, mapping.Id);
            }

            return string.Empty;
        }

        public Task<bool> IndexAvailableAsync()
        {
            return Task.Run<bool>(() =>
            {
                return _settingsService.GetAvailableDictionaries().Any();                 
            });
        }
    }
}
