using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ProxyCore
{
    public static class Colors
    {
        static Colors()
        {
            XTermToAnsiTable[0] = 'w';
            XTermToAnsiTable[1] = 'r';
            XTermToAnsiTable[2] = 'g';
            XTermToAnsiTable[3] = 'y';
            XTermToAnsiTable[4] = 'b';
            XTermToAnsiTable[5] = 'm';
            XTermToAnsiTable[6] = 'c';
            XTermToAnsiTable[7] = 'w';
            XTermToAnsiTable[8] = 'D';
            XTermToAnsiTable[9] = 'R';
            XTermToAnsiTable[10] = 'G';
            XTermToAnsiTable[11] = 'Y';
            XTermToAnsiTable[12] = 'B';
            XTermToAnsiTable[13] = 'M';
            XTermToAnsiTable[14] = 'C';
            XTermToAnsiTable[15] = 'W';
            XTermToAnsiTable[16] = 'w';
            XTermToAnsiTable[17] = 'b';
            XTermToAnsiTable[18] = 'b';
            XTermToAnsiTable[19] = 'b';
            XTermToAnsiTable[20] = 'B';
            XTermToAnsiTable[21] = 'B';
            XTermToAnsiTable[22] = 'g';
            XTermToAnsiTable[23] = 'c';
            XTermToAnsiTable[24] = 'c';
            XTermToAnsiTable[25] = 'c';
            XTermToAnsiTable[26] = 'c';
            XTermToAnsiTable[27] = 'B';
            XTermToAnsiTable[28] = 'g';
            XTermToAnsiTable[29] = 'c';
            XTermToAnsiTable[30] = 'c';
            XTermToAnsiTable[31] = 'c';
            XTermToAnsiTable[32] = 'c';
            XTermToAnsiTable[33] = 'C';
            XTermToAnsiTable[34] = 'g';
            XTermToAnsiTable[35] = 'c';
            XTermToAnsiTable[36] = 'c';
            XTermToAnsiTable[37] = 'c';
            XTermToAnsiTable[38] = 'C';
            XTermToAnsiTable[39] = 'C';
            XTermToAnsiTable[40] = 'G';
            XTermToAnsiTable[41] = 'c';
            XTermToAnsiTable[42] = 'c';
            XTermToAnsiTable[43] = 'C';
            XTermToAnsiTable[44] = 'C';
            XTermToAnsiTable[45] = 'C';
            XTermToAnsiTable[46] = 'G';
            XTermToAnsiTable[47] = 'G';
            XTermToAnsiTable[48] = 'C';
            XTermToAnsiTable[49] = 'C';
            XTermToAnsiTable[50] = 'C';
            XTermToAnsiTable[51] = 'C';
            XTermToAnsiTable[52] = 'r';
            XTermToAnsiTable[53] = 'm';
            XTermToAnsiTable[54] = 'm';
            XTermToAnsiTable[55] = 'm';
            XTermToAnsiTable[56] = 'm';
            XTermToAnsiTable[57] = 'B';
            XTermToAnsiTable[58] = 'y';
            XTermToAnsiTable[59] = 'D';
            XTermToAnsiTable[60] = 'D';
            XTermToAnsiTable[61] = 'D';
            XTermToAnsiTable[62] = 'D';
            XTermToAnsiTable[63] = 'B';
            XTermToAnsiTable[64] = 'y';
            XTermToAnsiTable[65] = 'D';
            XTermToAnsiTable[66] = 'D';
            XTermToAnsiTable[67] = 'D';
            XTermToAnsiTable[68] = 'D';
            XTermToAnsiTable[69] = 'w';
            XTermToAnsiTable[70] = 'y';
            XTermToAnsiTable[71] = 'D';
            XTermToAnsiTable[72] = 'D';
            XTermToAnsiTable[73] = 'D';
            XTermToAnsiTable[74] = 'w';
            XTermToAnsiTable[75] = 'w';
            XTermToAnsiTable[76] = 'y';
            XTermToAnsiTable[77] = 'D';
            XTermToAnsiTable[78] = 'D';
            XTermToAnsiTable[79] = 'w';
            XTermToAnsiTable[80] = 'w';
            XTermToAnsiTable[81] = 'C';
            XTermToAnsiTable[82] = 'G';
            XTermToAnsiTable[83] = 'G';
            XTermToAnsiTable[84] = 'w';
            XTermToAnsiTable[85] = 'w';
            XTermToAnsiTable[86] = 'C';
            XTermToAnsiTable[87] = 'C';
            XTermToAnsiTable[88] = 'r';
            XTermToAnsiTable[89] = 'm';
            XTermToAnsiTable[90] = 'm';
            XTermToAnsiTable[91] = 'm';
            XTermToAnsiTable[92] = 'm';
            XTermToAnsiTable[93] = 'M';
            XTermToAnsiTable[94] = 'y';
            XTermToAnsiTable[95] = 'D';
            XTermToAnsiTable[96] = 'D';
            XTermToAnsiTable[97] = 'D';
            XTermToAnsiTable[98] = 'D';
            XTermToAnsiTable[99] = 'w';
            XTermToAnsiTable[100] = 'y';
            XTermToAnsiTable[101] = 'D';
            XTermToAnsiTable[102] = 'D';
            XTermToAnsiTable[103] = 'D';
            XTermToAnsiTable[104] = 'w';
            XTermToAnsiTable[105] = 'w';
            XTermToAnsiTable[106] = 'y';
            XTermToAnsiTable[107] = 'D';
            XTermToAnsiTable[108] = 'D';
            XTermToAnsiTable[109] = 'w';
            XTermToAnsiTable[110] = 'w';
            XTermToAnsiTable[111] = 'w';
            XTermToAnsiTable[112] = 'y';
            XTermToAnsiTable[113] = 'D';
            XTermToAnsiTable[114] = 'w';
            XTermToAnsiTable[115] = 'w';
            XTermToAnsiTable[116] = 'w';
            XTermToAnsiTable[117] = 'w';
            XTermToAnsiTable[118] = 'Y';
            XTermToAnsiTable[119] = 'w';
            XTermToAnsiTable[120] = 'w';
            XTermToAnsiTable[121] = 'w';
            XTermToAnsiTable[122] = 'w';
            XTermToAnsiTable[123] = 'w';
            XTermToAnsiTable[124] = 'r';
            XTermToAnsiTable[125] = 'm';
            XTermToAnsiTable[126] = 'm';
            XTermToAnsiTable[127] = 'm';
            XTermToAnsiTable[128] = 'M';
            XTermToAnsiTable[129] = 'M';
            XTermToAnsiTable[130] = 'y';
            XTermToAnsiTable[131] = 'D';
            XTermToAnsiTable[132] = 'D';
            XTermToAnsiTable[133] = 'D';
            XTermToAnsiTable[134] = 'w';
            XTermToAnsiTable[135] = 'w';
            XTermToAnsiTable[136] = 'y';
            XTermToAnsiTable[137] = 'D';
            XTermToAnsiTable[138] = 'D';
            XTermToAnsiTable[139] = 'w';
            XTermToAnsiTable[140] = 'w';
            XTermToAnsiTable[141] = 'w';
            XTermToAnsiTable[142] = 'y';
            XTermToAnsiTable[143] = 'D';
            XTermToAnsiTable[144] = 'w';
            XTermToAnsiTable[145] = 'w';
            XTermToAnsiTable[146] = 'w';
            XTermToAnsiTable[147] = 'w';
            XTermToAnsiTable[148] = 'Y';
            XTermToAnsiTable[149] = 'w';
            XTermToAnsiTable[150] = 'w';
            XTermToAnsiTable[151] = 'w';
            XTermToAnsiTable[152] = 'w';
            XTermToAnsiTable[153] = 'w';
            XTermToAnsiTable[154] = 'Y';
            XTermToAnsiTable[155] = 'w';
            XTermToAnsiTable[156] = 'w';
            XTermToAnsiTable[157] = 'w';
            XTermToAnsiTable[158] = 'w';
            XTermToAnsiTable[159] = 'W';
            XTermToAnsiTable[160] = 'R';
            XTermToAnsiTable[161] = 'm';
            XTermToAnsiTable[162] = 'm';
            XTermToAnsiTable[163] = 'M';
            XTermToAnsiTable[164] = 'M';
            XTermToAnsiTable[165] = 'M';
            XTermToAnsiTable[166] = 'y';
            XTermToAnsiTable[167] = 'D';
            XTermToAnsiTable[168] = 'D';
            XTermToAnsiTable[169] = 'w';
            XTermToAnsiTable[170] = 'w';
            XTermToAnsiTable[171] = 'M';
            XTermToAnsiTable[172] = 'y';
            XTermToAnsiTable[173] = 'D';
            XTermToAnsiTable[174] = 'w';
            XTermToAnsiTable[175] = 'w';
            XTermToAnsiTable[176] = 'w';
            XTermToAnsiTable[177] = 'w';
            XTermToAnsiTable[178] = 'Y';
            XTermToAnsiTable[179] = 'w';
            XTermToAnsiTable[180] = 'w';
            XTermToAnsiTable[181] = 'w';
            XTermToAnsiTable[182] = 'w';
            XTermToAnsiTable[183] = 'w';
            XTermToAnsiTable[184] = 'Y';
            XTermToAnsiTable[185] = 'w';
            XTermToAnsiTable[186] = 'w';
            XTermToAnsiTable[187] = 'w';
            XTermToAnsiTable[188] = 'w';
            XTermToAnsiTable[189] = 'W';
            XTermToAnsiTable[190] = 'Y';
            XTermToAnsiTable[191] = 'Y';
            XTermToAnsiTable[192] = 'w';
            XTermToAnsiTable[193] = 'w';
            XTermToAnsiTable[194] = 'W';
            XTermToAnsiTable[195] = 'W';
            XTermToAnsiTable[196] = 'R';
            XTermToAnsiTable[197] = 'R';
            XTermToAnsiTable[198] = 'M';
            XTermToAnsiTable[199] = 'M';
            XTermToAnsiTable[200] = 'M';
            XTermToAnsiTable[201] = 'M';
            XTermToAnsiTable[202] = 'R';
            XTermToAnsiTable[203] = 'R';
            XTermToAnsiTable[204] = 'w';
            XTermToAnsiTable[205] = 'w';
            XTermToAnsiTable[206] = 'M';
            XTermToAnsiTable[207] = 'M';
            XTermToAnsiTable[208] = 'Y';
            XTermToAnsiTable[209] = 'w';
            XTermToAnsiTable[210] = 'w';
            XTermToAnsiTable[211] = 'w';
            XTermToAnsiTable[212] = 'w';
            XTermToAnsiTable[213] = 'w';
            XTermToAnsiTable[214] = 'Y';
            XTermToAnsiTable[215] = 'w';
            XTermToAnsiTable[216] = 'w';
            XTermToAnsiTable[217] = 'w';
            XTermToAnsiTable[218] = 'w';
            XTermToAnsiTable[219] = 'W';
            XTermToAnsiTable[220] = 'Y';
            XTermToAnsiTable[221] = 'Y';
            XTermToAnsiTable[222] = 'w';
            XTermToAnsiTable[223] = 'w';
            XTermToAnsiTable[224] = 'W';
            XTermToAnsiTable[225] = 'W';
            XTermToAnsiTable[226] = 'Y';
            XTermToAnsiTable[227] = 'Y';
            XTermToAnsiTable[228] = 'w';
            XTermToAnsiTable[229] = 'W';
            XTermToAnsiTable[230] = 'W';
            XTermToAnsiTable[231] = 'W';
            XTermToAnsiTable[232] = 'w';
            XTermToAnsiTable[233] = 'w';
            XTermToAnsiTable[234] = 'w';
            XTermToAnsiTable[235] = 'w';
            XTermToAnsiTable[236] = 'w';
            XTermToAnsiTable[237] = 'w';
            XTermToAnsiTable[238] = 'D';
            XTermToAnsiTable[239] = 'D';
            XTermToAnsiTable[240] = 'D';
            XTermToAnsiTable[241] = 'D';
            XTermToAnsiTable[242] = 'D';
            XTermToAnsiTable[243] = 'D';
            XTermToAnsiTable[244] = 'D';
            XTermToAnsiTable[245] = 'D';
            XTermToAnsiTable[246] = 'D';
            XTermToAnsiTable[247] = 'D';
            XTermToAnsiTable[248] = 'w';
            XTermToAnsiTable[249] = 'w';
            XTermToAnsiTable[250] = 'w';
            XTermToAnsiTable[251] = 'w';
            XTermToAnsiTable[252] = 'w';
            XTermToAnsiTable[253] = 'w';
            XTermToAnsiTable[254] = 'W';
            XTermToAnsiTable[255] = 'W';
        }

        /// <summary>
        /// Check if a char code is ANSI color code.
        /// </summary>
        /// <param name="code">Char to check for.</param>
        /// <returns></returns>
        public static bool IsColorCode(char code)
        {
            // Don't allow black color, we can't see it
            if(code == 'd')
                return false;

            foreach(ColorEntry x in ColorEntries)
            {
                if(x.ColorChar == code)
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Convert normal colors to HTML colors. It converts inserting &lt;/font&gt; in front of every color too
        /// so you need to have started with some font already.
        /// </summary>
        /// <param name="Msg">Message to convert colors in.</param>
        /// <returns></returns>
        public static string GetHTMLColors(string Msg)
        {
            Msg = Msg.Replace("@@", colorEscape);
            foreach(ColorEntry c in ColorEntries)
                Msg = Msg.Replace("@" + c.ColorChar, "</font><font color=\"#" + c.HEX + "\">");
            Msg = Msg.Replace(colorEscape, "@");
            return Msg;
        }

        // Any codes that aren't xterm and aren't listed here will be @e instead of escape char.
        private static ColorEntry[] ColorEntries = new[]
        {
            new ColorEntry('w', "[0;37m", "[0;47m", "grey", "C0C0C0"),
            new ColorEntry('W', "[1;37m", "[1;47m", "white", "FFFFFF"),
            new ColorEntry('G', "[1;32m", "[1;42m", "green", "00FF00"),
            new ColorEntry('g', "[0;32m", "[0;42m", "dark green", "008000"),
            new ColorEntry('R', "[1;31m", "[1;41m", "red", "FF0000"),
            new ColorEntry('r', "[0;31m", "[0;41m", "dark red", "800000"),
            new ColorEntry('Y', "[1;33m", "[1;43m", "yellow", "FFFF00"),
            new ColorEntry('y', "[0;33m", "[0;43m", "dark yellow", "808000"),
            new ColorEntry('C', "[1;36m", "[1;46m", "cyan", "00FFFF"),
            new ColorEntry('c', "[0;36m", "[0;46m", "dark cyan", "008080"),
            new ColorEntry('B', "[1;34m", "[1;44m", "blue", "0000FF"),
            new ColorEntry('b', "[0;34m", "[0;44m", "dark blue", "000080"),
            new ColorEntry('M', "[1;35m", "[1;45m", "magenta", "FF00FF"),
            new ColorEntry('m', "[0;35m", "[0;45m", "dark magenta", "800080"),
            new ColorEntry('D', "[1;30m", "[1;40m", "dark grey", "808080"),
            new ColorEntry('d', "[0;30m", "[0;40m", "black", "000000"),
            new ColorEntry('q', "[0m", "[999;999m", "default", "C0C0C0")
        };

        /// <summary>
        /// Get last color code in the string (not including foreground colors). This will return
        /// with the @ sign.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static string GetLastColorCode(string text)
        {
            text = text.Replace("@@", colorEscape);

            // Start checking from the end
            for(int i = text.Length - 1; i >= 0; i--)
            {
                if(text[i] == '@' && i + 1 < text.Length && text[i + 1] != 'f' && text[i + 1] != 'y')
                {
                    if(text[i + 1] == 'x')
                    {
                        int len;
                        string code = GetXTerm(text, i, out len).ToString();
                        if(len == 0)
                            continue;

                        if(code.Length == 1)
                            code = "00" + code;
                        else if(code.Length == 2)
                            code = "0" + code;

                        return "@x" + code;
                    }
                    else
                        return "@" + text[i + 1];
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// Removes duplicate colors from a string. For example "@r @Y bla@w" would become "  @Ybla".
        /// Also XTERM color codes become padded with zero if they are present.
        /// </summary>
        /// <param name="orig">String to fix colors in.</param>
        /// <returns></returns>
        public static string RemoveDuplicateColors(string orig)
        {
            StringBuilder txt = new StringBuilder();

            string currentColor = "";
            bool didWriteColor = false;
            bool hasAt = false;
            bool hasE = false;

            for(int i = 0; i < orig.Length; i++)
            {
                switch(orig[i])
                {
                    case '@':
                        {
                            if(hasAt)
                            {
                                if(!didWriteColor)
                                {
                                    txt.Append(currentColor);
                                    didWriteColor = true;
                                }

                                txt.Append("@@");
                                hasAt = false;
                            }
                            else
                            {
                                hasAt = true;
                            }
                        } break;

                    case ' ':
                    case '\r':
                    case '\n':
                    case '\t':
                        txt.Append(orig[i]);
                        break;

                    default:
                        {
                            if(hasAt)
                            {
                                if(orig[i] == 'e')
                                {
                                    txt.Append("@e");
                                    hasAt = false;
                                    hasE = true;
                                    continue;
                                }
                                if(orig[i] == 'f')
                                {
                                    if(i + 1 < orig.Length)
                                        txt.Append("@f" + orig[i + 1]);
                                    i++;
                                    hasAt = false;
                                    continue;
                                }
                                if(orig[i] == 'z')
                                {
                                    int len;
                                    string code = GetXTerm(orig, i - 1, out len).ToString();
                                    if(len > 0)
                                    {
                                        if(code.Length == 1)
                                            code = "00" + code;
                                        else if(code.Length == 2)
                                            code = "0" + code;
                                        txt.Append("@z" + code);
                                        i += len;
                                    }
                                    hasAt = false;
                                    continue;
                                }
                                if(orig[i] == 'x')
                                {
                                    int len;
                                    string code = GetXTerm(orig, i - 1, out len).ToString();
                                    if(len > 0)
                                    {
                                        if(code.Length == 1)
                                            code = "00" + code;
                                        else if(code.Length == 2)
                                            code = "0" + code;
                                        i += len;
                                        if(currentColor != "@x" + code)
                                        {
                                            currentColor = "@x" + code;
                                            didWriteColor = false;
                                        }
                                    }
                                    hasAt = false;
                                    continue;
                                }
                                if(currentColor != "@" + orig[i])
                                {
                                    currentColor = "@" + orig[i];
                                    didWriteColor = false;
                                }
                                hasAt = false;
                            }
                            else if(hasE)
                            {
                                txt.Append(orig[i]);
                                if(orig[i] == 'm')
                                    hasE = false;
                            }
                            else
                            {
                                if(!didWriteColor)
                                {
                                    txt.Append(currentColor);
                                    didWriteColor = true;
                                }

                                txt.Append(orig[i]);
                            }
                        } break;
                }
            }

            return txt.ToString();
        }

        /// <summary>
        /// This is what we turn the @ into so we can replace colors and keep the symbol intact
        /// </summary>
        private const string colorEscape = "#\r\ncolor_escape_sequence\r\n#";

        /// <summary>
        /// This function will turn either ANSI color to our format or vice versa.
        /// </summary>
        /// <param name="text">String passed for color changing.</param>
        /// <param name="fromRaw">False means we change our format (@x) to ANSI; true means we change ANSI to our format (we don't convert XTERM colors this way though).</param>
        /// <param name="allowXTerm">Allow replacing xterm colors? If disabled we will replace into closest ANSI.</param>
        /// <returns></returns>
        public static string FixColors(string text, bool fromRaw, bool allowXTerm)
        {
            if(fromRaw == false)
            {
                text = text.Replace("@@", colorEscape);
                text = ReplaceXTerm(text, fromRaw, allowXTerm);
                for(int i = 0; i < ColorEntries.Length; i++)
                {
                    text = text.Replace("@" + ColorEntries[i].ColorChar, ColorEntries[i].ANSI);
                    text = text.Replace("@f" + ColorEntries[i].ColorChar, ColorEntries[i].ForeANSI);
                }
                text = text.Replace("@e", "");
                text = text.Replace(colorEscape, "@");
            }
            else
            {
                text = text.Replace("@", colorEscape);
                text = ReplaceXTerm(text, fromRaw, allowXTerm);
                for(int i = 0; i < ColorEntries.Length; i++)
                {
                    text = text.Replace(ColorEntries[i].ANSI, "@" + ColorEntries[i].ColorChar);
                    text = text.Replace(ColorEntries[i].ForeANSI, "@f" + ColorEntries[i].ColorChar);
                }
                text = text.Replace("", "@e");
                text = text.Replace(colorEscape, "@@");
            }
            return text;
        }

        /// <summary>
        /// Remove dark grey color code from string and replace it with normal grey.
        /// </summary>
        /// <param name="text">String to remove dark grey from (replacing it with normal grey).</param>
        /// <returns></returns>
        public static string RemoveGray(string text)
        {
            text = text.Replace("@@", colorEscape);
            text = text.Replace("@D", "@w");
            text = text.Replace("@fD", "@fw");
            text = text.Replace(colorEscape, "@@");
            return text;
        }

        /// <summary>
        /// This function will Remove all color from the string.
        /// </summary>
        /// <param name="text">String where the color is being removed.</param>
        /// <param name="raw">True means raw color (ansi); false means our format (@x).</param>
        /// <returns></returns>
        public static string RemoveColors(string text, bool raw)
        {
            if(!raw)
            {
                text = text.Replace("@@", colorEscape);
                for(int i = 0; i < ColorEntries.Length; i++)
                {
                    text = text.Replace("@" + ColorEntries[i].ColorChar, "");
                    text = text.Replace("@f" + ColorEntries[i].ColorChar, "");
                }
                int j;
                while((j = text.IndexOf("@x")) != -1 || (j = text.IndexOf("@z")) != -1)
                    text = text.Remove(j, 5);
                text = text.Replace(colorEscape, "@");
                return text;
            }

            for(int i = 0; i < ColorEntries.Length; i++)
            {
                text = text.Replace(ColorEntries[i].ANSI, "");
                text = text.Replace(ColorEntries[i].ForeANSI, "");
            }

            int k;
            while((k = text.IndexOf("")) != -1)
            {
                int l = text.IndexOf('m', k);
                if(l == -1)
                    break;
                text = text.Remove(k, l - k);
            }
            return text;
        }

        /// <summary>
        /// Get XTerm color code from text in index (index must be at @x or @z, before it).
        /// </summary>
        /// <param name="text">Text to get color code from.</param>
        /// <param name="index">Index of @z or @x.</param>
        /// <returns></returns>
        public static byte GetXTerm(string text, int index, out int xlen)
        {
            // Skip @x or @z
            index += 2;

            // Get byte number now
            byte v = GetXTermCode(text, index, out xlen);
            return v;
        }

        /// <summary>
        /// Get full escape string from string in index.
        /// </summary>
        /// <param name="text">Text to search from.</param>
        /// <param name="index">Index in text of @e</param>
        /// <param name="xlen">Length of the thing behind @e.</param>
        /// <returns></returns>
        public static string GetEscape(string text, int index, out int xlen)
        {
            index += 2;
            if((xlen = text.IndexOf('m', index)) == -1)
            {
                xlen = 0;
                return "@e[0m";
            }

            xlen++; // include the 'm'
            xlen -= index;
            return "@e" + text.Substring(index, xlen);
        }

        /// <summary>
        /// Get XTerm color code from text in index (index must be at the number not before @x or @z like the other function)
        /// </summary>
        /// <param name="text">Text to get color code from.</param>
        /// <param name="index">Index of number.</param>
        /// <param name="xlen">Length of the number.</param>
        /// <returns></returns>
        public static byte GetXTermCode(string text, int index, out int xlen)
        {
            int len = 0;
            while(len < 3 && index + len < text.Length && char.IsNumber(text[index + len]))
                len++;

            byte v = 0;
            while(len > 0 && !byte.TryParse(text.Substring(index, len), out v))
                len--;

            if(len == 0)
                v = 0;
            xlen = len;
            return v;
        }

        /// <summary>
        /// Replace XTerm color codes into real XTerm or ANSI values.
        /// </summary>
        /// <param name="text">Text to search in.</param>
        /// <param name="allowXTerm">Replace into XTerm codes or if false then convert to closest regular ANSI.</param>
        /// <returns></returns>
        public static string ReplaceXTerm(string text, bool fromRaw, bool allowXTerm)
        {
            if(fromRaw == false)
            {
                int index = -1;
                while((index = text.IndexOf("@x")) != -1)
                {
                    int len;
                    byte code = GetXTerm(text, index, out len);
                    text = text.Remove(index, len + 2);
                    if(allowXTerm)
                        text = text.Insert(index, "[38;5;" + code.ToString() + "m");
                    else
                        text = text.Insert(index, "@" + XTermToANSI(code));
                }
                while((index = text.IndexOf("@z")) != -1)
                {
                    int len;
                    byte code = GetXTerm(text, index, out len);
                    text = text.Remove(index, len + 2);
                    if(allowXTerm)
                        text = text.Insert(index, "[48;5;" + code.ToString() + "m");
                    else
                        text = text.Insert(index, "@f" + XTermToANSI(code));
                }
            }
            else
            {
                int index = -1;
                while((index = text.IndexOf("[38;5;")) != -1)
                {
                    int len;
                    byte c = GetXTermCode(text, index + 7, out len);
                    string code = c.ToString();
                    if(code.Length == 1)
                        code = "00" + code;
                    else if(code.Length == 2)
                        code = "0" + code;
                    text = text.Remove(index, len + 8);
                    if(allowXTerm)
                        text = text.Insert(index, "@x" + code);
                    else
                        text = text.Insert(index, "@" + XTermToANSI(c));
                }
                while((index = text.IndexOf("[48;5;")) != -1)
                {
                    int len;
                    byte c = GetXTermCode(text, index + 7, out len);
                    string code = c.ToString();
                    if(code.Length == 1)
                        code = "00" + code;
                    else if(code.Length == 2)
                        code = "0" + code;
                    text = text.Remove(index, len + 8);
                    if(allowXTerm)
                        text = text.Insert(index, "@z" + code);
                    else
                        text = text.Insert(index, "@" + XTermToANSI(c));
                }
            }
            return text;
        }

        private static readonly char[] XTermToAnsiTable = new char[256];

        /// <summary>
        /// Convert XTerm to ansi char (closest match).
        /// </summary>
        /// <param name="code">Code to convert.</param>
        /// <returns></returns>
        public static char XTermToANSI(byte code)
        {
            return XTermToAnsiTable[(int)code];
        }
    }

    internal class ColorEntry
    {
        internal ColorEntry(char colorChar, string ansi, string foreansi, string name, string hex)
        {
            ColorChar = colorChar;
            ANSI = ansi;
            ForeANSI = foreansi;
            Name = name;
            HEX = hex;
        }

        public readonly char ColorChar;
        public readonly string ANSI;
        public readonly string ForeANSI;
        public readonly string Name;
        public readonly string HEX;
    }
}
