public final class Krobot
{
    public static final String VERSION = "3.0.0 ALPHA-10";

    public static final String PROPERTY_TOKEN = "krobot.key";
    public static final String PROPERTY_TOKEN_FILE = "krobot.keyFile";
    public static final String PROPERTY_DISABLE_TOKEN_SAVING = "krobot.disableKeySaving";
    public static final String PROPERTY_DISABLE_ASKING_TOKEN = "krobot.disableAskingKey";
    public static final String PROPERTY_DISABLE_START_MESSAGE = "krobot.disableStartMessage";
    public static final String PROPERTY_DISABLE_STATE_BAR = "krobot.disableStateBar";
    public static final String PROPERTY_DISABLE_CONSOLE = "krobot.disableConsole";
    public static final String PROPERTY_DISABLE_COLORS = "krobot.disableColors";

    public static KrobotRunner create()
    {
        return new KrobotRunner();
    }

    public static KrobotRuntime getRuntime()
    {
        return KrobotRuntime.get();
    }
} public abstract class KrobotModule
{
    protected static final Includes COMMANDS = Includes.COMMANDS;
    protected static final Includes CONFIGS = Includes.CONFIGS;
    protected static final Includes EVENTS = Includes.EVENTS;
    protected static final Includes FILTERS = Includes.FILTERS;

    protected Injector injector;

    private List<ImportRules> imports;
    private List<ConfigRules> configs;
    private List<FilterRules> filters;
    private List<Object> eventListeners;

    private String prefix;
    private List<KrobotCommand> commands;
    private List<CommandFilter> commandFilters;
    private Map<String, ArgumentFactory> argTypes;

    private List<Module> guiceModules;

    private List<ConsoleCommand> consoleCommands;

    public KrobotModule()
    {
        this.imports = new ArrayList<>();
        this.configs = new ArrayList<>();
        this.filters = new ArrayList<>();
        this.eventListeners = new ArrayList<>();

        this.commands = new ArrayList<>();
        this.commandFilters = new ArrayList<>();
        this.argTypes = new HashMap<>();

        this.guiceModules = new ArrayList<>();

        this.consoleCommands = new ArrayList<>();
    }

    public abstract void preInit();
    public abstract void init();
    public abstract void postInit();

    protected ImportAccessor from(Class<? extends KrobotModule> module)
    {
        ImportRules rules = new ImportRules(module);
        this.imports.add(rules);

        return new ImportAccessor(rules);
    }

    protected ConfigAccessor config(String path)
    {
        ConfigRules rules = new ConfigRules(path);
        this.configs.add(rules);

        return new ConfigAccessor(rules);
    }

    protected ConfigFolder folder(String path)
    {
        return folder(new File(path));
    }

    protected ConfigFolder folder(File file)
    {
        return new ConfigFolder(configs, file);
    }

    protected FilterAccessor when(Filter filter)
    {
        FilterRules rules = new FilterRules(filter);
        this.filters.add(rules);

        return new FilterAccessor(rules);
    }

    protected FilterAccessor guild(Function<Guild, Boolean> guildFilter)
    {
        return when(context -> {
            Guild guild = context.getGuild();
            if (guild == null)
            {
                return false;
            }

            return guildFilter.apply(guild);
        });
    }

    protected FilterAccessor guild(String guildName)
    {
        return guild(guild -> guild.getName().equalsIgnoreCase(guildName));
    }

    protected CommandAccessor command(String path, Class<? extends CommandHandler> handler)
    {
        return command(path, injector.getInstance(handler));
    }

    protected CommandAccessor command(String path, CommandHandler handler)
    {
        PathCompiler compiler = new PathCompiler(path.trim());

        try
        {
            compiler.compile();
        }
        catch (PathSyntaxException e)
        {
            System.exit(0);
        }

        KrobotCommand command = new KrobotCommand(compiler.label(), compiler.args(), handler);
        commands.add(command);

        return new CommandAccessor(this, command);
    }

    @SafeVarargs /* To suppress an ugly warning */
    protected final /* @SafeVarargs requires final */ void filters(Class<CommandFilter>... filters)
    {
        filters(Stream.of(filters).map(injector::getInstance).toArray(CommandFilter[]::new));
    }

    protected void filters(CommandFilter... filters)
    {
        commandFilters.addAll(Arrays.asList(filters));
    }

    @SafeVarargs /* To suppress an ugly warning */
    protected final /* @SafeVarargs requires final */ void listeners(Class<? extends Object>... listeners) // "? extends Object" needed for SafeVarargs
    {
        listeners(Stream.of(listeners).map(injector::getInstance).toArray(Object[]::new));
    }

    protected void listeners(Object... listeners)
    {
        eventListeners.addAll(Arrays.asList(listeners));
    }

    protected <T> void defineArgType(String name, Class<T> type, ArgumentFactory<T> factory)
    {
        this.argTypes.put(name, factory);
    }

    protected void addGuiceModules(Module... modules)
    {
        this.guiceModules.addAll(Arrays.asList(modules));
    }

    @SafeVarargs /* To suppress an ugly warning */
    protected final /* @SafeVarargs requires final */ void consoleCommands(Class<? extends ConsoleCommand>... commands)
    {
        consoleCommands(Stream.of(commands).map(injector::getInstance).toArray(ConsoleCommand[]::new));
    }

    protected void consoleCommands(ConsoleCommand... commands)
    {
        consoleCommands.addAll(Arrays.asList(commands));
    }

    protected void prefix(String prefix)
    {
        this.prefix = prefix;
    }

    protected JDA jda()
    {
        return KrobotRuntime.get().jda();
    }

    public Injector injector()
    {
        return this.injector;
    }

    public List<ImportRules> getImports()
    {
        return imports;
    }

    public List<ConfigRules> getConfigs()
    {
        return configs;
    }

    public List<FilterRules> getFilters()
    {
        return filters;
    }

    public List<Object> getEventListeners()
    {
        return eventListeners;
    }

    public String getPrefix()
    {
        return prefix;
    }

    public List<KrobotCommand> getCommands()
    {
        return commands;
    }

    public List<CommandFilter> getCommandFilters()
    {
        return commandFilters;
    }

    public Map<String, ArgumentFactory> getArgTypes()
    {
        return argTypes;
    }

    public List<ConsoleCommand> getConsoleCommands()
    {
        return consoleCommands;
    }

    public List<Module> getGuiceModules()
    {
        return guiceModules;
    }
} public class MessageContext
{
    private JDA jda;
    private User user;
    private Message message;
    private MessageChannel channel;

    /**
     * The command Context
     *
     * @param jda Current JDA instance
     * @param user The user that called the command
     * @param message The command message
     * @param channel The channel where the command was called
     */
    public MessageContext(JDA jda, User user, Message message, MessageChannel channel)
    {
        this.jda = jda;
        this.user = user;
        this.message = message;
        this.channel = channel;
    }

    /**
     * Check for a <b>bot</b> required permission<br>
     * Throws a {@link BotNotAllowedException} if it hasn't (by default,
     * it will be caught by the ExceptionHandler to print a specific message).
     *
     * @param permission The permission to check
     *
     * @throws BotNotAllowedException If the bot hasn't the permission
     */
    public void require(Permission permission) throws BotNotAllowedException
    {
        Guild guild = getGuild();
        if (guild == null)
        {
            return;
        }

        if (!guild.retrieveMember(jda.getSelfUser()).complete().hasPermission(permission))
        {
            throw new BotNotAllowedException(permission);
        }
    }

    /**
     * Check for a <b>user</b> required permission<br>
     * Throws a {@link UserNotAllowedException} if it hasn't (by default,
     * it will be caught by the ExceptionHandler to print a specific message).
     *
     * @param permission The permission to check
     *
     * @throws UserNotAllowedException If the user hasn't the permission
     */
    public void requireCaller(Permission permission) throws UserNotAllowedException
    {
        Member member = getMember();
        if (member == null)
        {
            return;
        }

        if (!this.getMember().hasPermission(permission))
        {
            throw new UserNotAllowedException(permission);
        }
    }

    /**
     * Send a message on the context channel
     *
     * @param content The message content
     *
     * @return A Future representing the task result
     */
    public CompletableFuture<Message> send(String content)
    {
        return channel.sendMessage(content).submit();
    }

    /**
     * Send a formatted message on the context channel
     *
     * @param content The message content (will be formated by {@link String#format(String, Object...)}
     * @param args The args for the format
     *
     * @return A Future representing the task result
     */
    public CompletableFuture<Message> send(String content, Object... args)
    {
        return channel.sendMessage(String.format(content, args)).submit();
    }

    /**
     * Send an embed message on the context channel
     *
     * @param content The message content
     *
     * @return A Future representing the task result
     */
    public CompletableFuture<Message> send(MessageEmbed content)
    {
        return channel.sendMessage(content).submit();
    }

    /**
     * Send an embed message on the context channel
     *
     * @param content The message content (will be built)
     *
     * @return A Future representing  task result
     */
    public CompletableFuture<Message> send(EmbedBuilder content)
    {
        return send(content.build());
    }

    public CompletableFuture<Message> info(String title, String message)
    {
        return send(Dialog.info(title, message));
    }

    public CompletableFuture<Message> warn(String title, String message)
    {
        return send(Dialog.warn(title, message));
    }

    public CompletableFuture<Message> error(String title, String message)
    {
        return send(Dialog.error(title, message));
    }

    public boolean hasPermission(Permission... permissions)
    {
        if (this.channel instanceof GuildChannel)
        {
            Member member = getMember();
            if (member == null) {
                try {
                    MessageUtils.deleteAfter(error("Membre inconnu", "Impossible de récupérer le membre de l'auteur de la commande").get(), 2500);
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }

                return false;
            }

            return member.hasPermission((GuildChannel) this.channel, permissions);
        }

        return true;
    }

    public boolean botHasPermission(Permission... permissions)
    {
        if (this.channel instanceof GuildChannel)
        {
            return getBotMember().hasPermission((GuildChannel) this.channel, permissions);
        }

        return true;
    }

    /**
     * @return Return the caller user as a mention
     */
    public String mentionCaller()
    {
        return this.getUser().getAsMention();
    }

    /**
     * @return The current JDA instance
     */
    public JDA getJDA()
    {
    	return jda;
    }
    
    /**
     * @return The guild where the command was called
     */
    public Guild getGuild()
    {
        if (this.channel instanceof GuildChannel)
        {
            return ((GuildChannel) this.getChannel()).getGuild();
        }

        return null;
    }

    public Member getBotMember()
    {

        Guild guild = getGuild();
        if (guild == null)
        {
            return null;
        }

        return guild.getMember(jda.getSelfUser());
    }

    /**
     * @return The guild member that called the command
     */
    public Member getMember()
    {
        Guild guild = getGuild();
        if (guild == null)
        {
            return null;
        }

        return guild.retrieveMember(this.getUser()).complete();
    }

    /**
     * @return The user that called the command
     */
    public User getUser()
    {
        return user;
    }

    /**
     * @return The command message
     */
    public Message getMessage()
    {
        return message;
    }

    /**
     * @return The channel where the command was called
     */
    public MessageChannel getChannel()
    {
        return channel;
    }

    /**
     * @return If the context is from a private message channel
     */
    public boolean isFromPrivateMessage()
    {
        return channel instanceof PrivateChannel;
    }
} @FunctionalInterface
public interface CommandFilter
{
    void filter(CommandCall command, MessageContext context, ArgumentMap args);
} @FunctionalInterface
public interface CommandHandler
{
    Object handle(MessageContext context, ArgumentMap args) throws Exception;
} @Singleton
public class CommandManager
{
    private static final Map<String, ArgumentFactory> argumentFactories = new HashMap<>();

