package net.glowstone.command;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.entity.Player;
import org.bukkit.title.Title;
import org.bukkit.title.TitleOptions;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import java.util.Arrays;

public class TitleCommand extends BukkitCommand {

    public TitleCommand() {
        super("title");
        this.description = "Sends a title to the specified player(s)";
        this.usageMessage = "/title <player> <title|subtitle|clear|reset|times> ...";
        this.setAliases(Arrays.<String>asList());
        this.setPermission("glowstone.command.title");
    }

    @Override
    public boolean execute(CommandSender sender, String commandLabel, String[] args) {
        if (!testPermission(sender)) return true;
        if (args.length < 2) {
            sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
            return false;
        }

        Player player = Bukkit.getPlayerExact(args[0]);

        if (player == null || (sender instanceof Player && !((Player) sender).canSee(player))) {
            sender.sendMessage("There's no player by that name online.");
        } else {
            if (args[1].equalsIgnoreCase("clear")) {
                player.clearTitle();
                sender.sendMessage("Cleared " + player.getName() + "'s title");
            } else if (args[1].equalsIgnoreCase("reset")) {
                player.resetTitle();
                sender.sendMessage("Reset " + player.getName() + "'s title");
            } else if (args[1].equalsIgnoreCase("times")) {
                if (args.length < 5) {
                    sender.sendMessage(ChatColor.RED + "Usage: /title <player> times <fadeIn> <stay> <fadeOut>");
                    return false;
                }

                TitleOptions options = player.getTitleOptions();

                int in = tryParseInt(sender, args[2], 0);
                int stay = tryParseInt(sender, args[3], in);
                int out = tryParseInt(sender, args[4], stay);

                if (out == -1) {
                    return false;
                }

                options.setFadeInTime(in);
                options.setFadeOutTime(out);
                options.setVisibleTime(stay);
                player.setTitleOptions(options);

                sender.sendMessage("Updated " + player.getName() + "'s title times");
            } else if (args[1].equalsIgnoreCase("title") || args[1].equalsIgnoreCase("subtitle")) {
                if (args.length < 3) {
                    sender.sendMessage(ChatColor.RED + "Usage: /title <player> " + args[1] + " <raw json>");
                    return false;
                }

                StringBuilder message = new StringBuilder();

                for (int i = 2; i < args.length; i++) {
                    if (i > 2) message.append(" ");
                    message.append(args[i]);
                }

                String raw = message.toString().trim();
                if (!validJson(raw)) {
                    sender.sendMessage(ChatColor.RED + "Invalid JSON: Could not parse, invalid format?");
                    return false;
                }

                String component = raw;
                Object parsed = JSONValue.parse(raw);
                if (parsed != null && parsed instanceof JSONObject)
                    component = convertJson((JSONObject) parsed);


                Title currentTitle = player.getTitle();
                if (args[1].equalsIgnoreCase("title")) {
                    currentTitle.setHeading(component);
                } else {
                    currentTitle.setSubtitle(component);
                }
                player.setTitle(currentTitle, args[1].equalsIgnoreCase("title"));

                sender.sendMessage("Updated " + player.getName() + "'s title");
            } else {
                sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
                return false;
            }
        }

        return true;
    }

    private int tryParseInt(CommandSender sender, String number, int lastParse) {
        if (lastParse == -1) {
            return -1;
        }

        int n = -1;
        try {
            n = Integer.parseInt(number);
        } catch (NumberFormatException e) {
            sender.sendMessage(ChatColor.RED + "'" + number + "' is not a number");
        }
        return n;
    }

    // TODO: Replace with a proper JSON chat component validator
    private boolean validJson(String raw) {
        Object object = JSONValue.parse(raw);

        if (object == null) {
            // Could not parse JSON: Check to see if it's at least a single word

            // Rule set:
            // 1. Cannot contain a space (or else the client fails)
            // 2. Must not look like JSON (first character check is sufficient)
            return !raw.contains(" ") && raw.charAt(0) != '{' && raw.charAt(0) != '[';
        }

        if (!(object instanceof JSONObject))
            return false; // Not a valid JSON object

        JSONObject json = (JSONObject) object;

        // Needs a text component at least
        if (!json.containsKey("text") || !(json.get("text") instanceof String))
            return false;

        // If 'extra' is present, some extra handling needs to be done
        if (json.containsKey("extra")) {
            Object extraObj = json.get("extra");

            // Check to make sure it's a valid component
            if (!(extraObj instanceof JSONArray)) return false;

            // Check all components in 'extra'
            JSONArray extra = (JSONArray) extraObj;
            if (extra.isEmpty()) return false; // No components
            for (Object o : extra) {
                if (!(o instanceof JSONObject)) return false;

                // Check to make sure there's a text component
                JSONObject e = (JSONObject) o;
                if (!e.containsKey("text") || !(e.get("text") instanceof String))
                    return false;
            }
        }

        // If we made it this far then it has a pretty good chance at being valid
        return true;
    }

    /**
     * Converts a valid JSON chat component to a basic colored string. This does not parse
     * components like hover or click events. This returns null on parse failure.
     *
     * @param json the json chat component
     *
     * @return the colored string, or null
     */
    // TODO: Replace with proper chat components when possible
    public static String convertJson(JSONObject json) {
        if (json == null || !json.containsKey("text") && !(json.get("text") instanceof String))
            return null; // We can't even parse this

        ChatColor color = ChatColor.WHITE;
        if (json.containsKey("color")) {
            if (!(json.get("color") instanceof String)) return null;
            color = toColor((String) json.get("color"));
        }
        if (color == null) return null; // Invalid color

        String text = color + (String) json.get("text");

        // Check for "extra"
        if (json.containsKey("extra")) {
            Object extraObj = json.get("extra");

            // Check to make sure it's a valid component
            if (!(extraObj instanceof JSONArray)) return null;

            // Check all components in 'extra'
            JSONArray extra = (JSONArray) extraObj;
            if (extra.isEmpty()) return null;
            for (Object o : extra) {
                if (!(o instanceof JSONObject)) return null;

                JSONObject e = (JSONObject) o;

                // Attempt to parse the component
                String temp = convertJson(e);
                if (temp == null) return null; // Could not parse 'extra'

                // It's valid, append it
                text += temp;
            }
        }

        return text;
    }

    private static ChatColor toColor(String name) {
        if (name.equals("obfuscated"))
            return ChatColor.MAGIC;

        // Loop to avoid exceptions, we'll just return null if it can't be parsed
        for (ChatColor color : ChatColor.values()) {
            if (color == ChatColor.MAGIC) continue; // This isn't a valid value for color anyways

            if (color.name().equals(name.toUpperCase()))
                return color;
        }
        return null;
    }
}

@@ -1292,24 +1292,50 @@
  online.%22);%0A
+            return false;%0A
         %7D el

Failed command = return false