package net.glowstone.util;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import lombok.EqualsAndHashCode;
import org.bukkit.ChatColor;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 * Simple container for chat message structures until more advanced chat formatting is implemented.
 */
@EqualsAndHashCode
public final class TextMessage {

    /**
     * The formatting ChatColors.
     */
    private static final ChatColor[] FORMATTING = {ChatColor.MAGIC, ChatColor.BOLD, ChatColor.STRIKETHROUGH, ChatColor.UNDERLINE, ChatColor.ITALIC};

    /**
     * The JSON structure of this text message.
     */
    private final JSONObject object;

    /**
     * Construct a new chat message from a simple text string. Handles style and colors in the original string, converting them to the new format.
     *
     * @param text The text of the message.
     */
    public TextMessage(String text) {
        object = convert(text);
    }

    /**
     * Construct a chat message from a JSON structure. No validation occurs.
     *
     * @param object The JSON structure of the message.
     */
    public TextMessage(JSONObject object) {
        checkNotNull(object, "object must not be null");
        this.object = object;
    }

    private static void flatten(StringBuilder dest, JSONObject obj) {
        if (obj.containsKey("color")) {
            try {
                dest.append(ChatColor.valueOf(obj.get("color").toString().toUpperCase()));
            } catch (IllegalArgumentException ex) {
                // invalid color, ignore
            }
        }
        for (ChatColor format : FORMATTING) {
            String name = format == ChatColor.MAGIC ? "obfuscated" : format.name().toLowerCase();
            if (obj.containsKey(name) && obj.get(name).equals(true)) {
                dest.append(format);
            }
        }
        if (obj.containsKey("text")) {
            dest.append(obj.get("text"));
        }
        if (obj.containsKey("extra")) {
            LinkedList<?> array = (LinkedList<?>) obj.get("extra");
            for (Object o : array) {
                if (o instanceof JSONObject) {
                    flatten(dest, (JSONObject) o);
                } else {
                    dest.append(o);
                }
            }
        }
    }

    /**
     * Flatten this message to an approximate old-style string representation.
     *
     * @return The best old-style string representation for this message.
     */
    public String flatten() {
        StringBuilder builder = new StringBuilder();
        flatten(builder, object);
        return builder.toString();
    }

    /**
     * Decode a chat message from its textual JSON representation if possible.
     *
     * @param json The encoded representation.
     * @return The decoded TextMessage.
     */
    public static TextMessage decode(String json) {
        JSONParser parser = new JSONParser();
        try {
            Object o = parser.parse(json);
            if (o instanceof JSONObject) {
                return new TextMessage((JSONObject) o);
            } else {
                return new TextMessage(o.toString());
            }
        } catch (ParseException e) {
            return new TextMessage(json);
        }
    }

    /**
     * Convert from an old-style to a new-style chat message.
     *
     * @param text The The text of the message.
     * @return The converted JSON structure.
     */
    @SuppressWarnings("unchecked")
    private static JSONObject convert(String text) {
        // state
        List<JSONObject> items = new LinkedList<>();
        Set<ChatColor> formatting = EnumSet.noneOf(ChatColor.class);
        StringBuilder current = new StringBuilder();
        ChatColor color = null;

        // work way through text, converting colors
        for (int i = 0; i < text.length(); ++i) {
            char ch = text.charAt(i);
            if (ch != ChatColor.COLOR_CHAR) {
                // no special handling
                current.append(ch);
                continue;
            }

            if (i == text.length() - 1) {
                // ignore color character at end
                continue;
            }

            // handle colors
            append(items, current, color, formatting);
            ChatColor code = ChatColor.getByChar(text.charAt(++i));
            if (code == ChatColor.RESET) {
                color = null;
                formatting.clear();
            } else if (code.isFormat()) {
                formatting.add(code);
            } else {
                color = code;
                formatting.clear();
            }
        }
        append(items, current, color, formatting);

        // convert list of items into structure
        if (items.isEmpty()) {
            // no items, return a blank message
            JSONObject object = new JSONObject();
            object.put("text", "");
            return object;
        } else if (items.size() == 1) {
            // only one item, return it as-is
            return items.get(0);
        } else {
            JSONObject object = items.get(0);
            if (object.size() == 1) {
                // only contains "text", no formatting, can reuse
                object.put("extra", items.subList(1, items.size()));
            } else {
                // must put everything in the "extra" list
                object = new JSONObject();
                object.put("text", "");
                object.put("extra", items);
            }
            return object;
        }
    }

    @SuppressWarnings("unchecked")
    private static void append(List<JSONObject> items, StringBuilder current, ChatColor color, Set<ChatColor> formatting) {
        if (current.length() == 0) {
            return;
        }

        JSONObject object = new JSONObject();
        object.put("text", current.toString());
        if (color != null) {
            object.put("color", color.name().toLowerCase());
        }
        for (ChatColor format : formatting) {
            if (format == ChatColor.MAGIC) {
                object.put("obfuscated", true);
            } else {
                object.put(format.name().toLowerCase(), true);
            }
        }
        current.setLength(0);
        items.add(object);
    }

    /**
     * Encode this chat message to its textual JSON representation.
     *
     * @return The encoded representation.
     */
    public String encode() {
        return object.toJSONString();
    }

    /**
     * Attempt to convert the message to its plaintext representation.
     *
     * @return The plain text, or the empty string on failure.
     */
    public String asPlaintext() {
        if (object.containsKey("text")) {
            Object obj = object.get("text");
            if (obj instanceof String) {
                return (String) obj;
            }
        }
        return "";
    }

    @Override
    public String toString() {
        return "Message" + encode();
    }

}

@@ -668,16 +668,24 @@
 or.BOLD,
+%0A       
  ChatCol
@@ -947,16 +947,23 @@
 s in the
+%0A     *
  origina
@@ -5874,16 +5874,28 @@
 r color,
+%0A           
  Set%3CCha

Fix CheckStyle issues in util/TextMessage.java (#746)