    private KrobotRuntime runtime;

    private List<KrobotCommand> commands;
    private List<CommandFilter> filters;

    @Inject
    public CommandManager(KrobotRuntime runtime)
    {
        this.runtime = runtime;

        this.commands = new ArrayList<>();
        this.filters = new ArrayList<>();
    }

    public void handle(MessageContext context)
    {
        String content = context.getMessage().getContentRaw().trim();
        String prefix = runtime.getFilterRunner().getPrefix(context);

        String botMention = "<@!" + runtime.jda().getSelfUser().getId() + "> ";

        if (content.startsWith(botMention) && !content.equals(botMention) && !runtime.getRootModule().getModule().getClass().isAnnotationPresent(DisableMention.class))
        {
            prefix = botMention;
        }

        if (prefix != null && (!content.startsWith(prefix) || content.equals(prefix)))
        {
            return;
        }

        if (content.startsWith(botMention))
        {
            content = content.substring(botMention.length());
        }
        else if (prefix != null && content.startsWith(prefix))
        {
            content = content.substring(prefix.length());
        }

        String[] split = splitWithQuotes(content);

        if (split.length == 0)
        {
            return;
        }

        String label = split[0];

        Optional<KrobotCommand> optional = commands.stream().filter(c -> {
            if (c.getAliases() != null)
            {
                Optional<String> alias = Stream.of(c.getAliases()).filter(a -> a.equalsIgnoreCase(label)).findFirst();

                if (alias.isPresent())
                {
                    return true;
                }
            }

            return c.getLabel().equalsIgnoreCase(label);
        }).findFirst();

        if (!optional.isPresent())
        {
            return;
        }

        KrobotCommand command = optional.get();
        String[] args = ArrayUtils.subarray(split, 1, split.length);

        if (command.getHandler().getClass().isAnnotationPresent(DisableMention.class) && Objects.equals(prefix, botMention))
        {
            return;
        }

        if (args.length > 0)
        {
            Optional<KrobotCommand> sub = command.getSubCommands().stream().filter(s -> s.getLabel().equalsIgnoreCase(args[0]) || ArrayUtils.contains(s.getAliases(), args[0])).findFirst();

            if (sub.isPresent())
            {
                try
                {
                    execute(context, sub.get(), ArrayUtils.subarray(args, 1, args.length));
                }
                catch (Exception e)
                {
                    runtime.getExceptionHandler().handle(context, command, args, e);
                }

                return;
            }
        }

        try
        {
            execute(context, command, args);
        }
        catch (Exception e)
        {
            runtime.getExceptionHandler().handle(context, command, args, e);
        }
    }

