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

namespace Core
{
    public class WordleUtil
    {
        public enum CharacterResponse
        {
            Actual,
            OtherPosition,
            NotInWord
        }

        public struct PositionCriteria
        {
            public char? Correct;
            public string OtherPosition;
            
            public string GetRegexRestriction() =>
                Correct.HasValue 
                    ? Correct.Value.ToString()
                    : !string.IsNullOrEmpty(OtherPosition) 
                        ? "[^" + OtherPosition + "]" 
                        : ".";

            public void RemoveLetter(char letter)
            {
                OtherPosition = OtherPosition?.Replace(letter.ToString(), string.Empty);
            }
        }

        public class WordleState
        {
            public string InWord { get; private set; } = string.Empty;
            public string NotInWord { get; private set; } = string.Empty;
            public PositionCriteria[] Criteria { get; } = new PositionCriteria[5];

            public bool IsSolved() => Criteria.All(c => c.Correct.HasValue);

            private void AddInWord(char letter)
            {
                if (!InWord.Contains(letter))
                    InWord += letter;
            }

            private void AddNotInWord(char letter)
            {
                if (!NotInWord.Contains(letter))
                    NotInWord += letter;
            }

            public void Update(string word, CharacterResponse[] response)
            {
                for (int i = 0; i < 5; i++)
                {
                    var letter = word[i];
                    switch (response[i])
                    {
                        case CharacterResponse.Actual:
                            // remove this letter from the criteria
                            foreach (var positionCriteria in Criteria)
                               positionCriteria.RemoveLetter(letter);
                            AddInWord(letter);
                            Criteria[i].Correct = letter;
                            break;
                        case CharacterResponse.OtherPosition:
                            AddInWord(letter);
                            Criteria[i].OtherPosition += letter;
                            break;
                        case CharacterResponse.NotInWord:
                            AddNotInWord(letter);
                            break;
                    }
                }
            }
        }

        public static string GetRegexRestrictions(PositionCriteria[] criteria)
        {
            return 
                criteria
                    .Aggregate(string.Empty, 
                        (r, criterion) 
                            => r + criterion.GetRegexRestriction()
                            );
        }

        public IList<string> Words { get; }
        private Dictionary<int, double> _repeatedLetterFactor;

        public WordleUtil()
            : this(@"WordleWords.txt")
        {
        }

        public WordleUtil(string filename)
            : this(filename.GetFileLines())
        {
        }

        public WordleUtil(IEnumerable<string> words)
        {
            Words = new List<string>();
            foreach (var word in words)
            {
                var w = word.Canonicalize();
                var split = w.Split(',');
                Words.Add(split[0]);
            }

            Words.Remove("ADMIN");
            Words.Remove("INBOX");

            var re =
                from w in Words
                let repeats = RepeatedLettersCount(w)
                group repeats by repeats
                into g
                select g;
            var max = re.Max(r => r.Count());
            _repeatedLetterFactor =
                re.Select(r => Tuple.Create(r.Key, r.Count() * 1.0 / max))
                    .ToDictionary(s => s.Item1, s => s.Item2);
        }

        public static CharacterResponse[] GetGuessResult(string actual, string guess)
        {
            if (actual.Length != 5 || guess.Length != 5)
                throw new ArgumentOutOfRangeException("not right size word or guess");
            var response = new CharacterResponse[5];
            for (var i = 0; i < 5; i++)
            {
                if (guess[i] == actual[i])
                    response[i] = CharacterResponse.Actual;
                else if (actual.Contains(guess[i]))
                    response[i] = CharacterResponse.OtherPosition;
                else
                    response[i] = CharacterResponse.NotInWord;
            }

            return response;
        }

        public static bool RepeatedLetters(string word)
        {
            var uniqueLetters = new HashSet<char>();

            foreach (var c in word)
                uniqueLetters.Add(c);

            return uniqueLetters.Count < word.Length;
        }


        public static int RepeatedLettersCount(string word)
        {
            var uniqueLetters = new HashSet<char>();

            foreach (var c in word)
                uniqueLetters.Add(c);

            return word.Length - uniqueLetters.Count;
        }

        public double FactorRepeatedLetters(string word, double score)
        {
            return score * _repeatedLetterFactor[RepeatedLettersCount(word)];
        }
        public Dictionary<char, int>[] GetLetterPositionFreq(int wordLen)
        {
            var ws = Words.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[i] == c)
                    );
            }

            return freqList;
        }
    }
}