/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "color_scheme.h"

#include <curses.h>
#include <regex.h> /* regexec() */

#include <assert.h> /* assert() */
#include <limits.h> /* INT_MAX */
#include <math.h> /* abs() */
#include <stddef.h> /* NULL size_t */
#include <stdio.h> /* snprintf() */
#include <stdlib.h> /* free() */
#include <string.h> /* memcpy() memset() strcpy() strlen() */

#include "../cfg/config.h"
#include "../compat/dtype.h"
#include "../compat/fs_limits.h"
#include "../compat/os.h"
#include "../compat/reallocarray.h"
#include "../engine/completion.h"
#include "../modes/dialogs/msg_dialog.h"
#include "../utils/fs.h"
#include "../utils/fsddata.h"
#include "../utils/macros.h"
#include "../utils/matchers.h"
#include "../utils/str.h"
#include "../utils/string_array.h"
#include "../utils/utils.h"
#include "../status.h"
#include "color_manager.h"
#include "statusbar.h"
#include "ui.h"

char *HI_GROUPS[] = {
	[WIN_COLOR]          = "Win",
	[DIRECTORY_COLOR]    = "Directory",
	[LINK_COLOR]         = "Link",
	[BROKEN_LINK_COLOR]  = "BrokenLink",
	[HARD_LINK_COLOR]    = "HardLink",
	[SOCKET_COLOR]       = "Socket",
	[DEVICE_COLOR]       = "Device",
	[FIFO_COLOR]         = "Fifo",
	[EXECUTABLE_COLOR]   = "Executable",
	[SELECTED_COLOR]     = "Selected",
	[CURR_LINE_COLOR]    = "CurrLine",
	[TOP_LINE_COLOR]     = "TopLine",
	[TOP_LINE_SEL_COLOR] = "TopLineSel",
	[STATUS_LINE_COLOR]  = "StatusLine",
	[WILD_BOX_COLOR]     = "WildBox",
	[WILD_MENU_COLOR]    = "WildMenu",
	[CMD_LINE_COLOR]     = "CmdLine",
	[ERROR_MSG_COLOR]    = "ErrorMsg",
	[BORDER_COLOR]       = "Border",
	[OTHER_LINE_COLOR]   = "OtherLine",
	[JOB_LINE_COLOR]     = "JobLine",
	[SUGGEST_BOX_COLOR]  = "SuggestBox",
	[MISMATCH_COLOR]     = "CmpMismatch",
	[UNMATCHED_COLOR]    = "CmpUnmatched",
	[BLANK_COLOR]        = "CmpBlank",
	[AUX_WIN_COLOR]      = "AuxWin",
	[TAB_LINE_COLOR]     = "TabLine",
	[TAB_LINE_SEL_COLOR] = "TabLineSel",
	[USER1_COLOR]        = "User1",
	[USER2_COLOR]        = "User2",
	[USER3_COLOR]        = "User3",
	[USER4_COLOR]        = "User4",
	[USER5_COLOR]        = "User5",
	[USER6_COLOR]        = "User6",
	[USER7_COLOR]        = "User7",
	[USER8_COLOR]        = "User8",
	[USER9_COLOR]        = "User9",
	[USER10_COLOR]       = "User10",
	[USER11_COLOR]       = "User11",
	[USER12_COLOR]       = "User12",
	[USER13_COLOR]       = "User13",
	[USER14_COLOR]       = "User14",
	[USER15_COLOR]       = "User15",
	[USER16_COLOR]       = "User16",
	[USER17_COLOR]       = "User17",
	[USER18_COLOR]       = "User18",
	[USER19_COLOR]       = "User19",
	[USER20_COLOR]       = "User20",
	[OTHER_WIN_COLOR]    = "OtherWin",
	[LINE_NUM_COLOR]     = "LineNr",
	[ODD_LINE_COLOR]     = "OddLine",
};
ARRAY_GUARD(HI_GROUPS, MAXNUM_COLOR);

const char *HI_GROUPS_DESCR[] = {
	[WIN_COLOR]          = "base highlight of every window",
	[DIRECTORY_COLOR]    = "directories and links to directories",
	[LINK_COLOR]         = "symbolic links",
	[BROKEN_LINK_COLOR]  = "dangling symbolic links",
	[HARD_LINK_COLOR]    = "regular files with more than one hard link",
	[SOCKET_COLOR]       = "sockets",
	[DEVICE_COLOR]       = "device file",
	[FIFO_COLOR]         = "pipe",
	[EXECUTABLE_COLOR]   = "executable file",
	[SELECTED_COLOR]     = "selected item",
	[CURR_LINE_COLOR]    = "cursor in active pane",
	[TOP_LINE_COLOR]     = "base top line highlight",
	[TOP_LINE_SEL_COLOR] = "active window title",
	[STATUS_LINE_COLOR]  = "status line",
	[WILD_BOX_COLOR]     = "completion window",
	[WILD_MENU_COLOR]    = "current completion item",
	[CMD_LINE_COLOR]     = "status/command-line bar",
	[ERROR_MSG_COLOR]    = "error message",
	[BORDER_COLOR]       = "horizontal borders",
	[OTHER_LINE_COLOR]   = "cursor in inactive pane",
	[JOB_LINE_COLOR]     = "job bar",
	[SUGGEST_BOX_COLOR]  = "suggestion box",
	[MISMATCH_COLOR]     = "mismatched diff entries",
	[UNMATCHED_COLOR]    = "diff entry with no match in the other pane",
	[BLANK_COLOR]        = "entry placeholder in a diff",
	[AUX_WIN_COLOR]      = "auxiliary part of window",
	[TAB_LINE_COLOR]     = "tab line",
	[TAB_LINE_SEL_COLOR] = "tip of selected tab",
	[USER1_COLOR]        = "user color #1",
	[USER2_COLOR]        = "user color #2",
	[USER3_COLOR]        = "user color #3",
	[USER4_COLOR]        = "user color #4",
	[USER5_COLOR]        = "user color #5",
	[USER6_COLOR]        = "user color #6",
	[USER7_COLOR]        = "user color #7",
	[USER8_COLOR]        = "user color #8",
	[USER9_COLOR]        = "user color #9",
	[USER10_COLOR]       = "user color #10",
	[USER11_COLOR]       = "user color #11",
	[USER12_COLOR]       = "user color #12",
	[USER13_COLOR]       = "user color #13",
	[USER14_COLOR]       = "user color #14",
	[USER15_COLOR]       = "user color #15",
	[USER16_COLOR]       = "user color #16",
	[USER17_COLOR]       = "user color #17",
	[USER18_COLOR]       = "user color #18",
	[USER19_COLOR]       = "user color #19",
	[USER20_COLOR]       = "user color #20",
	[OTHER_WIN_COLOR]    = "additional highlighting of inactive pane",
	[LINE_NUM_COLOR]     = "color of line number column in panes",
	[ODD_LINE_COLOR]     = "color of every second entry line in a pane",
};
ARRAY_GUARD(HI_GROUPS_DESCR, ARRAY_LEN(HI_GROUPS));

