package com.platymuus.bukkit.permissions;

import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.PluginDescriptionFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;

/**
 * CommandExecutor for /permissions
 */
final class PermissionsCommand implements CommandExecutor {
	private static final String GOLD = ChatColor.GOLD.toString();
	private static final String GREEN = ChatColor.GREEN.toString();
	private static final String AQUA = ChatColor.AQUA.toString();
	private static final String RED = ChatColor.RED.toString();
	private static final String LIGHT_PURPLE = ChatColor.LIGHT_PURPLE.toString();
	private static final String WHITE = ChatColor.WHITE.toString();
	private static final String UNDERLINE = ChatColor.UNDERLINE.toString();
	private static final String RESET = ChatColor.RESET.toString();

    private final PermissionsPlugin plugin;

    public PermissionsCommand(PermissionsPlugin plugin) {
        this.plugin = plugin;
    }

    public boolean onCommand(CommandSender sender, Command command, String label, String[] split) {
        if (split.length < 1) {
            return !checkPerm(sender, "help") || usage(sender, command);
        }

        String subcommand = split[0];
        if (subcommand.equals("reload")) {
            if (!checkPerm(sender, "reload")) return true;
            plugin.reloadConfig();
            if (plugin.configLoadError) {
                plugin.configLoadError = false;
                sender.sendMessage(RED + "Your configuration is invalid, see the console for details.");
            } else {
                plugin.refreshPermissions();
                sender.sendMessage(GREEN + "Configuration reloaded.");
            }
            return true;
        } else if (subcommand.equals("about")) {
            if (!checkPerm(sender, "about")) return true;

            // plugin information
            PluginDescriptionFile desc = plugin.getDescription();
            sender.sendMessage(GOLD + desc.getName() + GREEN + " version " + GOLD + desc.getVersion());
            String auth = desc.getAuthors().get(0);
            for (int i = 1; i < desc.getAuthors().size(); ++i) {
                auth += GREEN + ", " + WHITE + desc.getAuthors().get(i);
            }
            sender.sendMessage(GREEN + "By " + WHITE + auth);
            sender.sendMessage(GREEN + "Website: " + WHITE + desc.getWebsite());

            // stats
            if (!plugin.getMetrics().enabled) {
                sender.sendMessage(GOLD + "Metrics are disabled.");
                return true;
            }

            sender.sendMessage(GOLD + "Features Used:");
            for (Map.Entry<String, String> entry : plugin.getMetrics().summarize(0).entrySet()) {
                sender.sendMessage("  " + GREEN + entry.getKey() + ": " + WHITE + entry.getValue());
            }
            sender.sendMessage(GOLD + "Usage:");
            for (Map.Entry<String, String> entry : plugin.getMetrics().summarize(1).entrySet()) {
                sender.sendMessage("  " + GREEN + entry.getKey() + ": " + WHITE + entry.getValue());
            }

            return true;
        } else if (subcommand.equals("check")) {
            if (!checkPerm(sender, "check")) return true;
            if (split.length != 2 && split.length != 3) return usage(sender, command, subcommand);

            String node = split[1];
            Permissible permissible;
            if (split.length == 2) {
                permissible = sender;
            } else {
                permissible = plugin.getServer().getPlayer(split[2]);
            }

            String name = (permissible instanceof Player) ? ((Player) permissible).getName() : (permissible instanceof ConsoleCommandSender) ? "Console" : "Unknown";

            if (permissible == null) {
                sender.sendMessage(String.format("%sPlayer %s%s%s not found.", RED, WHITE, split[2], RED));
            } else {
                boolean set = permissible.isPermissionSet(node), has = permissible.hasPermission(node);
                String sets = set ? " sets " : " defaults ";
                String perm = has ? "true" : "false";
                sender.sendMessage(String.format("%sPlayer %s%s%s%s%s%s%s to %s%s%s.", GREEN, WHITE, name, GREEN, sets, WHITE, node, GREEN, WHITE, perm, GREEN));
            }
            return true;
        } else if (subcommand.equals("info")) {
            if (!checkPerm(sender, "info")) return true;
            if (split.length != 2) return usage(sender, command, subcommand);

            String node = split[1];
            Permission perm = plugin.getServer().getPluginManager().getPermission(node);

            if (perm == null) {
                sender.sendMessage(RED + "Permission " + WHITE + node + RED + " not found.");
            } else {
                sender.sendMessage(GREEN + "Info on permission " + WHITE + perm.getName() + GREEN + ":");
                sender.sendMessage(GREEN + "Default: " + WHITE + perm.getDefault());
                if (perm.getDescription() != null && perm.getDescription().length() > 0) {
                    sender.sendMessage(GREEN + "Description: " + WHITE + perm.getDescription());
                }
                if (perm.getChildren() != null && perm.getChildren().size() > 0) {
                    sender.sendMessage(GREEN + "Children: " + WHITE + perm.getChildren().size());
                }
                if (perm.getPermissibles() != null && perm.getPermissibles().size() > 0) {
                    int num = 0, numTrue = 0;
                    for (Permissible who : perm.getPermissibles()) {
                        ++num;
                        if (who.hasPermission(perm)) {
                            ++numTrue;
                        }
                    }
                    sender.sendMessage(String.format("%sSet on: %s%d%s (%s%d%s true)", GREEN, WHITE, num, GREEN, WHITE, numTrue, GREEN));
                }
            }
            return true;
        } else if (subcommand.equals("dump")) {
            if (!checkPerm(sender, "dump")) return true;
            if (split.length < 1 || split.length > 3) return usage(sender, command, subcommand);

            int page;
            Permissible permissible;
            if (split.length == 1) {
                permissible = sender;
                page = 1;
            } else if (split.length == 2) {
                permissible = sender;
                try {
                    page = Integer.parseInt(split[1]);
                } catch (NumberFormatException ex) {
                    if (split[1].equalsIgnoreCase("-file")) {
                        page = -1;
                    } else {
                        permissible = plugin.getServer().getPlayer(split[1]);
                        page = 1;
                    }
                }
            } else {
                permissible = plugin.getServer().getPlayer(split[1]);
                try {
                    page = Integer.parseInt(split[2]);
                } catch (NumberFormatException ex) {
                    if (split[2].equalsIgnoreCase("-file")) {
                        page = -1;
                    } else {
                        page = 1;
                    }
                }
            }

            if (permissible == null) {
                sender.sendMessage(String.format("%sPlayer %s%s%s not found.", RED, WHITE, split[1], RED));
                return true;
            }

            ArrayList<PermissionAttachmentInfo> dump = new ArrayList<PermissionAttachmentInfo>(permissible.getEffectivePermissions());
            Collections.sort(dump, new Comparator<PermissionAttachmentInfo>() {
                public int compare(PermissionAttachmentInfo a, PermissionAttachmentInfo b) {
                    return a.getPermission().compareTo(b.getPermission());
                }
            });

            if (page == -1) {
                // Dump to file
                File file = new File(plugin.getDataFolder(), "dump.txt");
                try {
                    FileOutputStream fos = new FileOutputStream(file);
                    PrintStream out = new PrintStream(fos);
                    // right now permissible is always a CommandSender
                    out.println("PermissionsBukkit dump for: " + ((CommandSender)permissible).getName());
                    out.println(new Date().toString());

                    for (PermissionAttachmentInfo info : dump) {
                        PermissionAttachment attachment = info.getAttachment();
                        if(attachment == null) {
                            out.printf("%s=%s (default)", info.getPermission(), Boolean.valueOf(info.getValue()));
                        } else {
                            out.printf("%s=%s (%s)", info.getPermission(), Boolean.valueOf(info.getValue()), attachment.getPlugin().getDescription().getName());
                        }
                    }

                    out.close();
                    fos.close();

                    sender.sendMessage(GREEN + "Permissions dump written to " + WHITE + file.toString());
                } catch (IOException e) {
                    sender.sendMessage(String.format("%sFailed to write to %s, see the console for more details", RED, file));
                    sender.sendMessage(RED + e.toString());
                    e.printStackTrace();
                }
                return true;
            }

            int numpages = 1 + (dump.size() - 1) / 8;
            if (page > numpages) {
                page = numpages;
            } else if (page < 1) {
                page = 1;
            }

            int start = 8 * (page - 1);
            sender.sendMessage(String.format("%s[==== %sPage %d of %d%s ====]", RED, GREEN, page, numpages, RED));
            for (int i = start; i < start + 8 && i < dump.size(); ++i) {
                PermissionAttachmentInfo info = dump.get(i);
                PermissionAttachment attachment = info.getAttachment();
                if(attachment == null) {
                    sender.sendMessage(String.format("%sNode %s%s%s=%s%s%s (%sdefault%s)", GREEN, WHITE, info.getPermission(), GREEN, WHITE, Boolean.valueOf(info.getValue()), GREEN, RED, GREEN));
                } else {
                    sender.sendMessage(String.format("%sNode %s%s%s=%s%s%s (%s%s%s)", GREEN, WHITE, info.getPermission(), GREEN, WHITE, Boolean.valueOf(info.getValue()), GREEN, WHITE, attachment.getPlugin().getDescription().getName(), GREEN));
                }
            }
            return true;
        } else if (subcommand.equals("rank") || subcommand.equals("setrank")) {
            if (!checkPerm(sender, "setrank")) return true;
            if (split.length != 3) return usage(sender, command, subcommand);

            // This is essentially player setgroup with an added check
            UUID player = resolvePlayer(sender, split[1]);
            if (player == null) return true;
            String group = split[2];

            if (!sender.hasPermission("permissions.setrank." + group)) {
                sender.sendMessage(RED + "You do not have permission to add players to " + WHITE + group + RED + ".");
                return true;
            }

            plugin.createNode("users/" + player).set("groups", Arrays.asList(group));
            plugin.refreshForPlayer(player);

            sender.sendMessage(String.format("%sPlayer %s%s%s is now in %s%s%s.", GREEN, WHITE, player, GREEN, WHITE, group, GREEN));
            return true;
        } else if (subcommand.equals("group")) {
            if (split.length < 2) {
                return !checkPerm(sender, "group.help") || usage(sender, command, subcommand);
            }
            groupCommand(sender, command, split);
            return true;
        } else if (subcommand.equals("player")) {
            if (split.length < 2) {
                return !checkPerm(sender, "player.help") || usage(sender, command, subcommand);
            }
            playerCommand(sender, command, split);
            return true;
        } else {
            return !checkPerm(sender, "help") || usage(sender, command);
        }
    }

