<commit_before>﻿using System.Threading.Tasks;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
using Discord;
using System;
using Microsoft.Extensions.DependencyInjection;
using PokemonGoRaidBot.Config;
using System.Collections.Generic;
using PokemonGoRaidBot.Objects;
using System.Linq;
using Discord.Rest;
using PokemonGoRaidBot.Parsing;
using System.Net;
using Newtonsoft.Json;
using System.IO;
using System.Text.RegularExpressions;

namespace PokemonGoRaidBot
{
    public class CommandHandler
    {
        private CommandService commands;
        private DiscordSocketClient bot;
        private IServiceProvider map;

        public RaidLogger Logger;
        public readonly BotConfig Config;

        private List<MessageParser> ParserCache = new List<MessageParser>();

        Dictionary<ulong, ISocketMessageChannel> channelCache;

        public CommandHandler(IServiceProvider provider, BotConfig botconfig, RaidLogger logger)
        {
            map = provider;
            bot = map.GetService<DiscordSocketClient>();

            Logger = logger;
            Config = botconfig;
            
            bot.MessageReceived += HandleCommand;
            commands = map.GetService<CommandService>();
            channelCache = new Dictionary<ulong, ISocketMessageChannel>();
        }
        
        public async Task ConfigureAsync()
        {
            await commands.AddModulesAsync(Assembly.GetEntryAssembly());
        }