    public void execute(MessageContext context, KrobotCommand command, String[] args) throws Exception
    {
        CommandHandler handler = command.getHandler();

        if (handler.getClass().isAnnotationPresent(BotRequires.class))
        {
            for (Permission perm : handler.getClass().getAnnotation(BotRequires.class).value())
            {
                if (!context.botHasPermission(perm))
                {
                    throw new BotNotAllowedException(perm);
                }
            }
        }

        if (handler.getClass().isAnnotationPresent(UserRequires.class))
        {
            for (Permission perm : handler.getClass().getAnnotation(UserRequires.class).value())
            {
                if (!context.hasPermission(perm))
                {
                    throw new UserNotAllowedException(perm);
                }
            }
        }

        if (handler.getClass().isAnnotationPresent(GuildOnly.class) && context.isFromPrivateMessage())
        {
            throw new PrivateChannelNotSupportedException();
        }

        Map<String, Object> supplied = new HashMap<>();

        int i;

        for (i = 0; i < command.getArguments().length; i++)
        {
            CommandArgument arg = command.getArguments()[i];

            if (i > args.length - 1)
            {
                if (arg.isRequired())
                {
                    throw new WrongArgumentNumberException(command, args.length);
                }

                break;
            }

            if (arg.isList())
            {
                List list = new ArrayList();

                for (; i < args.length; i++)
                {
                    list.add(arg.getFactory().process(args[i]));
                }

                supplied.put(arg.getKey(), list.toArray(arg.getFactory().createArray()));
            }
            else
            {
                supplied.put(arg.getKey(), arg.getFactory().process(args[i]));
            }
        }

        if (i < args.length - 1)
        {
            throw new WrongArgumentNumberException(command, args.length);
        }

        ArgumentMap argsMap = new ArgumentMap(supplied);

        CommandCall call = new CommandCall(command);

        if (command.getFilters() != null)
        {
            for (CommandFilter filter : command.getFilters())
            {
                filter.filter(call, context, argsMap);
            }
        }

        if (!call.isCancelled())
        {
            if (!command.getHandler().getClass().isAnnotationPresent(NoTyping.class) && context.getChannel() != null)
            {
                context.getChannel().sendTyping().queue();
            }

            final MessageContext originalContext = context;
            Object result;

            try
            {
                if (command.getHandleMP())
                	context = new MessageContext(context.getJDA(), context.getUser(), context.getMessage(), context.getUser().openPrivateChannel().complete());

                result = command.getHandler().handle(context, argsMap);
            }
            catch (Throwable t)
            {
                runtime.getExceptionHandler().handle(context, command, args, t);
                return;
            }

            if (originalContext.botHasPermission(Permission.MESSAGE_MANAGE) && originalContext.getGuild() != null /* Check we are not in dm */ )
            {
                try
                {
                    originalContext.getMessage().delete().reason("Command triggered").complete();
                }
                catch (Exception ignored)
                {
                }
            }

            if (result != null)
            {
                if (result instanceof EmbedBuilder)
                {
                    context.send((EmbedBuilder) result);
                }
                else if (result instanceof MessageEmbed)
                {
                    context.send((MessageEmbed) result);
                }
                else if (result instanceof RestAction)
                {
                    ((RestAction) result).queue();
                }
                else if (!(result instanceof Future))
                {
                    context.send(result.toString());
                }
            }
        }
    }

