using Discord;
using Discord.Commands;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NadekoBot.Services;
using Image = SixLabors.ImageSharp.Image;

namespace NadekoBot.Modules.Gambling
{
    public partial class Gambling
    {
        [Group]
        public class DiceRollCommands : NadekoSubmodule
        {
            private static readonly Regex dndRegex = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
            private static readonly Regex fudgeRegex = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);

            private static readonly char[] _fateRolls = { '-', ' ', '+' };
            private readonly IImageCache _images;

            public DiceRollCommands(IDataCache data)
            {
                _images = data.LocalImages;
            }

            [NadekoCommand, Aliases]
            public async Task Roll()
            {
                var rng = new NadekoRandom();
                var gen = rng.Next(1, 101);

                var num1 = gen / 10;
                var num2 = gen % 10;

                using (var img1 = GetDice(num1))
                using (var img2 = GetDice(num2))
                using (var img = new[] { img1, img2 }.Merge(out var format))
                using (var ms = img.ToStream(format))
                {
                    await ctx.Channel.SendFileAsync(ms,
                        $"dice.{format.FileExtensions.First()}",
                        Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
                }
            }

            [NadekoCommand, Aliases]
            [Priority(1)]
            public async Task Roll(int num)
            {
                await InternalRoll(num, true).ConfigureAwait(false);
            }


            [NadekoCommand, Aliases]
            [Priority(1)]
            public async Task Rolluo(int num = 1)
            {
                await InternalRoll(num, false).ConfigureAwait(false);
            }

            [NadekoCommand, Aliases]
            [Priority(0)]
            public async Task Roll(string arg)
            {
                await InternallDndRoll(arg, true).ConfigureAwait(false);
            }

            [NadekoCommand, Aliases]
            [Priority(0)]
            public async Task Rolluo(string arg)
            {
                await InternallDndRoll(arg, false).ConfigureAwait(false);
            }

            private async Task InternalRoll(int num, bool ordered)
            {
                if (num < 1 || num > 30)
                {
                    await ReplyErrorLocalizedAsync(strs.dice_invalid_number(1, 30));
                    return;
                }

                var rng = new NadekoRandom();

                var dice = new List<Image<Rgba32>>(num);
                var values = new List<int>(num);
                for (var i = 0; i < num; i++)
                {
                    var randomNumber = rng.Next(1, 7);
                    var toInsert = dice.Count;
                    if (ordered)
                    {
                        if (randomNumber == 6 || dice.Count == 0)
                            toInsert = 0;
                        else if (randomNumber != 1)
                            for (var j = 0; j < dice.Count; j++)
                            {
                                if (values[j] < randomNumber)
                                {
                                    toInsert = j;
                                    break;
                                }
                            }
                    }
                    else
                    {
                        toInsert = dice.Count;
                    }
                    dice.Insert(toInsert, GetDice(randomNumber));
                    values.Insert(toInsert, randomNumber);
                }

                using (var bitmap = dice.Merge(out var format))
                using (var ms = bitmap.ToStream(format))
                {
                    foreach (var d in dice)
                    {
                        d.Dispose();
                    }

                    await ctx.Channel.SendFileAsync(ms, $"dice.{format.FileExtensions.First()}",
                        Format.Bold(ctx.User.ToString()) + " " +
                        GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))) +
                        " " + GetText(strs.total_average(
                            Format.Bold(values.Sum().ToString()),
                            Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))));
                }
            }

            private async Task InternallDndRoll(string arg, bool ordered)
            {
                Match match;
                if ((match = fudgeRegex.Match(arg)).Length != 0 &&
                    int.TryParse(match.Groups["n1"].ToString(), out int n1) &&
                    n1 > 0 && n1 < 500)
                {
                    var rng = new NadekoRandom();

                    var rolls = new List<char>();

                    for (int i = 0; i < n1; i++)
                    {
                        rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
                    }
                    var embed = _eb.Create()
                        .WithOkColor()
                        .WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
                        .AddField(Format.Bold("Result"), string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
                    
                    await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
                }
                else if ((match = dndRegex.Match(arg)).Length != 0)
                {
                    var rng = new NadekoRandom();
                    if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
                        int.TryParse(match.Groups["n2"].ToString(), out int n2) &&
                        n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
                    {
                        if (!int.TryParse(match.Groups["add"].Value, out int add))
                            add = 0;
                        if (!int.TryParse(match.Groups["sub"].Value, out int sub))
                            sub = 0;

                        var arr = new int[n1];
                        for (int i = 0; i < n1; i++)
                        {
                            arr[i] = rng.Next(1, n2 + 1);
                        }

                        var sum = arr.Sum();
                        var embed = _eb.Create().WithOkColor()
                            .WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
                            .AddField(Format.Bold("Rolls"), string.Join(" ",
                                (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x =>
                                    Format.Code(x.ToString()))))
                            .AddField(Format.Bold("Sum"),
                                sum + " + " + add + " - " + sub + " = " + (sum + add - sub));
                        await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
                    }
                }
            }

            [NadekoCommand, Aliases]
            public async Task NRoll([Leftover] string range)
            {
                int rolled;
                if (range.Contains("-"))
                {
                    var arr = range.Split('-')
                        .Take(2)
                        .Select(int.Parse)
                        .ToArray();
                    if (arr[0] > arr[1])
                    {
                        await ReplyErrorLocalizedAsync(strs.second_larger_than_first).ConfigureAwait(false);
                        return;
                    }
                    rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
                }
                else
                {
                    rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
                }

                await ReplyConfirmLocalizedAsync(strs.dice_rolled(Format.Bold(rolled.ToString()))).ConfigureAwait(false);
            }

            private Image<Rgba32> GetDice(int num)
            {
                if (num < 0 || num > 10)
                    throw new ArgumentOutOfRangeException(nameof(num));

                if (num == 10)
                {
                    var images = _images.Dice;
                    using (var imgOne = Image.Load(images[1]))
                    using (var imgZero = Image.Load(images[0]))
                    {
                        return new[] { imgOne, imgZero }.Merge();
                    }
                }
                return Image.Load(_images.Dice[num]);
            }
        }
    }
}