char *LIGHT_COLOR_NAMES[8] = {
	[COLOR_BLACK]   = "lightblack",
	[COLOR_RED]     = "lightred",
	[COLOR_GREEN]   = "lightgreen",
	[COLOR_YELLOW]  = "lightyellow",
	[COLOR_BLUE]    = "lightblue",
	[COLOR_MAGENTA] = "lightmagenta",
	[COLOR_CYAN]    = "lightcyan",
	[COLOR_WHITE]   = "lightwhite",
};

char *XTERM256_COLOR_NAMES[256] = {
	[COLOR_BLACK]   = "black",
	[COLOR_RED]     = "red",
	[COLOR_GREEN]   = "green",
	[COLOR_YELLOW]  = "yellow",
	[COLOR_BLUE]    = "blue",
	[COLOR_MAGENTA] = "magenta",
	[COLOR_CYAN]    = "cyan",
	[COLOR_WHITE]   = "white",
	[8]  = "lightblack",
	[9]  = "lightred",
	[10] = "lightgreen",
	[11] = "lightyellow",
	[12] = "lightblue",
	[13] = "lightmagenta",
	[14] = "lightcyan",
	[15] = "lightwhite",
	[16] = "Grey0",
	[17] = "NavyBlue",
	[18] = "DarkBlue",
	[19] = "Blue3",
	[20] = "Blue3_2",
	[21] = "Blue1",
	[22] = "DarkGreen",
	[23] = "DeepSkyBlue4",
	[24] = "DeepSkyBlue4_2",
	[25] = "DeepSkyBlue4_3",
	[26] = "DodgerBlue3",
	[27] = "DodgerBlue2",
	[28] = "Green4",
	[29] = "SpringGreen4",
	[30] = "Turquoise4",
	[31] = "DeepSkyBlue3",
	[32] = "DeepSkyBlue3_2",
	[33] = "DodgerBlue1",
	[34] = "Green3",
	[35] = "SpringGreen3",
	[36] = "DarkCyan",
	[37] = "LightSeaGreen",
	[38] = "DeepSkyBlue2",
	[39] = "DeepSkyBlue1",
	[40] = "Green3_2",
	[41] = "SpringGreen3_2",
	[42] = "SpringGreen2",
	[43] = "Cyan3",
	[44] = "DarkTurquoise",
	[45] = "Turquoise2",
	[46] = "Green1",
	[47] = "SpringGreen2_2",
	[48] = "SpringGreen1",
	[49] = "MediumSpringGreen",
	[50] = "Cyan2",
	[51] = "Cyan1",
	[52] = "DarkRed",
	[53] = "DeepPink4",
	[54] = "Purple4",
	[55] = "Purple4_2",
	[56] = "Purple3",
	[57] = "BlueViolet",
	[58] = "Orange4",
	[59] = "Grey37",
	[60] = "MediumPurple4",
	[61] = "SlateBlue3",
	[62] = "SlateBlue3_2",
	[63] = "RoyalBlue1",
	[64] = "Chartreuse4",
	[65] = "DarkSeaGreen4",
	[66] = "PaleTurquoise4",
	[67] = "SteelBlue",
	[68] = "SteelBlue3",
	[69] = "CornflowerBlue",
	[70] = "Chartreuse3",
	[71] = "DarkSeaGreen4_2",
	[72] = "CadetBlue",
	[73] = "CadetBlue_2",
	[74] = "SkyBlue3",
	[75] = "SteelBlue1",
	[76] = "Chartreuse3_2",
	[77] = "PaleGreen3",
	[78] = "SeaGreen3",
	[79] = "Aquamarine3",
	[80] = "MediumTurquoise",
	[81] = "SteelBlue1_2",
	[82] = "Chartreuse2",
	[83] = "SeaGreen2",
	[84] = "SeaGreen1",
	[85] = "SeaGreen1_2",
	[86] = "Aquamarine1",
	[87] = "DarkSlateGray2",
	[88] = "DarkRed_2",
	[89] = "DeepPink4_2",
	[90] = "DarkMagenta",
	[91] = "DarkMagenta_2",
	[92] = "DarkViolet",
	[93] = "Purple",
	[94] = "Orange4_2",
	[95] = "LightPink4",
	[96] = "Plum4",
	[97] = "MediumPurple3",
	[98] = "MediumPurple3_2",
	[99] = "SlateBlue1",
	[100] = "Yellow4",
	[101] = "Wheat4",
	[102] = "Grey53",
	[103] = "LightSlateGrey",
	[104] = "MediumPurple",
	[105] = "LightSlateBlue",
	[106] = "Yellow4_2",
	[107] = "DarkOliveGreen3",
	[108] = "DarkSeaGreen",
	[109] = "LightSkyBlue3",
	[110] = "LightSkyBlue3_2",
	[111] = "SkyBlue2",
	[112] = "Chartreuse2_2",
	[113] = "DarkOliveGreen3_2",
	[114] = "PaleGreen3_2",
	[115] = "DarkSeaGreen3",
	[116] = "DarkSlateGray3",
	[117] = "SkyBlue1",
	[118] = "Chartreuse1",
	[119] = "LightGreen_2",
	[120] = "LightGreen_3",
	[121] = "PaleGreen1",
	[122] = "Aquamarine1_2",
	[123] = "DarkSlateGray1",
	[124] = "Red3",
	[125] = "DeepPink4_3",
	[126] = "MediumVioletRed",
	[127] = "Magenta3",
	[128] = "DarkViolet_2",
	[129] = "Purple_2",
	[130] = "DarkOrange3",
	[131] = "IndianRed",
	[132] = "HotPink3",
	[133] = "MediumOrchid3",
	[134] = "MediumOrchid",
	[135] = "MediumPurple2",
	[136] = "DarkGoldenrod",
	[137] = "LightSalmon3",
	[138] = "RosyBrown",
	[139] = "Grey63",
	[140] = "MediumPurple2_2",
	[141] = "MediumPurple1",
	[142] = "Gold3",
	[143] = "DarkKhaki",
	[144] = "NavajoWhite3",
	[145] = "Grey69",
	[146] = "LightSteelBlue3",
	[147] = "LightSteelBlue",
	[148] = "Yellow3",
	[149] = "DarkOliveGreen3_3",
	[150] = "DarkSeaGreen3_2",
	[151] = "DarkSeaGreen2",
	[152] = "LightCyan3",
	[153] = "LightSkyBlue1",
	[154] = "GreenYellow",
	[155] = "DarkOliveGreen2",
	[156] = "PaleGreen1_2",
	[157] = "DarkSeaGreen2_2",
	[158] = "DarkSeaGreen1",
	[159] = "PaleTurquoise1",
	[160] = "Red3_2",
	[161] = "DeepPink3",
	[162] = "DeepPink3_2",
	[163] = "Magenta3_2",
	[164] = "Magenta3_3",
	[165] = "Magenta2",
	[166] = "DarkOrange3_2",
	[167] = "IndianRed_2",
	[168] = "HotPink3_2",
	[169] = "HotPink2",
	[170] = "Orchid",
	[171] = "MediumOrchid1",
	[172] = "Orange3",
	[173] = "LightSalmon3_2",
	[174] = "LightPink3",
	[175] = "Pink3",
	[176] = "Plum3",
	[177] = "Violet",
	[178] = "Gold3_2",
	[179] = "LightGoldenrod3",
	[180] = "Tan",
	[181] = "MistyRose3",
	[182] = "Thistle3",
	[183] = "Plum2",
	[184] = "Yellow3_2",
	[185] = "Khaki3",
	[186] = "LightGoldenrod2",
	[187] = "LightYellow3",
	[188] = "Grey84",
	[189] = "LightSteelBlue1",
	[190] = "Yellow2",
	[191] = "DarkOliveGreen1",
	[192] = "DarkOliveGreen1_2",
	[193] = "DarkSeaGreen1_2",
	[194] = "Honeydew2",
	[195] = "LightCyan1",
	[196] = "Red1",
	[197] = "DeepPink2",
	[198] = "DeepPink1",
	[199] = "DeepPink1_2",
	[200] = "Magenta2_2",
	[201] = "Magenta1",
	[202] = "OrangeRed1",
	[203] = "IndianRed1",
	[204] = "IndianRed1_2",
	[205] = "HotPink",
	[206] = "HotPink_2",
	[207] = "MediumOrchid1_2",
	[208] = "DarkOrange",
	[209] = "Salmon1",
	[210] = "LightCoral",
	[211] = "PaleVioletRed1",
	[212] = "Orchid2",
	[213] = "Orchid1",
	[214] = "Orange1",
	[215] = "SandyBrown",
	[216] = "LightSalmon1",
	[217] = "LightPink1",
	[218] = "Pink1",
	[219] = "Plum1",
	[220] = "Gold1",
	[221] = "LightGoldenrod2_2",
	[222] = "LightGoldenrod2_3",
	[223] = "NavajoWhite1",
	[224] = "MistyRose1",
	[225] = "Thistle1",
	[226] = "Yellow1",
	[227] = "LightGoldenrod1",
	[228] = "Khaki1",
	[229] = "Wheat1",
	[230] = "Cornsilk1",
	[231] = "Grey100",
	[232] = "Grey3",
	[233] = "Grey7",
	[234] = "Grey11",
	[235] = "Grey15",
	[236] = "Grey19",
	[237] = "Grey23",
	[238] = "Grey27",
	[239] = "Grey30",
	[240] = "Grey35",
	[241] = "Grey39",
	[242] = "Grey42",
	[243] = "Grey46",
	[244] = "Grey50",
	[245] = "Grey54",
	[246] = "Grey58",
	[247] = "Grey62",
	[248] = "Grey66",
	[249] = "Grey70",
	[250] = "Grey74",
	[251] = "Grey78",
	[252] = "Grey82",
	[253] = "Grey85",
	[254] = "Grey89",
	[255] = "Grey93",
};