    /**
     * Split a message from whitespaces, ignoring the one in quotes.<br><br>
     *
     * <b>Example :</b>
     *
     * <pre>
     *     I am a "discord bot"
     * </pre>
     *
     * Will return ["I", "am", "a", "discord bot"].
     *
     * @param line The line to split
     *
     * @return The line split
     */
    public static String[] splitWithQuotes(String line)
    {
        ArrayList<String> matchList = new ArrayList<>();
        Pattern regex = Pattern.compile("[^\\s\"]+|\"([^\"]*)\"");
        Matcher matcher = regex.matcher(line);

        while (matcher.find())
        {
            if (matcher.group(1) != null)
            {
                matchList.add(matcher.group(1));
            }
            else
            {
                matchList.add(matcher.group());
            }
        }

        return matchList.toArray(new String[matchList.size()]);
    }

    public List<KrobotCommand> getCommands()
    {
        return commands;
    }

    public List<CommandFilter> getFilters()
    {
        return filters;
    }

    public static void registerArgumentFactory(String name, ArgumentFactory factory)
    {
        argumentFactories.put(name, factory);
    }

    public static ArgumentFactory getArgumentFactory(String key)
    {
        return argumentFactories.get(key);
    }

    static
    {
        registerArgumentFactory("string", new ArgumentFactory<String>()
        {
            @Override
            public String process(String a) throws BadArgumentTypeException
            {
                return a;
            }

            @Override
            public String[] createArray()
            {
                return new String[0];
            }
        });

        registerArgumentFactory("number", new ArgumentFactory<Integer>()
        {
            @Override
            public Integer process(String argument) throws BadArgumentTypeException
            {
                try
                {
                    return Integer.parseInt(argument);
                }
                catch (NumberFormatException e)
                {
                    throw new BadArgumentTypeException(argument, "number");
                }
            }

            @Override
            public Integer[] createArray()
            {
                return new Integer[0];
            }
        });

        registerArgumentFactory("float", new ArgumentFactory<Float>()
        {
            @Override
            public Float process(String argument) throws BadArgumentTypeException
            {
                try
                {
                    return Float.parseFloat(argument);
                }
                catch (NumberFormatException e)
                {
                    throw new BadArgumentTypeException(argument, "float");
                }
            }

            @Override
            public Float[] createArray()
            {
                return new Float[0];
            }
        });

        registerArgumentFactory("user", new ArgumentFactory<User>()
        {
            @Override
            public User process(String argument) throws BadArgumentTypeException
            {
                User result = UserUtils.resolve(argument);

                if (result == null)
                {
                    throw new BadArgumentTypeException("Can't find user '" + argument + "'", argument, "user");
                }

                return result;
            }

            @Override
            public User[] createArray()
            {
                return new User[0];
            }
        });

        // Aliases
        registerArgumentFactory("integer", getArgumentFactory("number"));
        registerArgumentFactory("int", getArgumentFactory("number"));
    }
} @Singleton
public class ExceptionHandler
{
    private static final ColoredLogger log = ColoredLogger.getLogger("ExceptionHandler");

