package com.patchworkmc;

import java.awt.Color;
import java.util.function.Supplier;

import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;

import org.apache.logging.log4j.Level;

public class ColorPane extends JTextPane {
	private static final Color D_Black = Color.getHSBColor(0.000f, 0.000f, 0.000f);
	private static final Color D_Red = Color.getHSBColor(0.000f, 1.000f, 0.502f);
	private static final Color D_Blue = Color.getHSBColor(0.667f, 1.000f, 0.502f);
	private static final Color D_Magenta = Color.getHSBColor(0.833f, 1.000f, 0.502f);
	private static final Color D_Green = Color.getHSBColor(0.333f, 1.000f, 0.502f);
	private static final Color D_Yellow = Color.getHSBColor(0.167f, 1.000f, 0.502f);
	private static final Color D_Cyan = Color.getHSBColor(0.500f, 1.000f, 0.502f);
	private static final Color D_White = Color.getHSBColor(0.000f, 0.000f, 0.753f);
	private static final Color B_Black = Color.getHSBColor(0.000f, 0.000f, 0.502f);
	private static final Color B_Red = Color.getHSBColor(0.000f, 1.000f, 1.000f);
	private static final Color B_Blue = Color.getHSBColor(0.667f, 1.000f, 1.000f);
	private static final Color B_Magenta = Color.getHSBColor(0.833f, 1.000f, 1.000f);
	private static final Color B_Green = Color.getHSBColor(0.333f, 1.000f, 1.000f);
	private static final Color B_Yellow = Color.getHSBColor(0.167f, 1.000f, 1.000f);
	private static final Color B_Cyan = Color.getHSBColor(0.500f, 1.000f, 1.000f);
	private static final Color B_White = Color.getHSBColor(0.000f, 0.000f, 1.000f);

	// TODO: instance-based and mutable when we have a dark mode
	private static final boolean IS_LIGHT = true;
	private static final Supplier<Color> cReset = () -> IS_LIGHT ? D_Black : D_White;

	// Cache to prevent looking up the color when it hasn't changed.
	private Color currentColor = D_Black;

	String remaining = "";

	private final Style oneStyleToRuleThemAll;

	public ColorPane() {
		this.oneStyleToRuleThemAll = this.addStyle("An interesting title.", null);

	public void append(Color color, String string) {
		if (color != null) {
			StyleConstants.setForeground(oneStyleToRuleThemAll, color);

		try {
			this.getDocument().insertString(this.getDocument().getLength(), string, oneStyleToRuleThemAll);
		} catch (BadLocationException e) {
			PatchworkUI.LOGGER.throwing(Level.ERROR, e);

	public void appendANSI(String s) {
		// convert ANSI color codes first
		int aPos = 0;   // current char position in addString
		int aIndex; // index of next Escape sequence
		int mIndex; // index of "m" terminating Escape sequence
		String tmpString = "";
		String addString = remaining + s;
		remaining = "";

		if (!addString.isEmpty()) {
			aIndex = addString.indexOf("\u001B"); // find first escape
			if (aIndex == -1) { // no escape/color change in this string, so just send it with current color
				append(currentColor, addString);

			// otherwise There is an escape character in the string, so we must process it

			if (aIndex > 0) { // Escape is not first char, so send text up to first escape
				tmpString = addString.substring(0, aIndex);
				append(currentColor, tmpString);
				aPos = aIndex;

			boolean stillSearching = true; // true until no more Escape sequences

			// aPos is now at the beginning of the first escape sequence
			while (stillSearching) {
				mIndex = addString.indexOf('m', aPos); // find the end of the escape sequence

				if (mIndex < 0) { // the buffer ends halfway through the ansi string!
					remaining = addString.substring(aPos);
					stillSearching = false;
				} else {
					tmpString = addString.substring(aPos, mIndex + 1);
					currentColor = getANSIColor(tmpString);

				aPos = mIndex + 1;
				// now we have the color, send text that is in that color (up to next escape)

				aIndex = addString.indexOf("\u001B", aPos);

				if (aIndex == -1) { // if that was the last sequence of the input, send remaining text
					tmpString = addString.substring(aPos);
					append(currentColor, tmpString);
					stillSearching = false;
					continue; // jump out of loop early, as the whole string has been sent now

				// there is another escape sequence, so send part of the string and prepare for the next
				tmpString = addString.substring(aPos, aIndex);
				aPos = aIndex;
				append(currentColor, tmpString);
			} // while there's text in the input buffer

	private Color getANSIColor(String ANSIColor) {
		switch (ANSIColor) {
		case "\u001B[30m":
		case "\u001B[0;30m":
			// If we're in dark mode, blacks need to be white.
			return !IS_LIGHT ? D_Black : D_White;
		case "\u001B[31m":
		case "\u001B[0;31m":
			return D_Red;
		case "\u001B[32m":
		case "\u001B[0;32m":
			return D_Green;
		case "\u001B[33m":
		case "\u001B[0;33m":
			return D_Yellow;
		case "\u001B[34m":
		case "\u001B[0;34m":
			return D_Blue;
		case "\u001B[35m":
		case "\u001B[0;35m":
			return D_Magenta;
		case "\u001B[36m":
		case "\u001B[0;36m":
			return D_Cyan;
		case "\u001B[37m":
		case "\u001B[0;37m":
			// If we're in light mode, whites need to be black
			return IS_LIGHT ? D_White : D_Black;
		case "\u001B[1;30m":
			// Etc...
			return !IS_LIGHT ? B_Black : B_White;
		case "\u001B[1;31m":
			return B_Red;
		case "\u001B[1;32m":
			return B_Green;
		case "\u001B[1;33m":
			return B_Yellow;
		case "\u001B[1;34m":
			return B_Blue;
		case "\u001B[1;35m":
			return B_Magenta;
		case "\u001B[1;36m":
			return B_Cyan;
		case "\u001B[1;37m":
			return !IS_LIGHT ? B_White : B_Black;
		case "\u001B[0m":
			return cReset.get();
			return IS_LIGHT ? B_Black : B_White;