        public async Task HandleCommand(SocketMessage pMsg)
        {
            try
            {
                if (pMsg.Source == MessageSource.System && pMsg.Author.Id == bot.CurrentUser.Id)
                {//this is the annoying "x pinned a message to this channel" system message.
                    await pMsg.Channel.DeleteMessagesAsync(new IMessage[] { pMsg });
                    return;
                }
                var message = pMsg as SocketUserMessage;

                if (message == null || message.Author == null || message.Author.IsBot)
                    return;

                var guild = ((SocketGuildChannel)message.Channel).Guild;

                var firstLoad = !Config.HasGuildConfig(guild.Id);

                var guildConfig = Config.GetGuildConfig(guild.Id);

                if (firstLoad)//pin all on first load
                {
                    foreach(var channel in ((SocketGuildChannel)message.Channel).Guild.Channels)
                    {
                        guildConfig.PinChannels.Add(channel.Id);
                    }
                }

                ISocketMessageChannel outputchannel = null;
                var pin = guildConfig.PinChannels.Contains(message.Channel.Id);

                //get output channel
                if (guildConfig.OutputChannelId.HasValue)
                    outputchannel = (ISocketMessageChannel)guild.Channels.FirstOrDefault(x => x.Id == guildConfig.OutputChannelId.Value);
                else
                    outputchannel = (ISocketMessageChannel)guild.Channels.FirstOrDefault(x => x.Name == Config.OutputChannel);

                var context = new SocketCommandContext(bot, message);

                //get configured guild language or default "en-us"
                var lang = guildConfig.Language ?? "en-us";
                //timezone of the bot machine
                var botTimezone = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).Hours - (TimeZoneInfo.Local.IsDaylightSavingTime(DateTime.Now) ? 1 : 0);
                //get configured timezone
                var serverTimezone = guildConfig.Timezone ?? botTimezone;
                var timeOffset = serverTimezone - botTimezone;

                var parser = GetParser(lang, timeOffset);//new MessageParser(lang, serverTimezone - botTimezone);

                var doPost = (outputchannel != null || pin) && !guildConfig.MuteChannels.Contains(message.Channel.Id);

                var argPos = 0;

                //begin parsing/execution
                //Someone is issuing a command, respond in their channel
                if (message.HasStringPrefix(Config.Prefix, ref argPos))
                {
                    await DoCommand(message, parser);
                }
                //possibly a response to someone who posted a raid -- MOVING INTO DoPost
                //else if (doPost && message.MentionedUsers.Count() > 0)
                //{
                //    await DoResponse(message, parser);
                //}
                //try to see if a raid was posted
                else if (doPost)
                {
                    await DoPost(message, parser, outputchannel, pin);
                }
            }
            catch (Exception e)
            {
                DoError(e);
            }
        }
        /// <summary>
        /// Used as a recurring method which will remove old posts to keep the output channel clean of expired raids.
        /// </summary>
        /// <param name="stateInfo"></param>
        public async void PurgePosts()
        {
            try
            {
                var now = DateTime.Now;
                int remaining = 0, deleted = 0;
                foreach (var guild in bot.Guilds)
                {
                    var deletedPosts = new List<PokemonRaidPost>();
                    var posts = Config.GetGuildConfig(guild.Id).Posts;
                    foreach (var post in posts)
                    {
                        if (post.EndDate < now)
                        {
                            deletedPosts.Add(post);
                            deleted++;
                        }
                        else remaining++;
                    }

                    foreach (var post in deletedPosts)
                    {
                        var outputChannel = GetChannel(post.OutputChannelId);
                        var fromChannel = GetChannel(post.FromChannelId);

                        var messages = new List<IMessage>();

                        if (post.OutputMessageId != default(ulong))
                        {
                            try
                            {

                                var m = new IMessage[] { await outputChannel.GetMessageAsync(post.OutputMessageId) };
                                messages.AddRange(m.Where(x => x != null));
                            }
                            catch (Exception e)
                            {
                                DoError(e);
                            }
                        }
                        try
                        {
                            if (messages.Count() > 0) await outputChannel.DeleteMessagesAsync(messages);
                            posts.Remove(post);
                        }
                        catch (Exception e)
                        {
                            DoError(e);
                        }

                        if (post.MessageId > 0)
                        {
                            var m1 = new IMessage[] { await fromChannel.GetMessageAsync(post.MessageId) };

                            if (m1.Count() > 0 && m1[0] != null)
                            {
                                try
                                {
                                    if (m1[0] is SocketUserMessage && ((SocketUserMessage)m1[0]).IsPinned)
                                        await ((SocketUserMessage)m1[0]).UnpinAsync();

                                    await m1[0].DeleteAsync();
                                }
                                catch (Exception e)
                                {
                                    DoError(e);
                                }
                            }
                        }
                    }
                }
                Config.Save();
                await Logger.Log(new LogMessage(LogSeverity.Debug, "handler", string.Format("deleted:{0}; remaining:{1}", deleted, remaining)));
            }
            catch (Exception e)
            {
                DoError(e);
            }
        }
        /// <summary>
        /// Output an error to the bot console.
        /// </summary>
        /// <param name="e"></param>
        public void DoError(Exception e, string source = "handler")
        {
            Logger.Log(new LogMessage(LogSeverity.Error, source, null, e));
        }
        /// <summary>
        /// Executes a explicit bot command
        /// </summary>
        /// <param name="message"></param>
        /// <param name="parser"></param>
        /// <returns></returns>
        private async Task DoCommand(SocketUserMessage message, MessageParser parser)
        {
            var executor = new CommandExecutor(this, message, parser);
            await executor.Execute();
        }
        /// <summary>
        /// Performs the raid post behavior
        /// </summary>
        /// <param name="message"></param>
        /// <param name="parser"></param>
        /// <param name="outputchannel"></param>
        /// <param name="pin"></param>
        /// <returns></returns>
        private async Task DoPost(SocketUserMessage message, MessageParser parser, ISocketMessageChannel outputchannel, bool pin)
        {
            var post = await parser.ParsePost(message, Config);//await DoResponse(message, parser);//returns null if 

            if (post != null)
            {
                post.OutputChannelId = outputchannel?.Id ?? 0;
                post.Pin = pin;
                post.GuildId = ((SocketGuildChannel)message.Channel).Guild.Id;
                post.LastMessageDate = DateTime.Now;
                post = AddPost(post, parser, message);

                if (post.PokemonId != default(int))
                    await MakePost(post, parser);
            }

            Config.Save();
        }
        /// <summary>
        /// Creates the text string and outputs the post message into the channel.
        /// If post.MessageId is populated, will delete and recreate the message.
        /// 403 Error is output to console if bot user doesn't have role access to manage messages.
        /// </summary>
        /// <param name="post"></param>
        /// <param name="outputchannel"></param>
        public async Task MakePost(PokemonRaidPost post, MessageParser parser)
        {
            try
            {
                IMessage deleteMessage;
                var fromChannel = GetChannel(post.FromChannelId);
                var outputChannel = GetChannel(post.OutputChannelId);

                //var messages = parser.MakePostStrings(post);
                Embed headerEmbed, responsesEmbed;
                string mentionString;<commit_msg>Removing unnecessary variable<commit_after>﻿using System.Threading.Tasks;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
using Discord;
using System;
using Microsoft.Extensions.DependencyInjection;
using PokemonGoRaidBot.Config;
using System.Collections.Generic;
using PokemonGoRaidBot.Objects;
using System.Linq;
using Discord.Rest;
using PokemonGoRaidBot.Parsing;
using System.Net;
using Newtonsoft.Json;
using System.IO;
using System.Text.RegularExpressions;

namespace PokemonGoRaidBot
{
    public class CommandHandler
    {
        private CommandService commands;
        private DiscordSocketClient bot;
        private IServiceProvider map;

        public RaidLogger Logger;
        public readonly BotConfig Config;

        private List<MessageParser> ParserCache = new List<MessageParser>();

        Dictionary<ulong, ISocketMessageChannel> channelCache;

        public CommandHandler(IServiceProvider provider, BotConfig botconfig, RaidLogger logger)
        {
            map = provider;
            bot = map.GetService<DiscordSocketClient>();

            Logger = logger;
            Config = botconfig;
            
            bot.MessageReceived += HandleCommand;
            commands = map.GetService<CommandService>();
            channelCache = new Dictionary<ulong, ISocketMessageChannel>();
        }
        
        public async Task ConfigureAsync()
        {
            await commands.AddModulesAsync(Assembly.GetEntryAssembly());
        }

        public async Task HandleCommand(SocketMessage pMsg)
        {
            try
            {
                if (pMsg.Source == MessageSource.System && pMsg.Author.Id == bot.CurrentUser.Id)
                {//this is the annoying "x pinned a message to this channel" system message.
                    await pMsg.Channel.DeleteMessagesAsync(new IMessage[] { pMsg });
                    return;
                }
                var message = pMsg as SocketUserMessage;

                if (message == null || message.Author == null || message.Author.IsBot)
                    return;

                var guild = ((SocketGuildChannel)message.Channel).Guild;

                var firstLoad = !Config.HasGuildConfig(guild.Id);

                var guildConfig = Config.GetGuildConfig(guild.Id);

                if (firstLoad)//pin all on first load
                {
                    foreach(var channel in ((SocketGuildChannel)message.Channel).Guild.Channels)
                    {
                        guildConfig.PinChannels.Add(channel.Id);
                    }
                }

                ISocketMessageChannel outputchannel = null;
                var pin = guildConfig.PinChannels.Contains(message.Channel.Id);

                //get output channel
                if (guildConfig.OutputChannelId.HasValue)
                    outputchannel = (ISocketMessageChannel)guild.Channels.FirstOrDefault(x => x.Id == guildConfig.OutputChannelId.Value);
                else
                    outputchannel = (ISocketMessageChannel)guild.Channels.FirstOrDefault(x => x.Name == Config.OutputChannel);

                var context = new SocketCommandContext(bot, message);

                //get configured guild language or default "en-us"
                var lang = guildConfig.Language ?? "en-us";
                //timezone of the bot machine
                var botTimezone = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).Hours - (TimeZoneInfo.Local.IsDaylightSavingTime(DateTime.Now) ? 1 : 0);
                //get configured timezone
                var serverTimezone = guildConfig.Timezone ?? botTimezone;
                var timeOffset = serverTimezone - botTimezone;

                var parser = GetParser(lang, timeOffset);//new MessageParser(lang, serverTimezone - botTimezone);

                var doPost = (outputchannel != null || pin) && !guildConfig.MuteChannels.Contains(message.Channel.Id);

                var argPos = 0;

                //begin parsing/execution
                //Someone is issuing a command, respond in their channel
                if (message.HasStringPrefix(Config.Prefix, ref argPos))
                {
                    await DoCommand(message, parser);
                }
                //possibly a response to someone who posted a raid -- MOVING INTO DoPost
                //else if (doPost && message.MentionedUsers.Count() > 0)
                //{
                //    await DoResponse(message, parser);
                //}
                //try to see if a raid was posted
                else if (doPost)
                {
                    await DoPost(message, parser, outputchannel, pin);
                }
            }
            catch (Exception e)
            {
                DoError(e);
            }
        }
        /// <summary>
        /// Used as a recurring method which will remove old posts to keep the output channel clean of expired raids.
        /// </summary>
        /// <param name="stateInfo"></param>
        public async void PurgePosts()
        {
            try
            {
                var now = DateTime.Now;
                int remaining = 0, deleted = 0;
                foreach (var guild in bot.Guilds)
                {
                    var deletedPosts = new List<PokemonRaidPost>();
                    var posts = Config.GetGuildConfig(guild.Id).Posts;
                    foreach (var post in posts)
                    {
                        if (post.EndDate < now)
                        {
                            deletedPosts.Add(post);
                            deleted++;
                        }
                        else remaining++;
                    }

                    foreach (var post in deletedPosts)
                    {
                        var outputChannel = GetChannel(post.OutputChannelId);
                        var fromChannel = GetChannel(post.FromChannelId);

                        var messages = new List<IMessage>();

                        if (post.OutputMessageId != default(ulong))
                        {
                            try
                            {

                                var m = new IMessage[] { await outputChannel.GetMessageAsync(post.OutputMessageId) };
                                messages.AddRange(m.Where(x => x != null));
                            }
                            catch (Exception e)
                            {
                                DoError(e);
                            }
                        }
                        try
                        {
                            if (messages.Count() > 0) await outputChannel.DeleteMessagesAsync(messages);
                            posts.Remove(post);
                        }
                        catch (Exception e)
                        {
                            DoError(e);
                        }

                        if (post.MessageId > 0)
                        {
                            var m1 = new IMessage[] { await fromChannel.GetMessageAsync(post.MessageId) };

                            if (m1.Count() > 0 && m1[0] != null)
                            {
                                try
                                {
                                    if (m1[0] is SocketUserMessage && ((SocketUserMessage)m1[0]).IsPinned)
                                        await ((SocketUserMessage)m1[0]).UnpinAsync();

                                    await m1[0].DeleteAsync();
                                }
                                catch (Exception e)
                                {
                                    DoError(e);
                                }
                            }
                        }
                    }
                }
                Config.Save();
                await Logger.Log(new LogMessage(LogSeverity.Debug, "handler", string.Format("deleted:{0}; remaining:{1}", deleted, remaining)));
            }
            catch (Exception e)
            {
                DoError(e);
            }
        }
        /// <summary>
        /// Output an error to the bot console.
        /// </summary>
        /// <param name="e"></param>
        public void DoError(Exception e, string source = "handler")
        {
            Logger.Log(new LogMessage(LogSeverity.Error, source, null, e));
        }
        /// <summary>
        /// Executes a explicit bot command
        /// </summary>
        /// <param name="message"></param>
        /// <param name="parser"></param>
        /// <returns></returns>
        private async Task DoCommand(SocketUserMessage message, MessageParser parser)
        {
            var executor = new CommandExecutor(this, message, parser);
            await executor.Execute();
        }
        /// <summary>
        /// Performs the raid post behavior
        /// </summary>
        /// <param name="message"></param>
        /// <param name="parser"></param>
        /// <param name="outputchannel"></param>
        /// <param name="pin"></param>
        /// <returns></returns>
        private async Task DoPost(SocketUserMessage message, MessageParser parser, ISocketMessageChannel outputchannel, bool pin)
        {
            var post = await parser.ParsePost(message, Config);//await DoResponse(message, parser);//returns null if 

            if (post != null)
            {
                post.OutputChannelId = outputchannel?.Id ?? 0;
                post.Pin = pin;
                post.GuildId = ((SocketGuildChannel)message.Channel).Guild.Id;
                post.LastMessageDate = DateTime.Now;
                post = AddPost(post, parser, message);

                if (post.PokemonId != default(int))
                    await MakePost(post, parser);
            }

            Config.Save();
        }
        /// <summary>
        /// Creates the text string and outputs the post message into the channel.
        /// If post.MessageId is populated, will delete and recreate the message.
        /// 403 Error is output to console if bot user doesn't have role access to manage messages.
        /// </summary>
        /// <param name="post"></param>
        /// <param name="outputchannel"></param>
        public async Task MakePost(PokemonRaidPost post, MessageParser parser)
        {
            try
            {
                var fromChannel = GetChannel(post.FromChannelId);
                var outputChannel = GetChannel(post.OutputChannelId);

                //var messages = parser.MakePostStrings(post);
                Embed headerEmbed, responsesEmbed;
                string mentionString;
{'fraction_non_alphanumeric': 0.09073485415108452, 'fraction_numerical': 0.002524308152580404, 'mean_word_length': 1.2926803129353768, 'pattern_counts': {'":': 0, '<': 89, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4492742', 'n_tokens_mistral': 5146, 'n_tokens_neox': 4797, 'n_words': 1211}