    private Map<Class<? extends Throwable>, IExceptionHandler> handlers = new HashMap<>();

    public void handle(MessageContext context, KrobotCommand command, String[] args, Throwable t)
    {
        Optional<IExceptionHandler> handler = handlers.entrySet().stream().filter(e -> e.getKey().isInstance(t)).map(Entry::getValue).findFirst();

        if (handler.isPresent())
        {
        	if (command.getErrorMP())
        		handler.get().handle(new MessageContext(context.getJDA(), context.getUser(), context.getMessage(), context.getUser().openPrivateChannel().complete()), t);
        	else
        		handler.get().handle(context, t);
            return;
        }

        log.errorAuto("@|red Unhandled |@ @|red,bold '" + t.getClass().getName() + "'|@ @|red while executing command '|@@|red,bold " + command.getLabel() + "|@@|red '|@", t);

        String report = makeCrashReport(t, command, args, context);

        try
        {
            MessageUtils.deleteAfter(context.send(Dialog.error("Command crashed !", "A crash report has been sent to you " + context.getUser().getAsMention() + " . Please send it to the developer as soon as possible !")).get(), 5000);
        }
        catch (InterruptedException | ExecutionException ignored)
        {
        }

        PrivateChannel channel = context.getUser().openPrivateChannel().complete();
        for (String message : MessageUtils.splitMessageKeepLines(report, MessageUtils.MAX_MESSAGE_CHARS - Markdown.code("").length()))
        {
            channel.sendMessage(Markdown.code(message)).queue();
        }
    }

    /**
     * Create a crash report based on an Exception
     *
     * @param throwable The command of the report
     * @param command The command that was being handled when the exception was thrown
     * @param args The arguments of the command
     * @param context The context of the command
     *
     * @return The generated crash report
     */
    protected String makeCrashReport(Throwable throwable, KrobotCommand command, String[] args, MessageContext context)
    {
        StringBuilder builder = new StringBuilder();

        builder.append("####################################\n\n");

        builder.append("Krobot v").append(Krobot.VERSION).append(" crash report\n\n");

        builder.append("Command       : ").append(command.toString()).append("\n");
        builder.append("Args          : ").append(Arrays.toString(args)).append("\n");
        builder.append("Caller        : ").append(context.getUser().getName()).append("#").append(context.getUser().getDiscriminator()).append("\n");
        builder.append("Conversation  : #").append(context.getChannel().getName()).append("\n");
        builder.append("Time          : ").append(new Date()).append("\n\n");

        builder.append(ExceptionUtils.getStackTrace(throwable)).append("\n\n");

        builder.append("####################################");

        return builder.toString();
    }

    public <T extends Throwable> void on(Class<T> cl, IExceptionHandler<T> handler)
    {
        handlers.put(cl, handler);
    }