    private boolean groupCommand(CommandSender sender, Command command, String[] split) {
        String subcommand = split[1];

        if (subcommand.equals("list")) {
            if (!checkPerm(sender, "group.list")) return true;
            if (split.length != 2) return usage(sender, command, "group list");

            String result = "", sep = "";
            for (String key : plugin.getNode("groups").getKeys(false)) {
                result += sep + key;
                sep = ", ";
            }
            sender.sendMessage(GREEN + "Groups: " + WHITE + result);
            return true;
        } else if (subcommand.equals("players")) {
            if (!checkPerm(sender, "group.players")) return true;
            if (split.length != 3) return usage(sender, command, "group players");
            String group = split[2];

            if (plugin.getNode("groups/" + group) == null) {
                sender.sendMessage(RED + "No such group " + WHITE + group + RED + ".");
                return true;
            }

            List<String> users = new LinkedList<String>();
            for (String userKey : plugin.getNode("users").getKeys(false)) {
                ConfigurationSection node = plugin.getNode("users/" + userKey);
                if (node.getStringList("groups").contains(group)) {
                    try {
                        // show UUID and name if available
                        UUID uuid = UUID.fromString(userKey);
                        String name = node.getString("name", "???");
                        users.add(String.format("%s%s (%s%s%s)", name, GREEN, WHITE, uuid, GREEN));
                    } catch (IllegalArgumentException ex) {
                        // show as unconverted name-only entry
                        users.add(userKey + GREEN + " (" + WHITE + "unconverted" + GREEN + ")");
                    }
                }
            }
            sender.sendMessage(String.format("%sUsers in %s%s%s (%s%d%s):", GREEN, WHITE, group, GREEN, WHITE, users.size(), GREEN));
            for (String user : users) {
                sender.sendMessage("  " + user);
            }
            return true;
        } else if (subcommand.equals("setperm")) {
            if (!checkPerm(sender, "group.setperm")) return true;
            if (split.length != 4 && split.length != 5) return usage(sender, command, "group setperm");
            String group = split[2];
            String perm = split[3];
            boolean value = (split.length != 5) || Boolean.parseBoolean(split[4]);

            String node = "permissions";
            if (plugin.getNode("groups/" + group) == null) {
                sender.sendMessage(RED + "No such group " + WHITE + group + RED + ".");
                return true;
            }

            if (perm.contains(":")) {
                String world = perm.substring(0, perm.indexOf(':'));
                perm = perm.substring(perm.indexOf(':') + 1);
                node = "worlds/" + world;
            }

            plugin.createNode("groups/" + group + "/" + node).set(perm, value);
            plugin.refreshForGroup(group);

            sender.sendMessage(String.format("%sGroup %s%s%s now has %s%s%s = %s%s%s.", GREEN, WHITE, group, GREEN, WHITE, perm, GREEN, WHITE, Boolean.valueOf(value), GREEN));
            return true;
        } else if (subcommand.equals("unsetperm")) {
            if (!checkPerm(sender, "group.unsetperm")) return true;
            if (split.length != 4) return usage(sender, command, "group unsetperm");
            String group = split[2].toLowerCase();
            String perm = split[3];

            String node = "permissions";
            if (plugin.getNode("groups/" + group) == null) {
                sender.sendMessage(RED + "No such group " + WHITE + group + RED + ".");
                return true;
            }

            if (perm.contains(":")) {
                String world = perm.substring(0, perm.indexOf(':'));
                perm = perm.substring(perm.indexOf(':') + 1);
                node = "worlds/" + world;
            }

            ConfigurationSection sec = plugin.createNode("groups/" + group + "/" + node);
            if (!sec.contains(perm)) {
                sender.sendMessage(String.format("%sGroup %s%s%s did not have %s%s%s set.", GREEN, WHITE, group, GREEN, WHITE, perm, GREEN));
                return true;
            }
            sec.set(perm, null);
            plugin.refreshForGroup(group);

            sender.sendMessage(String.format("%sGroup %s%s%s no longer has %s%s%s set.", GREEN, WHITE, group, GREEN, WHITE, perm, GREEN));
            return true;
        } else {
            return !checkPerm(sender, "group.help") || usage(sender, command);
        }
    }

