using System;
using System.Linq;

namespace FishDice.LuckyDice
{
    internal sealed class ExactComboMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.ExactComboMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return string.Equals(
                spec.UseOrderedKey ? facts.OrderedKey : facts.NormalizedKey,
                spec.ComboKey,
                StringComparison.Ordinal);
        }
    }

    internal sealed class AllNumbersMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.AllNumbersMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return facts.NumberValues.Count == facts.DiceCount;
        }
    }

    internal sealed class ContainsFaceMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.ContainsFaceMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return ComboMatcherFacts.FaceCount(facts, spec.FaceKey) >= (spec.RequiredCount ?? 1);
        }
    }

    internal sealed class FaceCountMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.FaceCountMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return ComboMatcherFacts.FaceCount(facts, spec.FaceKey) == (spec.RequiredCount ?? 1);
        }
    }

    internal sealed class FaceCountRangeMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.FaceCountRangeMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return ComboMatcherFacts.IsInRange(
                ComboMatcherFacts.FaceCount(facts, spec.FaceKey),
                spec.MinCount,
                spec.MaxCount);
        }
    }

    internal sealed class DiceCountMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.DiceCountMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return !spec.DiceCount.HasValue || facts.DiceCount == spec.DiceCount.Value;
        }
    }

    internal sealed class NumberPairMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.NumberPairMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return facts.NumberValues.Count == 2 && facts.NumberValues[0] == facts.NumberValues[1];
        }
    }

    internal sealed class NumberOfAKindMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.NumberOfAKindMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return facts.NumberValues
                .GroupBy(value => value)
                .Any(group => group.Count() >= (spec.OfAKindCount ?? 2));
        }
    }

    internal sealed class NumberSumRangeMatcher : IComboMatcher
    {
        public string MatcherType => LuckyDiceKeys.NumberSumRangeMatcher;

        public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
        {
            return ComboMatcherFacts.IsInRange(facts.NumberSum, spec.MinSum, spec.MaxSum);
        }
    }

    internal static class ComboMatcherFacts
    {
        public static int FaceCount(ComboFacts facts, string faceKey)
        {
            if (string.IsNullOrEmpty(faceKey))
            {
                return 0;
            }

            return facts.FaceCounts.TryGetValue(faceKey, out var count) ? count : 0;
        }

        public static bool IsInRange(int value, int? min, int? max)
        {
            if (min.HasValue && value < min.Value)
            {
                return false;
            }

            if (max.HasValue && value > max.Value)
            {
                return false;
            }

            return true;
        }
    }
}