    // Default handlers
    {
        on(WrongArgumentNumberException.class, (context, t) -> context.send(Dialog.warn("Wrong number of argument", t.getMessage())));
        on(BadArgumentTypeException.class, (context, t) -> {
            String message = t.getMessage() != null ? t.getMessage() : "Can't convert '" + t.getValue() + "' to a '" + t.getType() + "'";
            context.send(Dialog.warn("Wrong argument types", message));
        });

        on(BotNotAllowedException.class, (context, t) -> context.send(Dialog.error("Missing required permission", t.getMessage())));
        on(UserNotAllowedException.class, (context, t) -> context.send(Dialog.error("You're missing a required permission", t.getMessage())));
        on(PrivateChannelNotSupportedException.class, (context, t) -> context.send(Dialog.error("Unsupported channel", t.getMessage())));
    }
} public class KrobotCommand
{
    private String label;
    private String[] aliases;
    private CommandArgument[] arguments;
    private String description;
    private boolean errorMP; //error in private message
    private boolean handleMP; //private message only
    private List<CommandFilter> filters;
    private List<KrobotCommand> subs;
    private CommandHandler handler;


    public KrobotCommand(String label, CommandArgument[] arguments, CommandHandler handler)
    {
        this(label, arguments, handler, new ArrayList<>());
    }

    public KrobotCommand(String label, CommandArgument[] arguments, CommandHandler handler, List<KrobotCommand> subs)
    {
        this(label, arguments, "", new String[] {}, false, false, new ArrayList<>(), handler, new ArrayList<>());
    }

    public KrobotCommand(String label, CommandArgument[] arguments, String description, String[] aliases, boolean errorMP, boolean handleMP, List<CommandFilter> filters, CommandHandler handler, List<KrobotCommand> subs)
    {
        this.label = label;
        this.aliases = aliases;
        this.arguments = arguments;
        this.description = description;
        this.errorMP = errorMP;
        this.handleMP = handleMP;
        this.filters = filters;
        this.subs = subs;
        this.handler = handler;
    }

    public String getLabel()
    {
        return label;
    }

    public void setLabel(String label)
    {
        this.label = label;
    }

    public String[] getAliases()
    {
        return aliases;
    }

    public void setAliases(String[] aliases)
    {
        this.aliases = aliases;
    }

    public CommandArgument[] getArguments()
    {
        return arguments;
    }

    public void setArguments(CommandArgument[] arguments)
    {
        this.arguments = arguments;
    }

    public String getDescription()
    {
        return description;
    }

    public void setDescription(String description)
    {
        this.description = description;
    }

    public boolean getErrorMP()
    {
    	return errorMP;
    }

    public void setErrorMP(boolean errorMP)
    {
    	this.errorMP = errorMP;
    }

    public boolean getHandleMP()
    {
    	return handleMP;
    }

    public void setHandleMP(boolean handleMP)
    {
    	this.handleMP = handleMP;
    }

    public List<CommandFilter> getFilters()
    {
        return filters;
    }

    public List<KrobotCommand> getSubCommands()
    {
        return subs;
    }

    public CommandHandler getHandler()
    {
        return handler;
    }

    public void setHandler(CommandHandler handler)
    {
        this.handler = handler;
    }

    @Override
    public String toString()
    {
        return toString("", true);
    }

    /**
     * Convert the command to a displayable string
     * (similar to the {@link PathCompiler} syntax)
     *
     * @param prefix The prefix to add (like tabs)
     * @param subs If the subs should be displayed too
     *
     * @return The generated string
     */
    public String toString(String prefix, boolean subs)
    {
        StringBuilder string = new StringBuilder(prefix + this.label + " ");

        for (CommandArgument argument : arguments)
        {
            string.append(argument).append(" ");
        }

        if (subs && this.subs != null && this.subs.size() != 0)
        {
            for (KrobotCommand sub : this.subs)
            {
                string.append("\n");
                string.append(prefix).append(sub.toString(prefix + this.getLabel() + " ", true));
            }
        }

        return string.toString();
    }
} public class PathCompiler
{
    private static final ColoredLogger log = ColoredLogger.getLogger("PathCompiler");

    private String path;

    private int cursor;

    private String label;
    private List<CommandArgument> args;

    public PathCompiler(String path)
    {
        this.path = path;

        this.cursor = 0;
        this.args = new ArrayList<>();
    }

    public void compile()
    {
        label = readWord();

        while (true)
        {
            if (!hasNext())
            {
                break;
            }

            skipEnsure(' ');

            switch (next("'[' or '<'"))
            {
                case '<':
                    readArg(true);
                    break;
                case '[':
                    readArg(false);
                    break;
                default:
                    throw error("Expected '<' or '[' got '" + previous() + "'");
            }
        }
    }

    protected void readArg(boolean required)
    {
        int start = cursor;
        char end = required ? '>' : ']';

        String name = readWord();

        if (args.stream().anyMatch(arg -> arg.getKey().equalsIgnoreCase(name)))
        {
            throw error("Duplicated argument '" + name + "'", cursor - name.length() + 1, cursor);
        }

        String type = null;

        char next = next("':' or '" + end + "'");

        if (next == ':')
        {
            type = readWord();
            next = next("'...', '|' or '" + end + "'");
        }

        ArgumentFactory factory = null;
        boolean list = false;

        if (type != null && next == '|')
        {
            List<String> choices = new ArrayList<>();
            choices.add(type);

            while (next == '|')
            {
                choices.add(readWord());
                next = next("'|' or '" + end + "'");
            }

            type = String.join("|", choices);
            String finalType = type;

            factory = new ArgumentFactory<String>()
            {
                @Override
                public String process(String argument) throws BadArgumentTypeException
                {
                    if (!choices.contains(argument))
                    {
                        throw new BadArgumentTypeException("Can only be one of : " + String.join(", ", choices) + "; but not '" + argument + "'", argument, finalType);
                    }

                    return argument;
                }

                @Override
                public String[] createArray()
                {
                    return new String[0];
                }
            };
        }
        else
        {
            if (next == '.')
            {
                skipEnsure('.');
                skipEnsure('.');

                list = true;

                skipEnsure(end);
            }
            else if (next != end)
            {
                throw error("Expected " + (type == null ? "':' or " : "") + "'" + end + "' got '" + next + "'");
            }
        }

        if (args.size() > 0)
        {
            CommandArgument last = args.get(args.size() - 1);

            if (required && !last.isRequired())
            {
                throw error("Can't put a required argument after an optional one", start, cursor);
            }
            else if (last.isList())
            {
                throw error("List argument must stay the last one", start, cursor);
            }
        }

        type = type == null ? "string" : type;
        factory = factory == null ? CommandManager.getArgumentFactory(type) : factory;

        if (factory == null)
        {
            throw error("Unknown argument type '" + type + "'", cursor - type.length(), cursor - 1);
        }

        args.add(new CommandArgument(required, name, type, factory, list));
    }

    protected void skipEnsure(char c)
    {
        char next = next("'" + c + "'");

        if (next != c)
        {
            throw error("Expected '" + c + "' got '" + next + "'");
        }
    }

    protected String readWord()
    {
        StringBuilder result = new StringBuilder();
        char current;

        while (cursor < path.length())
        {
            current = current();

            if (!Character.isLetterOrDigit(current) && current != '-')
            {
                break;
            }

            result.append(current);
            cursor++;
        }

        return result.toString();
    }

    protected boolean hasNext()
    {
        return cursor < path.length();
    }

    protected char previous()
    {
        return path.charAt(cursor - 1);
    }

    protected char current()
    {
        return path.charAt(cursor);
    }

    protected char next(String expectation)
    {
        if (cursor >= path.length())
        {
            throw error("Unexpected end of path, expected '" + expectation + "'");
        }

        char val = current();
        cursor++;

        return val;
    }

    protected PathSyntaxException error(String message)
    {
        return error(message, cursor);
    }

    protected PathSyntaxException error(String message, int to)
    {
        return error(message, cursor, to);
    }

    protected PathSyntaxException error(String message, int from, int to)
    {
        log.error("");
        log.errorAuto("@|bold,red Error:|@ @|bold,white {}|@", message);
        log.errorAuto("    @|bold,blue -->|@ {}", path);
        log.errorAuto("        " + repeat(' ', from - 1) + "@|bold,red ^" + repeat('^', to - from) + "|@");
        log.errorBold(Color.WHITE, "Aborting due to command path compilation error");

        return new PathSyntaxException();
    }

    protected String repeat(char c, int amount)
    {
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < amount; i++)
        {
            result.append(c);
        }

        return result.toString();
    }

    public String label()
    {
        return this.label;
    }

    public CommandArgument[] args()
    {
        return this.args.toArray(new CommandArgument[this.args.size()]);
    }

    public String getPath()
    {
        return path;
    }

    public static class PathSyntaxException extends RuntimeException
    {
    }
} public class ExitCommand extends ConsoleCommand
{
    @Override
    public String getPath()
    {
        return "exit";
    }

    @Override
    public String getDescription()
    {
        return "Shut down the bot";
    }

    @Override
    public void execute(ArgumentMap args)
    {
        KrobotRuntime.stop();
        System.exit(0);
    }
} public class HelpCommand extends ConsoleCommand
{
    private KrobotConsole console;

    public HelpCommand(KrobotConsole console)
    {
        this.console = console;
    }

    @Override
    public String getPath()
    {
        return "help";
    }

    @Override
    public String getDescription()
    {
        return "Displays a detailed list of commands";
    }

    @Override
    public void execute(ArgumentMap args)
    {
        Ansi result = Ansi.ansi().fgYellow().bold().a("\nList of commands :\n").reset();

        console.getCommands().forEach(command -> {
            result.a("    ").bold().fgBlue().a(command.getLabel()).reset().a(" ");

            for (CommandArgument arg : command.getArguments())
            {
                result.fgCyan().a(arg.toString()).reset().a(" ");
            }

            result.a("\n        ").fgBrightGreen().a(command.getCommand().getDescription()).reset().a("\n");
        });

        System.out.println(result.toString());
    }
} public class KrobotConsole
{
    private KrobotRuntime runtime;

    private List<ComputedConsoleCommand> commands;
    private ConsoleProcessor processor;

    private boolean processing;

    public KrobotConsole(KrobotRuntime runtime)
    {
        this.runtime = runtime;

        this.commands = new ArrayList<>();
        this.processor = new ConsoleProcessor(this, new ConsoleHighlighter(this), new ConsoleCompleter(this));
    }

    public void start()
    {
        this.processor.start();
    }

    public void register(Class<? extends ConsoleCommand> command)
    {
        register(runtime.getInjector().getInstance(command));
    }

    public void register(ConsoleCommand command)
    {
        PathCompiler compiler = new PathCompiler(command.getPath());
        compiler.compile();

        commands.add(new ComputedConsoleCommand(compiler.label(), compiler.args(), command));
    }

    public List<ComputedConsoleCommand> getCommands()
    {
        return commands;
    }

    public boolean isProcessing()
    {
        return processing;
    }

    void setProcessing(boolean processing)
    {
        this.processing = processing;
    }

    public KrobotRuntime getRuntime()
    {
        return runtime;
    }
} public static class ComputedModule
{
    private KrobotModule module;

    private List<FilterRules> filters;
    private List<ConfigRules> configs;
    private List<Pair<ConfigBridge, KrobotModule>> bridges;
    private KrobotCommand parentCommand;

    private Includes[] includes;

    public ComputedModule(KrobotModule module)
    {
        this.module = module;

        this.filters = new ArrayList<>();
        this.configs = new ArrayList<>();
        this.bridges = new ArrayList<>();

        this.includes = null;
    }

    public KrobotModule getModule()
    {
        return module;
    }

    public List<FilterRules> getFilters()
    {
        return filters;
    }

    public List<ConfigRules> getConfigs()
    {
        return configs;
    }

    public List<Pair<ConfigBridge, KrobotModule>> getBridges()
    {
        return bridges;
    }

    public Includes[] getIncludes()
    {
        return includes;
    }

    public void setIncludes(Includes[] includes)
    {
        this.includes = includes;
    }

    public KrobotCommand getParentCommand()
    {
        return parentCommand;
    }

    public void setParentCommand(KrobotCommand parentCommand)
    {
        this.parentCommand = parentCommand;
    }
} public class ColoredLogger
{
    private Logger logger;

    // TODO: fatal

    public ColoredLogger(Logger logger)
    {
        this.logger = logger;
    }

    public static ColoredLogger getLogger(String name)
    {
        return new ColoredLogger(LogManager.getLogger(name));
    }

    public void info(Object text, Object... format)
    {
        logger.info(String.valueOf(text), format);
    }

    public void info(Color color, Object text, Object... format)
    {
        logger.info(ansi().fg(color).a(text).reset().toString(), format);
    }

    public void infoBold(Object text, Object... format)
    {
        logger.info(ansi().bold().a(text).reset().toString(), format);
    }

    public void infoBold(Color color, Object text, Object... format)
    {
        logger.info(ansi().bold().fg(color).a(text).reset().toString(), format);
    }

    public void infoAuto(String text, Object... format)
    {
        logger.info(ansi().render(text).reset().toString(), format);
    }

    public void warn(Object text, Object... format)
    {
        logger.warn(String.valueOf(text), format);
    }

    public void warn(Color color, Object text, Object... format)
    {
        logger.warn(ansi().fg(color).a(text).reset().toString(), format);
    }

    public void warnBold(Object text, Object... format)
    {
        logger.warn(ansi().bold().a(text).reset().toString(), format);
    }

    public void warnBold(Color color, Object text, Object... format)
    {
        logger.warn(ansi().bold().fg(color).a(text).reset().toString(), format);
    }

    public void warnAuto(String text, Object... format)
    {
        logger.warn(ansi().render(text).reset().toString(), format);
    }

    public void error(Object text, Object... format)
    {
        logger.error(String.valueOf(text), format);
    }

    public void error(Color color, Object text, Object... format)
    {
        logger.error(ansi().fg(color).a(text).reset().toString(), format);
    }

    public void errorBold(Object text, Object... format)
    {
        logger.error(ansi().bold().a(text).reset().toString(), format);
    }

    public void errorBold(Color color, Object text, Object... format)
    {
        logger.error(ansi().bold().fg(color).a(text).reset().toString(), format);
    }

    public void errorBold(Color color, Object text, Throwable t)
    {
        logger.error(ansi().bold().fg(color).a(text).reset().toString(), t);
    }

    public void errorAuto(String text, Object... format)
    {
        logger.error(ansi().render(text).reset().toString(), format);
    }

    public void errorAuto(String text, Throwable t)
    {
        logger.error(ansi().render(text).reset().toString(), t);
    }

    public void errorAuto(Color color, String text, Object... format)
    {
        logger.error(ansi().fg(color).render(text).reset().toString(), format);
    }

    public void errorAuto(Color color, String text, Throwable t)
    {
        logger.error(ansi().fg(color).render(text).reset().toString(), t);
    }
}
import static org.fusesource.jansi.Ansi.ansi;
import static org.fusesource.jansi.Ansi.Color.BLUE;
import java.lang.reflect.Field;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.security.auth.login.LoginException;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.entities.MessageChannel;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Color;
import org.fusesource.jansi.AnsiConsole;
import org.krobot.Bot;
import org.krobot.Krobot;
import org.krobot.KrobotModule;
import org.krobot.MessageContext;
import org.krobot.command.Command;
import org.krobot.command.CommandFilter;
import org.krobot.command.CommandHandler;
import org.krobot.command.CommandManager;
import org.krobot.command.ExceptionHandler;
import org.krobot.command.KrobotCommand;
import org.krobot.command.PathCompiler;
import org.krobot.console.ExitCommand;
import org.krobot.console.HelpCommand;
import org.krobot.console.KrobotConsole;
import org.krobot.module.Include;
import org.krobot.module.LoadModule;
import org.krobot.runtime.ModuleLoader.ComputedModule;
import org.krobot.util.ColoredLogger;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import net.dv8tion.jda.api.AccountType;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.SelfUser;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.AnnotatedEventManager;
import net.dv8tion.jda.api.hooks.SubscribeEvent;
/*
 * Copyright 2017 The Krobot Contributors
 *
 * This file is part of Krobot.
 *
 * Krobot is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Krobot is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Krobot.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.krobot.runtime;







public class KrobotRuntime
{
    // TODO: Rolling logger

    public static final int DEFAULT_MAX_THREAD = 25;

    private static final ColoredLogger log = ColoredLogger.getLogger("Krobot");