/* Maps 256 colors into corresponding #RRGGBB values.  The array is offset by
 * one to allow handling of default color (-1). */
static const int XTERM256_PALETTE[] = {
	-1,
	0, 1, 2, 3, 4, 5, 6, 7,
	0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff,
	0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff,
	0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700,
	0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f,
	0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787,
	0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf,
	0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7,
	0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
	0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00,
	0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f,
	0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87,
	0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f, 0x870087, 0x8700af,
	0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7,
	0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff,
	0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700,
	0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
	0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087,
	0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf,
	0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7,
	0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff,
	0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00,
	0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f,
	0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87,
	0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
	0xd787d7, 0xd787ff, 0xdfaf00, 0xdfaf5f, 0xdfaf87, 0xdfafaf, 0xdfafdf,
	0xdfafff, 0xdfdf00, 0xdfdf5f, 0xdfdf87, 0xdfdfaf, 0xdfdfdf, 0xdfdfff,
	0xdfff00, 0xdfff5f, 0xdfff87, 0xdfffaf, 0xdfffdf, 0xdfffff, 0xff0000,
	0xff005f, 0xff0087, 0xff00af, 0xff00df, 0xff00ff, 0xff5f00, 0xff5f5f,
	0xff5f87, 0xff5faf, 0xff5fdf, 0xff5fff, 0xff8700, 0xff875f, 0xff8787,
	0xff87af, 0xff87df, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf,
	0xffafdf, 0xffafff, 0xffdf00, 0xffdf5f, 0xffdf87, 0xffdfaf, 0xffdfdf,
	0xffdfff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffdf, 0xffffff,
	0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444,
	0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a,
	0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0,
	0xdadada, 0xe4e4e4, 0xeeeeee,
};
ARRAY_GUARD(XTERM256_PALETTE, 1 + 256);