    private boolean playerCommand(CommandSender sender, Command command, String[] split) {
        String subcommand = split[1];

        if (subcommand.equals("groups")) {
            if (!checkPerm(sender, "player.groups")) return true;
            if (split.length != 3) return usage(sender, command, "player groups");
            UUID player = resolvePlayer(sender, split[2]);
            if (player == null) return true;

            if (plugin.getNode("users/" + player) == null) {
                sender.sendMessage(GREEN + "Player " + WHITE + player + RED + " is in the default group.");
                return true;
            }

            int count = 0;
            String text = "", sep = "";
            for (String group : plugin.getNode("users/" + player).getStringList("groups")) {
                ++count;
                text += sep + group;
                sep = ", ";
            }
            sender.sendMessage(String.format("%sPlayer %s%s%s is in groups (%s%d%s): %s%s", GREEN, WHITE, player, GREEN, WHITE, count, GREEN, WHITE, text));
            return true;
        } else if (subcommand.equals("setgroup")) {
            if (!checkPerm(sender, "player.setgroup")) return true;
            if (split.length != 4) return usage(sender, command, "player setgroup");
            UUID player = resolvePlayer(sender, split[2]);
            if (player == null) return true;
            String[] groups = split[3].split(",");

            plugin.createNode("users/" + player).set("groups", Arrays.asList(groups));
            plugin.refreshForPlayer(player);

            sender.sendMessage(String.format("%sPlayer %s%s%s is now in %s%s%s.", GREEN, WHITE, player, GREEN, WHITE, split[3], GREEN));
            return true;
        } else if (subcommand.equals("addgroup")) {
            if (!checkPerm(sender, "player.addgroup")) return true;
            if (split.length != 4) return usage(sender, command, "player addgroup");
            UUID player = resolvePlayer(sender, split[2]);
            if (player == null) return true;
            String group = split[3];

            List<String> list = plugin.createNode("users/" + player).getStringList("groups");
            if (list.contains(group)) {
                sender.sendMessage(String.format("%sPlayer %s%s%s was already in %s%s%s.", GREEN, WHITE, player, GREEN, WHITE, group, GREEN));
                return true;
            }
            list.add(group);
            plugin.getNode("users/" + player).set("groups", list);

            plugin.refreshForPlayer(player);

            sender.sendMessage(String.format("%sPlayer %s%s%s is now in %s%s%s.", GREEN, WHITE, player, GREEN, WHITE, group, GREEN));
            return true;
        } else if (subcommand.equals("removegroup")) {
            if (!checkPerm(sender, "player.removegroup")) return true;
            if (split.length != 4) return usage(sender, command, "player removegroup");
            UUID player = resolvePlayer(sender, split[2]);
            if (player == null) return true;
            String group = split[3];

            List<String> list = plugin.createNode("users/" + player).getStringList("groups");
            if (!list.contains(group)) {
                sender.sendMessage(String.format("%sPlayer %s%s%s was not in %s%s%s.", GREEN, WHITE, player, GREEN, WHITE, group, GREEN));
                return true;
            }
            list.remove(group);
            plugin.getNode("users/" + player).set("groups", list);

            plugin.refreshForPlayer(player);

            sender.sendMessage(String.format("%sPlayer %s%s%s is no longer in %s%s%s.", GREEN, WHITE, player, GREEN, WHITE, group, GREEN));
            return true;
        } else if (subcommand.equals("setperm")) {
            if (!checkPerm(sender, "player.setperm")) return true;
            if (split.length != 4 && split.length != 5) return usage(sender, command, "player setperm");
            UUID player = resolvePlayer(sender, split[2]);
            if (player == null) return true;
            String perm = split[3];
            boolean value = (split.length != 5) || Boolean.parseBoolean(split[4]);

            String node = "permissions";
            if (perm.contains(":")) {
                String world = perm.substring(0, perm.indexOf(':'));
                perm = perm.substring(perm.indexOf(':') + 1);
                node = "worlds/" + world;
            }

            plugin.createNode("users/" + player + "/" + node).set(perm, value);
            plugin.refreshForPlayer(player);

            sender.sendMessage(String.format("%sPlayer %s%s%s now has %s%s%s = %s%s%s.", GREEN, WHITE, player, GREEN, WHITE, perm, GREEN, WHITE, Boolean.valueOf(value), GREEN));
            return true;
        } else if (subcommand.equals("unsetperm")) {
            if (!checkPerm(sender, "player.unsetperm")) return true;
            if (split.length != 4) return usage(sender, command, "player unsetperm");
            UUID player = resolvePlayer(sender, split[2]);
            if (player == null) return true;
            String perm = split[3];

            String node = "permissions";
            if (perm.contains(":")) {
                String world = perm.substring(0, perm.indexOf(':'));
                perm = perm.substring(perm.indexOf(':') + 1);
                node = "worlds/" + world;
            }

            ConfigurationSection sec = plugin.createNode("users/" + player + "/" + node);
            if (!sec.contains(perm)) {
                sender.sendMessage(String.format("%sPlayer %s%s%s did not have %s%s%s set.", GREEN, WHITE, player, GREEN, WHITE, perm, GREEN));
                return true;
            }
            sec.set(perm, null);
            plugin.refreshForPlayer(player);

            sender.sendMessage(String.format("%sPlayer %s%s%s no longer has %s%s%s set.", GREEN, WHITE, player, GREEN, WHITE, perm, GREEN));
            return true;
        } else {
            return !checkPerm(sender, "player.help") || usage(sender, command);
        }
    }

