using System;
using System.Collections.Generic;
using System.Linq;
using C5;

namespace Core
{
    public class DictionarySearcher
    {
        static HashBag<char> GetLettersBag(string letters)
        {
            var hashBag = new HashBag<char>();
            hashBag.AddAll(letters.Canonicalize().ToCharArray());
            return hashBag;
        }

        readonly struct DictWord
        {
            public readonly string Word;
            public readonly HashBag<char> CharBag;

            public DictWord(string word) : this()
            {
                Word = word;
                CharBag = GetLettersBag(word);
            }

            public static implicit operator DictWord(string s) => new DictWord(s);
            public static implicit operator string(DictWord dw) => dw.Word;

            public char[] ToCharArray() => Word.ToCharArray();
            public bool Contains(char c) => Word.Contains(c);

            public int Length => Word.Length;
            public bool StartsWith(string s) => Word.StartsWith(s);

            public string Substring(int startIndex) => Word.Substring(startIndex);
        }

        private class DictWordComprer : IComparer<DictWord>
        {
            private readonly IComparer<string> _comparer;
            public DictWordComprer(IComparer<string> comparer)
            {
                _comparer = comparer;
            }
            public int Compare(DictWord x, DictWord y)
            {
                return _comparer.Compare(x, y);
            }
        }

        private readonly List<DictWord> _dictionary;

        public DictionarySearcher(int? characterCount = null)
            : this(@"TWL06.txt", characterCount)
        {
        }

        public DictionarySearcher(string filename, int? characterCount = null)
            : this(filename.GetFileLines(), characterCount)
        {
        }

        public DictionarySearcher(IEnumerable<string> words, int? characterCount = null)
        {
            _dictionary = new List<DictWord>();
            foreach (var word in words)
            {
                if (characterCount != null && word.Length != characterCount)
                    continue;
                var w = word.Canonicalize();
                _dictionary.Add(w);
            }
        }

        public System.Collections.Generic.IList<string> GetWords()
        {
            return _dictionary.Select(d => d.Word).ToList();
        }

        public void SortDictionary(IComparer<string> comparer)
        {
            _dictionary.Sort(new DictWordComprer(comparer));
        }

        public IEnumerable<string> FindWords(string letters)
        {
            var hashBag = new HashBag<char>();
            hashBag.AddAll(letters.Canonicalize().ToCharArray());
            return from w in _dictionary
                where hashBag.ContainsAll(w.ToCharArray())
                select w.Word;
        }

        public IEnumerable<string> FindWords(string includeLetters, string excludeLetters)
        {
            var excludeBag = GetLettersBag(excludeLetters);

            return from w in _dictionary
                where w.CharBag.ContainsAll(includeLetters.Canonicalize())
                      && excludeBag.All(c => !w.Contains(c))
                select w.Word;
        }

        public IEnumerable<string> FindWords(string includeLetters, string excludeLetters, int? len)
        {
            return 
                from w in FindWords(includeLetters, excludeLetters)
                where !len.HasValue || w.Length == len.Value
                select w;
        }

        public IEnumerable<Tuple<string, string>> MatchingWords(string firstSelector, string secondSelector)
        {
            var firstSet = _dictionary.Where(w => w.StartsWith(firstSelector.Canonicalize())).GroupBy(w => w.Length)
                .ToList();
            var secondSet = _dictionary.Where(w => w.StartsWith(secondSelector.Canonicalize())).GroupBy(w => w.Length)
                .ToList();

            return from len in firstSet.Select(g => g.Key)
                from w1 in firstSet.Where(g => g.Key == len)
                from w2 in secondSet.Where(g => g.Key == len)
                from ww1 in w1
                from ww2 in w2
                where ww1.Substring(1).Equals(ww2.Substring(1))
                select Tuple.Create(ww1.Word, ww2.Word);
        }
        /*public Dictionary<char, int>[] GetLetterPositionFreq(int wordLen)
        {
            var ws = _dictionary.Where(w => w.Length == wordLen).ToList();

            var freqList = new Dictionary<char, int>[wordLen];
            for (var i = 0; i < wordLen; i++)
            {
                var letters = Enumerable.Range(0, 26).Select(r => Convert.ToChar('A' + r));
                freqList[i] =
                    letters.ToDictionary(c => c,
                        c => ws.Count(w => w.Word[i] == c)
                    );
            }

            return freqList;
        }*/
    }
}