/* Default color scheme definition. */
static const col_attr_t default_cs[] = {
	                      /* fg             bg           attr */
	[WIN_COLOR]          = { COLOR_WHITE,   COLOR_BLACK, 0                       },
	[DIRECTORY_COLOR]    = { COLOR_CYAN,    -1,          A_BOLD                  },
	[LINK_COLOR]         = { COLOR_YELLOW,  -1,          A_BOLD                  },
	[BROKEN_LINK_COLOR]  = { COLOR_RED,     -1,          A_BOLD                  },
	[HARD_LINK_COLOR]    = { COLOR_YELLOW,  -1,          0                       },
	[SOCKET_COLOR]       = { COLOR_MAGENTA, -1,          A_BOLD                  },
	[DEVICE_COLOR]       = { COLOR_RED,     -1,          A_BOLD                  },
	[FIFO_COLOR]         = { COLOR_CYAN,    -1,          A_BOLD                  },
	[EXECUTABLE_COLOR]   = { COLOR_GREEN,   -1,          A_BOLD                  },
	[SELECTED_COLOR]     = { COLOR_MAGENTA, -1,          A_BOLD                  },
	[CURR_LINE_COLOR]    = { -1,            -1,          A_BOLD | A_REVERSE      },
	[TOP_LINE_COLOR]     = { COLOR_BLACK,   COLOR_WHITE, 0                       },
	[TOP_LINE_SEL_COLOR] = { COLOR_BLACK,   -1,          A_BOLD                  },
	[STATUS_LINE_COLOR]  = { COLOR_BLACK,   COLOR_WHITE, A_BOLD                  },
	[WILD_BOX_COLOR]     = { -1,            -1,          -1                      },
	[WILD_MENU_COLOR]    = { COLOR_WHITE,   COLOR_BLACK, A_UNDERLINE | A_REVERSE },
	[CMD_LINE_COLOR]     = { COLOR_WHITE,   COLOR_BLACK, 0                       },
	[ERROR_MSG_COLOR]    = { COLOR_RED,     COLOR_BLACK, 0                       },
	[BORDER_COLOR]       = { COLOR_BLACK,   COLOR_WHITE, 0                       },
	[OTHER_LINE_COLOR]   = { -1,            -1,          -1                      },
	[JOB_LINE_COLOR]     = { COLOR_BLACK,   COLOR_WHITE, A_BOLD | A_REVERSE      },
	[SUGGEST_BOX_COLOR]  = { -1,            -1,          A_BOLD                  },
	[MISMATCH_COLOR]     = { COLOR_WHITE,   COLOR_RED,   A_BOLD                  },
	[UNMATCHED_COLOR]    = { COLOR_WHITE,   COLOR_GREEN, A_BOLD                  },
	[BLANK_COLOR]        = { -1,            -1,          -1                      },
	[AUX_WIN_COLOR]      = { -1,            -1,          -1                      },
	[TAB_LINE_COLOR]     = { COLOR_WHITE,   COLOR_BLACK, 0                       },
	[TAB_LINE_SEL_COLOR] = { -1,            -1,          A_BOLD | A_REVERSE      },
	[USER1_COLOR]        = { -1,            -1,          -1                      },
	[USER2_COLOR]        = { -1,            -1,          -1                      },
	[USER3_COLOR]        = { -1,            -1,          -1                      },
	[USER4_COLOR]        = { -1,            -1,          -1                      },
	[USER5_COLOR]        = { -1,            -1,          -1                      },
	[USER6_COLOR]        = { -1,            -1,          -1                      },
	[USER7_COLOR]        = { -1,            -1,          -1                      },
	[USER8_COLOR]        = { -1,            -1,          -1                      },
	[USER9_COLOR]        = { -1,            -1,          -1                      },
	[USER10_COLOR]       = { -1,            -1,          -1                      },
	[USER11_COLOR]       = { -1,            -1,          -1                      },
	[USER12_COLOR]       = { -1,            -1,          -1                      },
	[USER13_COLOR]       = { -1,            -1,          -1                      },
	[USER14_COLOR]       = { -1,            -1,          -1                      },
	[USER15_COLOR]       = { -1,            -1,          -1                      },
	[USER16_COLOR]       = { -1,            -1,          -1                      },
	[USER17_COLOR]       = { -1,            -1,          -1                      },
	[USER18_COLOR]       = { -1,            -1,          -1                      },
	[USER19_COLOR]       = { -1,            -1,          -1                      },
	[USER20_COLOR]       = { -1,            -1,          -1                      },
	[OTHER_WIN_COLOR]    = { -1,            -1,          -1                      },
	[LINE_NUM_COLOR]     = { -1,            -1,          -1                      },
	[ODD_LINE_COLOR]     = { -1,            -1,          -1                      },
};
ARRAY_GUARD(default_cs, MAXNUM_COLOR);

static char ** list_cs_files(int *len);
static void restore_primary_cs(const col_scheme_t *cs);
static void reset_to_default_cs(col_scheme_t *cs);
static void free_cs_highlights(col_scheme_t *cs);
static file_hi_t * clone_file_highlights(const col_scheme_t *from);
static col_attr_t * clone_column_highlights(const col_scheme_t *from);
static void reset_cs_colors(col_scheme_t *cs);
static int source_cs(const char name[]);
static void get_cs_path(const char name[], char buf[], size_t buf_size);
static int get_colors_dir(int idx, char buf[], size_t buf_len);
static void check_cs(col_scheme_t *cs);
static void load_color_pairs(col_scheme_t *cs);
static void ensure_dir_map_exists(void);
static void overlap_cterm_colors(col_attr_t *color,
		const col_attr_t *admixture);
static void overlap_gui_colors(col_attr_t *color, const col_attr_t *admixture);
static col_attr_t convert_to_gui(const col_attr_t *color);
static int color_diff(int a, int b);

/* Mapping of color schemes associations onto file system tree. */
static fsddata_t *dir_map;

int
cs_have_no_extensions(void)
{
	int len;
	char **list = list_cs_files(&len);
	int i;

	/* Check if extensions are in use. */
	for(i = 0; i < len; ++i)
	{
		if(ends_with(list[i], ".vifm"))
		{
			break;
		}
	}

	free_string_array(list, len);
	return (len > 0 && i >= len);
}

void
cs_rename_all(void)
{
	DIR *dir;
	struct dirent *d;

	dir = os_opendir(cfg.colors_dir);
	if(dir == NULL)
	{
		return;
	}

	while((d = os_readdir(dir)) != NULL)
	{
#ifndef _WIN32
		unsigned char type;
#endif
		char full_old_path[PATH_MAX + 32 + NAME_MAX];
		char full_new_path[PATH_MAX + 32 + NAME_MAX];
		snprintf(full_old_path, sizeof(full_old_path), "%s/%s", cfg.colors_dir,
				d->d_name);
		snprintf(full_new_path, sizeof(full_new_path), "%s/%s.vifm", cfg.colors_dir,
				d->d_name);

#ifndef _WIN32
		type = get_dirent_type(d, full_old_path);
		if(type == DT_REG || (type == DT_UNKNOWN && is_regular_file(full_old_path)))
#else
		if(is_regular_file(full_old_path))
#endif
		{
			(void)os_rename(full_old_path, full_new_path);
		}
	}
	os_closedir(dir);
}