    private UUID resolvePlayer(CommandSender sender, String arg) {
        arg = arg.toLowerCase();

        // see if it resolves to a single player name
        List<Player> players = plugin.getServer().matchPlayer(arg);
        if (players.size() == 1) {
            return players.get(0).getUniqueId();
        } else if (players.size() > 1) {
            sender.sendMessage(RED + "Username " + WHITE + arg + RED + " is ambiguous.");
            return null;
        }

        if (arg.length() == 32) {
            // expand UUIDs which do not have dashes in them
            arg = arg.substring(0, 8) + "-" + arg.substring(8, 12) + "-" + arg.substring(12, 16) +
                    "-" + arg.substring(16, 20) + "-" + arg.substring(20, 32);
        }
        if (arg.length() == 36) {
            // is of correct UUID length
            try {
                return UUID.fromString(arg);
            } catch (IllegalArgumentException ex) {
                // ignore
            }
        }

        sender.sendMessage(RED + "Could not resolve player: " + WHITE + arg);
        sender.sendMessage(RED + "You must provide a UUID or the name of an online player.");
        return null;
    }

    // -- utilities --

    private boolean checkPerm(CommandSender sender, String subnode) {
        boolean ok = sender.hasPermission("permissions." + subnode);
        if (!ok) {
            sender.sendMessage(RED + "You do not have permissions to do that.");
        }
        return ok;
    }