char **
cs_list(int *len)
{
	char **const list = list_cs_files(len);
	int i, j;
	int new_names;

	/* Check if extensions are in use. */
	new_names = 0;
	for(i = 0; i < *len; ++i)
	{
		if(ends_with(list[i], ".vifm"))
		{
			new_names = 1;
		}
	}

	/* Remove hidden files, maybe along with old style file names, maybe cut off
	 * extensions from new style file names. */
	j = 0;
	for(i = 0; i < *len; ++i)
	{
		if(list[i][0] == '.' || (new_names && !cut_suffix(list[i], ".vifm")))
		{
			free(list[i]);
		}
		else
		{
			list[j++] = list[i];
		}
	}
	*len = j;

	return list;
}

/* Lists names of all files in color schemes directories.  Allocates an array of
 * strings, which should be freed by the caller.  Always sets *len.  Returns
 * NULL on error. */
static char **
list_cs_files(int *len)
{
	char **list = NULL;
	*len = 0;

	int i = 0;
	while(1)
	{
		char colors_dir[PATH_MAX + 1];
		if(!get_colors_dir(i++, colors_dir, sizeof(colors_dir)))
		{
			break;
		}

		list = list_regular_files(colors_dir, list, len);
	}

	return list;
}

int
cs_exists(const char name[])
{
	char cs_path[PATH_MAX + 32];
	get_cs_path(name, cs_path, sizeof(cs_path));
	return is_regular_file(cs_path);
}

void
cs_write(void)
{
	FILE *fp;
	char def_cs_path[PATH_MAX + 32];
	size_t i;

	if(create_path(cfg.colors_dir, S_IRWXU) != 0)
	{
		/* Do nothing if local colors directory exists or we've failed to create
		 * it. */
		return;
	}

	snprintf(def_cs_path, sizeof(def_cs_path), "%s/Default.vifm", cfg.colors_dir);
	fp = os_fopen(def_cs_path, "w");
	if(fp == NULL)
	{
		return;
	}

	fprintf(fp, "\" You can edit this file by hand.\n");
	fprintf(fp, "\" The \" character at the beginning of a line comments out the line.\n");
	fprintf(fp, "\" Blank lines are ignored.\n\n");

	fprintf(fp, "\" The Default color scheme is used for any directory that does not have\n");
	fprintf(fp, "\" a specified scheme and for parts of user interface like menus. A\n");
	fprintf(fp, "\" color scheme set for a base directory will also\n");
	fprintf(fp, "\" be used for the sub directories.\n\n");

	fprintf(fp, "\" The standard ncurses colors are:\n");
	fprintf(fp, "\" Default = -1 = None, can be used for transparency or default color\n");
	fprintf(fp, "\" Black = 0\n");
	fprintf(fp, "\" Red = 1\n");
	fprintf(fp, "\" Green = 2\n");
	fprintf(fp, "\" Yellow = 3\n");
	fprintf(fp, "\" Blue = 4\n");
	fprintf(fp, "\" Magenta = 5\n");
	fprintf(fp, "\" Cyan = 6\n");
	fprintf(fp, "\" White = 7\n\n");

	fprintf(fp, "\" Light versions of colors are also available (they set bold\n"
	            "\" attribute in terminals with less than 16 colors):\n");
	fprintf(fp, "\" LightBlack\n");
	fprintf(fp, "\" LightRed\n");
	fprintf(fp, "\" LightGreen\n");
	fprintf(fp, "\" LightYellow\n");
	fprintf(fp, "\" LightBlue\n");
	fprintf(fp, "\" LightMagenta\n");
	fprintf(fp, "\" LightCyan\n");
	fprintf(fp, "\" LightWhite\n\n");

	fprintf(fp, "\" Available attributes (some of them can be combined):\n");
	fprintf(fp, "\" bold\n");
	fprintf(fp, "\" underline\n");
	fprintf(fp, "\" reverse or inverse\n");
	fprintf(fp, "\" standout\n");
	fprintf(fp, "\" italic (on unsupported systems becomes reverse)\n");
	fprintf(fp, "\" combine\n");
	fprintf(fp, "\" none\n\n");

	fprintf(fp, "\" Vifm supports 256 colors you can use color numbers 0-255\n");
	fprintf(fp, "\" (requires properly set up terminal: set your TERM environment variable\n");
	fprintf(fp, "\" (directly or using resources) to some color terminal name (e.g.\n");
	fprintf(fp, "\" xterm-256color) from /usr/lib/terminfo/; you can check current number\n");
	fprintf(fp, "\" of colors in your terminal with tput colors command)\n\n");

	fprintf(fp, "\" highlight group cterm=attrs ctermfg=foreground_color ctermbg=background_color\n\n");

	fprintf(fp, "highlight clear\n\n");

	for(i = 0U; i < ARRAY_LEN(default_cs); ++i)
	{
		char fg_buf[16], bg_buf[16];
		cs_color_to_str(default_cs[i].fg, sizeof(fg_buf), fg_buf, /*is_gui=*/0);
		cs_color_to_str(default_cs[i].bg, sizeof(bg_buf), bg_buf, /*is_gui=*/0);

		if(default_cs[i].attr == -1)
		{
			fprintf(fp, "highlight %s ctermfg=%s ctermbg=%s\n", HI_GROUPS[i], fg_buf,
					bg_buf);
		}
		else
		{
			fprintf(fp, "highlight %s cterm=%s ctermfg=%s ctermbg=%s\n", HI_GROUPS[i],
					cs_attrs_to_str(&default_cs[i], /*gui_part=*/0), fg_buf, bg_buf);
		}
	}

	fclose(fp);
}

void
cs_color_to_str(int color, size_t buf_len, char str_buf[], int is_gui)
{
	int color_limit = (is_gui ? 8 : ARRAY_LEN(XTERM256_COLOR_NAMES));

	if(color == -1)
	{
		copy_str(str_buf, buf_len, "default");
	}
	else if(color >= 0 && color < color_limit)
	{
		copy_str(str_buf, buf_len, XTERM256_COLOR_NAMES[color]);
	}
	else if(is_gui)
	{
		snprintf(str_buf, buf_len, "#%06x", color);
	}
	else
	{
		snprintf(str_buf, buf_len, "%d", color);
	}
}

void
cs_load_primary(const char name[])
{
	col_scheme_t prev_cs = {};

	if(!cs_exists(name))
	{
		show_error_msgf("Color Scheme", "No such color scheme: \"%s\"", name);
		return;
	}

	cs_assign(&prev_cs, &cfg.cs);
	curr_stats.cs = &cfg.cs;
	cfg.cs.state = CSS_LOADING;

	if(source_cs(name) != 0)
	{
		restore_primary_cs(&prev_cs);
		show_error_msgf("Color Scheme Sourcing",
				"An error occurred on sourcing color scheme: \"%s\"", name);
		return;
	}

	check_cs(&cfg.cs);
	if(cfg.cs.state == CSS_DEFAULTED)
	{
		restore_primary_cs(&prev_cs);
		show_error_msgf("Color Scheme Error",
				"\"%s\" color scheme is not supported by the terminal, restored \"%s\"",
				name, prev_cs.name);
		return;
	}

	copy_str(cfg.cs.name, sizeof(cfg.cs.name), name);
	update_attributes();

	free_cs_highlights(&prev_cs);
	cfg.cs.state = CSS_NORMAL;
}

void
cs_load_primary_list(char *names[], int count)
{
	col_scheme_t prev_cs = {};
	cs_assign(&prev_cs, &cfg.cs);

	while(count-- > 0)
	{
		const char *name = *names++;

		cs_assign(&cfg.cs, &prev_cs);
		curr_stats.cs = &cfg.cs;
		cfg.cs.state = CSS_LOADING;

		if(!cs_exists(name))
		{
			continue;
		}

		if(source_cs(name) != 0)
		{
			show_error_msgf("Color Scheme Sourcing",
					"An error occurred on sourcing color scheme: \"%s\"", name);
			continue;
		}

		check_cs(&cfg.cs);
		if(cfg.cs.state == CSS_DEFAULTED)
		{
			continue;
		}

		copy_str(cfg.cs.name, sizeof(cfg.cs.name), name);
		update_attributes();

		free_cs_highlights(&prev_cs);
		cfg.cs.state = CSS_NORMAL;

		return;
	}

	restore_primary_cs(&prev_cs);
}

/* Restore previous state of primary color scheme. */
static void
restore_primary_cs(const col_scheme_t *cs)
{
	free_cs_highlights(&cfg.cs);
	cfg.cs = *cs;
	cs_load_pairs();
	update_screen(UT_FULL);
}

void
cs_load_pairs(void)
{
	ensure_dir_map_exists();

	load_color_pairs(&cfg.cs);
	load_color_pairs(&lwin.cs);
	load_color_pairs(&rwin.cs);
}

void
cs_load_defaults(void)
{
	fsddata_free(dir_map);
	dir_map = NULL;

	lwin.local_cs = 0;
	rwin.local_cs = 0;

	reset_to_default_cs(&cfg.cs);
	reset_to_default_cs(&lwin.cs);
	reset_to_default_cs(&rwin.cs);

	load_color_pairs(&cfg.cs);
	load_color_pairs(&lwin.cs);
	load_color_pairs(&rwin.cs);
}

/* Completely resets the cs to builtin default color scheme.  Changes: colors,
 * name, state. */
static void
reset_to_default_cs(col_scheme_t *cs)
{
	reset_cs_colors(cs);

	copy_str(cs->name, sizeof(cs->name), DEF_CS_NAME);

	cs->state = CSS_NORMAL;
}

void
cs_reset(col_scheme_t *cs)
{
	reset_cs_colors(cs);
	free_cs_highlights(cs);
	load_color_pairs(cs);
}

void
cs_assign(col_scheme_t *to, const col_scheme_t *from)
{
	free_cs_highlights(to);
	*to = *from;
	to->file_hi = clone_file_highlights(from);
	to->column_hi = clone_column_highlights(from);
}

/* Resets color scheme to default builtin values. */
static void
reset_cs_colors(col_scheme_t *cs)
{
	size_t i;
	for(i = 0U; i < ARRAY_LEN(default_cs); ++i)
	{
		cs->color[i] = default_cs[i];
	}
}

/* Frees data structures of the color scheme that are related to filename
 * specific highlight. */
static void
free_cs_highlights(col_scheme_t *cs)
{
	int i;

	for(i = 0; i < cs->file_hi_count; ++i)
	{
		matchers_free(cs->file_hi[i].matchers);
	}

	free(cs->file_hi);
	cs->file_hi = NULL;
	cs->file_hi_count = 0;

	free(cs->column_hi);
	cs->column_hi = NULL;
	cs->column_hi_count = 0;
}

/* Clones filename specific highlight array of the *from color scheme and
 * returns it. */
static file_hi_t *
clone_file_highlights(const col_scheme_t *from)
{
	int i;
	file_hi_t *const file_hi = reallocarray(NULL, from->file_hi_count + 1,
			sizeof(*file_hi));

	for(i = 0; i < from->file_hi_count; ++i)
	{
		const file_hi_t *const hi = &from->file_hi[i];
		file_hi[i].matchers = matchers_clone(hi->matchers);
		file_hi[i].hi = hi->hi;
	}

	return file_hi;
}

/* Clones column highlight array of the *from color scheme and returns it. */
static col_attr_t *
clone_column_highlights(const col_scheme_t *from)
{
	col_attr_t *column_hi = NULL;

	size_t size = sizeof(*from->column_hi)*from->column_hi_count;
	if(size != 0)
	{
		column_hi = malloc(size);
		if(column_hi != NULL)
		{
			memcpy(column_hi, from->column_hi, size);
		}
	}
	return column_hi;
}

int
cs_load_local(int left, const char dir[])
{
	if(dir_map == NULL || curr_stats.cs != &cfg.cs)
	{
		/* Either have nothing to do, or we're already doing it (sourcing a file can
		 * trigger view redraws). */
		return 0;
	}

	curr_stats.cs = left ? &lwin.cs : &rwin.cs;
	cs_assign(curr_stats.cs, &cfg.cs);

	/* TODO: maybe use split_and_get() here as in io/iop:iop_mkdir(). */
	char *const dir_copy = strdup(dir);
	if(dir_copy == NULL)
	{
		return 0;
	}

	char *p = dir_copy;
	int altered = 0;
	char t;
	do
	{
		void *name;

		t = *p;
		*p = '\0';

		if(fsddata_get(dir_map, dir_copy, &name) == 0 && cs_exists(name))
		{
			(void)source_cs(name);
			altered = 1;
		}

		*p = t;
		p = (t == '\0' ? NULL : until_first(p + 1, '/'));
	}
	while(t != '\0');
	free(dir_copy);

	curr_stats.cs = &cfg.cs;

	if(!altered)
	{
		return 0;
	}

	check_cs(curr_stats.cs);
	load_color_pairs(curr_stats.cs);

	return 1;
}

/* Sources color scheme file.  Returns non-zero on error (e.g. file doesn't
 * exist), and zero otherwise. */