    private boolean usage(CommandSender sender, Command command) {
        sender.sendMessage(RED + "[====" + GREEN + " permissons " + RED + "====]");
        for (String line : command.getUsage().split("\\n")) {
            if ((line.startsWith("/<command> group") && !line.startsWith("/<command> group -")) ||
                    (line.startsWith("/<command> player") && !line.startsWith("/<command> player -"))) {
                continue;
            }
            sender.sendMessage(formatLine(line));
        }
        return true;
    }

    private boolean usage(CommandSender sender, Command command, String subcommand) {
        sender.sendMessage(RED + "[====" + GREEN + " permissons " + subcommand + " " + RED + "====]");
        for (String line : command.getUsage().split("\\n")) {
            if (line.startsWith("<command> " + subcommand)) {
                sender.sendMessage(formatLine(line));
            }
        }
        return true;
    }

	private String formatLine(String line) {
		int i = line.indexOf(" - ");
		String usage = line.substring(0, i);
		String desc = line.substring(i + 3);
		usage = usage.replace("<command>", "permissions");
		usage = usage.replaceAll("(\\[|(\\.{3})?\\])", AQUA + "$0" + GREEN);
		usage = usage.replaceAll("<[^>]+>", LIGHT_PURPLE + UNDERLINE + "$0" + RESET + GREEN);
		return String.format("%s%s%s - %s%s", GREEN, usage, RESET, WHITE, desc);
	}
}