static int
source_cs(const char name[])
{
	char cs_path[PATH_MAX + 1];
	get_cs_path(name, cs_path, sizeof(cs_path));
	return cfg_source_file(cs_path);
}

/* Fill the buffer of specified size with path to color scheme, which might not
 * exist. */
static void
get_cs_path(const char name[], char buf[], size_t buf_size)
{
	int i = 0;
	while(1)
	{
		char colors_dir[PATH_MAX + 1];
		if(!get_colors_dir(i++, colors_dir, sizeof(colors_dir)))
		{
			break;
		}

		snprintf(buf, buf_size, "%s/%s.vifm", colors_dir, name);
		if(is_regular_file(buf))
		{
			break;
		}

		(void)cut_suffix(buf, ".vifm");
		if(is_regular_file(buf))
		{
			break;
		}
	}
}

/* Retrieves path to a directory containing color schemes.  Returns non-zero on
 * success. */
static int
get_colors_dir(int idx, char buf[], size_t buf_len)
{
	if(idx == 0)
	{
		return (copy_str(buf, buf_len, cfg.colors_dir) < buf_len);
	}

	const char *conf_dir = get_sys_conf_dir(idx - 1);
	if(conf_dir == NULL)
	{
		return 0;
	}

	int written = snprintf(buf, buf_len, "%s/colors", conf_dir);
	return (written >= 0 && written < (int)buf_len);
}

/* Checks whether colorscheme is in unusable state and resets it to normal
 * state. */
static void
check_cs(col_scheme_t *cs)
{
	if(cs->state == CSS_BROKEN)
	{
		reset_cs_colors(cs);
		cs->state = CSS_DEFAULTED;
	}
}

/* Loads color scheme settings into color pairs. */
static void
load_color_pairs(col_scheme_t *cs)
{
	int i;
	for(i = 0; i < MAXNUM_COLOR; ++i)
	{
		cs->pair[i] = cs_load_color(&cs->color[i]);
	}
}

void
cs_complete(const char name[])
{
	int i;
	size_t len;
	int schemes_len;
	char **schemes;

	len = strlen(name);
	schemes = cs_list(&schemes_len);

	for(i = 0; i < schemes_len; ++i)
	{
		if(schemes[i][0] != '.' || name[0] == '.')
		{
			if(strnoscmp(name, schemes[i], len) == 0)
			{
				vle_compl_add_match(schemes[i], "");
			}
		}
	}

	free_string_array(schemes, schemes_len);

	vle_compl_finish_group();
	vle_compl_add_last_match(name);
}

const char *
cs_attrs_to_str(const col_attr_t *color, int gui_part)
{
	static char result[64];

	const int attrs = (gui_part ? color->gui_attr : color->attr);
	const int combine =
		(gui_part ? color->combine_gui_attrs : color->combine_attrs);

	if((attrs == 0 || attrs == -1) && !combine)
	{
		return strcpy(result, "none");
	}

	result[0] = '\0';
	if((attrs & A_BOLD) == A_BOLD)
		strcat(result, "bold,");
	if((attrs & A_UNDERLINE) == A_UNDERLINE)
		strcat(result, "underline,");
	if((attrs & A_REVERSE) == A_REVERSE)
		strcat(result, "reverse,");
	if((attrs & A_STANDOUT) == A_STANDOUT)
		strcat(result, "standout,");
#ifdef HAVE_A_ITALIC_DECL
	if((attrs & A_ITALIC) == A_ITALIC)
		strcat(result, "italic,");
#endif
	if(combine)
		strcat(result, "combine,");
	if(result[0] != '\0')
		result[strlen(result) - 1] = '\0';
	return result;
}

void
cs_assoc_dir(const char name[], const char dir[])
{
	char *const copy = strdup(name);

	ensure_dir_map_exists();

	if(fsddata_set(dir_map, dir, copy) != 0)
	{
		free(copy);
	}
}

/* Makes sure that dir_map variable is initialized. */
static void
ensure_dir_map_exists(void)
{
	if(dir_map == NULL)
	{
		dir_map = fsddata_create(1, 1);
	}
}

void
cs_mix_colors(col_attr_t *color, const col_attr_t *admixture)
{
	if(curr_stats.direct_color)
	{
		*color = convert_to_gui(color);
	}

	const int attr = color->attr;
	const int gui_attr = color->gui_attr;

	cs_overlap_colors(color, admixture);

	if(attr != -1 && admixture->combine_attrs)
	{
		color->attr |= attr;
	}

	if(gui_attr != -1 && admixture->combine_gui_attrs)
	{
		color->gui_attr |= gui_attr;
	}
}

void
cs_overlap_colors(col_attr_t *color, const col_attr_t *admixture)
{
	if(curr_stats.direct_color)
	{
		*color = convert_to_gui(color);
		const col_attr_t gui_admixture = convert_to_gui(admixture);
		overlap_gui_colors(color, &gui_admixture);
	}
	else
	{
		overlap_cterm_colors(color, admixture);
	}
}

/* Overlaps cterm colors of *admixture into the *color.  Non-transparent
 * properties of *admixture are transferred onto *color.  Attributes are never
 * combined. */
static void
overlap_cterm_colors(col_attr_t *color, const col_attr_t *admixture)
{
	if(admixture->fg != -1)
	{
		color->fg = admixture->fg;
	}

	if(admixture->bg != -1)
	{
		color->bg = admixture->bg;
	}

	if(admixture->attr != -1)
	{
		color->attr = admixture->attr;
	}
}

/* Overlaps gui colors of *admixture into the *color.  Non-transparent
 * properties of *admixture are transferred onto *color.  Attributes are never
 * combined. */
static void
overlap_gui_colors(col_attr_t *color, const col_attr_t *admixture)
{
	if(admixture->gui_fg != -1)
	{
		color->gui_fg = admixture->gui_fg;
	}

	if(admixture->gui_bg != -1)
	{
		color->gui_bg = admixture->gui_bg;
	}

	if(admixture->gui_attr != -1)
	{
		color->gui_attr = admixture->gui_attr;
	}
}

void
cs_add_file_hi(struct matchers_t *matchers, const col_attr_t *hi)
{
	void *p;
	file_hi_t *file_hi;
	col_scheme_t *const cs = curr_stats.cs;

	/* Try to find existing record that exactly matches the expression and update
	 * the record. */
	int i;
	const char *expr = matchers_get_expr(matchers);
	for(i = 0; i < cs->file_hi_count; ++i)
	{
		if(strcmp(matchers_get_expr(cs->file_hi[i].matchers), expr) == 0)
		{
			matchers_free(matchers);
			cs->file_hi[i].hi = *hi;
			return;
		}
	}

	p = reallocarray(cs->file_hi, cs->file_hi_count + 1, sizeof(*cs->file_hi));
	if(p == NULL)
	{
		matchers_free(matchers);
		show_error_msg("Color Scheme File Highlight", "Not enough memory");
		return;
	}
	cs->file_hi = p;

	file_hi = &cs->file_hi[cs->file_hi_count];

	file_hi->matchers = matchers;
	file_hi->hi = *hi;

	++cs->file_hi_count;
}

const col_attr_t *
cs_get_file_hi(const col_scheme_t *cs, const char fname[], int *hi_hint)
{
	if(*hi_hint == INT_MAX)
	{
		return NULL;
	}
	if(*hi_hint != -1)
	{
		assert(*hi_hint >= 0 && "Wrong index.");
		assert(*hi_hint < cs->file_hi_count && "Wrong index.");
		return &cs->file_hi[*hi_hint].hi;
	}

	int i;
	for(i = 0; i < cs->file_hi_count; ++i)
	{
		const file_hi_t *const file_hi = &cs->file_hi[i];
		if(matchers_match(file_hi->matchers, fname))
		{
			*hi_hint = i;
			return &file_hi->hi;
		}
	}

	*hi_hint = INT_MAX;
	return NULL;
}

int
cs_del_file_hi(const char matchers_expr[])
{
	col_scheme_t *const cs = curr_stats.cs;

	int i;
	for(i = 0; i < cs->file_hi_count; ++i)
	{
		if(strcmp(matchers_get_expr(cs->file_hi[i].matchers), matchers_expr) == 0)
		{
			matchers_free(cs->file_hi[i].matchers);
			memmove(&cs->file_hi[i], &cs->file_hi[i + 1],
					sizeof(*cs->file_hi)*((cs->file_hi_count - 1) - i));
			--cs->file_hi_count;
			return 1;
		}
	}

	return 0;
}

int
cs_is_color_set(const col_attr_t *color)
{
	return color->fg != -1 || color->bg != -1 || color->attr != -1
	    || color->gui_set;
}

int
cs_load_color(const col_attr_t *color)
{
	if(curr_stats.direct_color)
	{
		if(color->gui_set)
		{
			return colmgr_get_pair(color->gui_fg, color->gui_bg);
		}

		const col_attr_t gui = convert_to_gui(color);
		return colmgr_get_pair(gui.gui_fg, gui.gui_bg);
	}

	return colmgr_get_pair(color->fg, color->bg);
}

/* Converts cterm colors to a corresponding gui direct colors if necessary.
 * Returns direct colors with cterm part erased. */
static col_attr_t
convert_to_gui(const col_attr_t *color)
{
	if(!curr_stats.direct_color || color->gui_set)
	{
		return *color;
	}

	col_attr_t gui = {
		.gui_fg = -1,
		.gui_bg = -1,
		.gui_attr = color->attr,
		.combine_gui_attrs = color->combine_attrs,
		.gui_set = 1,
	};

	if(color->fg >= -1 && 1 + color->fg < (int)ARRAY_LEN(XTERM256_PALETTE))
	{
		gui.gui_fg = XTERM256_PALETTE[1 + color->fg];
	}
	if(color->bg >= -1 && 1 + color->bg < (int)ARRAY_LEN(XTERM256_PALETTE))
	{
		gui.gui_bg = XTERM256_PALETTE[1 + color->bg];
	}

	return gui;
}

int
cs_color_get_attr(const col_attr_t *color)
{
	return (curr_stats.direct_color && color->gui_set) ? color->gui_attr
	                                                   : color->attr;
}

cchar_t
cs_color_to_cchar(const col_attr_t *color, int pair)
{
	if(pair < 0)
	{
		pair = cs_load_color(color);
	}

	cchar_t cch;
	setcchar(&cch, L" ", cs_color_get_attr(color), pair, NULL);
	return cch;
}

void
cs_color_enable_gui(col_attr_t *color)
{
	if(!color->gui_set)
	{
		color->gui_set = 1;
		color->gui_fg = -1;
		color->gui_bg = -1;
		color->gui_attr = 0;
		color->combine_gui_attrs = 0;
	}
}

int
cs_downscale_color(int direct_color)
{
	int i;
	int closest = 0;
	int diff = color_diff(XTERM256_PALETTE[1], direct_color);
	int color_limit = MIN(COLORS, 256);
	for(i = 1; i < color_limit; ++i)
	{
		int d = color_diff(XTERM256_PALETTE[1 + i], direct_color);
		if(d < diff)
		{
			diff = d;
			closest = i;
		}
	}
	return closest;
}

/* Computes distance between two RGB colors.  Returns the distance. */
static int
color_diff(int a, int b)
{
	int rd = abs((a >> 16) - (b >> 16));
	int gd = abs(((a >> 8)&0xff) - ((b >> 8)&0xff));
	int bd = abs((a&0xff) - (b&0xff));
	return rd + gd + bd;
}

int
cs_set_column_hi(col_scheme_t *cs, int column_id, const col_attr_t *hi)
{
	if(column_id >= cs->column_hi_count)
	{
		void *p =
			reallocarray(cs->column_hi, column_id + 1, sizeof(*cs->column_hi));
		if(p == NULL)
		{
			return 1;
		}
		cs->column_hi = p;

		int i;
		for(i = cs->column_hi_count; i < column_id; ++i)
		{
			memset(&cs->column_hi[i], 0xff, sizeof(cs->column_hi[i]));
		}

		cs->column_hi_count = column_id + 1;
	}

	cs->column_hi[column_id] = *hi;
	return 0;
}

const col_attr_t *
cs_get_column_hi(const col_scheme_t *cs, int column_id)
{
	if(column_id <= 0 || column_id >= cs->column_hi_count)
	{
		return NULL;
	}

	char ones[sizeof(cs->column_hi[column_id])];
	memset(ones, 0xff, sizeof(ones));

	/* XXX: might want to improve marking entries as unused. */
	if(memcmp(&cs->column_hi[column_id], &ones, sizeof(ones)) == 0)
	{
		return NULL;
	}
	return &cs->column_hi[column_id];
}

int
cs_del_column_hi(col_scheme_t *cs, int column_id)
{
	if(cs_get_column_hi(cs, column_id) == NULL)
	{
		return 0;
	}

	memset(&cs->column_hi[column_id], 0xff, sizeof(cs->column_hi[column_id]));
	return 1;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
