let g:Pl#Parser#Symbols = {
	\ 'compatible': {
		\   'dividers': [ '', [0x2502], '', [0x2502] ]
		\ , 'symbols' : {
			\   'BRANCH': 'BR:'
			\ , 'RO'    : 'RO'
			\ , 'FT'    : 'FT'
			\ , 'LINE'  : 'LN'
			\ , 'COL'   : 'C'
		\ }
	\ },
	\ 'unicode': {
		\   'dividers': [ [0x25b6], [0x276f], [0x25c0], [0x276e]  ]
		\ , 'symbols' : {
			\   'BRANCH': [0x26a1]
			\ , 'RO'    : [0x2613]
			\ , 'FT'    : [0x2691]
			\ , 'LINE'  : [0x204b]
			\ , 'COL'   : [0x2551]
		\ },
	\ },
	\ 'fancy': {
		\   'dividers': [ [0x2b80], [0x2b81], [0x2b82], [0x2b83] ]
		\ , 'symbols' : {
			\   'BRANCH': [0x2b60]
			\ , 'RO'    : [0x2b64]
			\ , 'FT'    : [0x2b62, 0x2b63]
			\ , 'LINE'  : [0x2b61]
			\ , 'COL'   : [0x2551]
		\ }
	\ }
\ }

let s:LEFT_SIDE = 0
let s:RIGHT_SIDE = 2

let s:PADDING = 1

let s:EMPTY_SEGMENT = { 'type': 'empty' }

let s:HARD_DIVIDER = 0
let s:SOFT_DIVIDER = 1

function! Pl#Parser#GetStatusline(segments) " {{{
	let statusline = {
		\   'n': ''
		\ , 'N': ''
		\ , 'v': ''
		\ , 'i': ''
		\ , 'r': ''
		\ , 's': ''
		\ }

	" Run through the different modes and create the statuslines
	for mode in keys(statusline)
		" Create an empty statusline list
		let stl = []

		call extend(stl, s:ParseSegments(mode, s:LEFT_SIDE, a:segments))

		let statusline[mode] .= join(stl, '')
	endfor

	return statusline
endfunction " }}}
function! Pl#Parser#ParseChars(arg) " {{{
	" Handles symbol arrays which can be either an array of hex values,
	" or a string. Will convert the hex array to a string, or return the
	" string as-is.
	let arg = a:arg

	if type(arg) == type([])
		" Hex array
		call map(arg, 'nr2char(v:val)')

		return join(arg, '')
	endif

	" Anything else, just return it as it is
	return arg
endfunction " }}}
function! s:ParseSegments(mode, side, segments, ...) " {{{
	let mode     = a:mode
	let side     = a:side
	let segments = a:segments

	let level      = exists('a:1') ? a:1 : 0
	let base_color = exists('a:2') ? a:2 : {}

	let ret = []

	for i in range(0, len(segments) - 1)
		unlet! seg_prev seg_curr seg_next

		" Prepare some resources (fetch previous, current and next segment)
		let seg_curr = deepcopy(get(segments, i))

		" Find previous segment
		let seg_prev = s:EMPTY_SEGMENT

		" If we're currently at i = 0 we have to start on 0 or else we will start on the last segment (list[-1])
		let range_start = (i == 0 ? i : i - 1)
		for j in range(range_start, 0, -1)
			let seg = deepcopy(get(segments, j))
			if get(seg, 'name') ==# 'TRUNCATE'
				" Skip truncate segments
				continue
			endif

			" Look ahead for another segment that's visible in this mode
			if index(get(seg, 'modes'), mode) != -1
				" Use this segment
				let seg_prev = seg

				break
			endif
		endfor

		"" Find next segment
		let seg_next = s:EMPTY_SEGMENT

		" If we're currently at i = len(segments) - 1 we have to start on i or else we will get an error because the index doesn't exist
		let range_start = (i == len(segments) - 1 ? i : i + 1)
		for j in range(range_start, len(segments) - 1, 1)
			let seg = deepcopy(get(segments, j))
			if get(seg, 'name') ==# 'TRUNCATE'
				" Skip truncate segments
				continue
			endif

			" Look ahead for another segment that's visible in this mode
			if index(get(seg, 'modes'), mode) != -1
				" Use this segment
				let seg_next = seg

				break
			endif
		endfor

		if index(get(seg_curr, 'modes', []), mode) == -1
			" The segment is invisible in this mode, skip it
			" FIXME When two segments after each other are hidden, a gap appears where the segments would be, this is probably due to segment padding
			continue
		endif

		" Handle the different segment types
		if seg_curr.type == 'segment'
			if seg_curr.name ==# 'TRUNCATE'
				" Truncate statusline
				call add(ret, '%<')
			elseif seg_curr.name ==# 'SPLIT'
				" Split statusline

				" Switch sides
				let side = s:RIGHT_SIDE

				" Handle highlighting
				let mode_colors = get(seg_curr.colors, mode, seg_curr.colors['n'])
				let hl_group = s:HlCreate(mode_colors)

				" Add segment text
				call add(ret, '%#'. hl_group .'#%=')
			else
				" Add segment text
				let text = seg_curr.text

				" Decide on whether to use the group's colors or the segment's colors
				let colors = get(seg_curr, 'colors', base_color)

				" Fallback to normal (current) highlighting if we don't have mode-specific highlighting
				let mode_colors = get(colors, mode, get(colors, 'n', {}))

				if empty(mode_colors)
					echom 'Segment doesn''t have any colors! NS: "'. seg_curr.ns .'" SEG: "'. seg_curr.name .'"'

					continue
				endif

				" Check if we're in a group (level > 0)
				if level > 0
					" If we're in a group we don't have dividers between segments, so we should only pad one side
					let padding_right = (side == s:LEFT_SIDE  ? repeat(' ', s:PADDING) : '')
					let padding_left  = (side == s:RIGHT_SIDE ? repeat(' ', s:PADDING) : '')

					" Check if we lack a bg/fg color for this segment
					" If we do, use the bg/fg color from base_color
					let base_color_mode = ! has_key(base_color, mode) ? base_color['n'] : base_color[mode]

					for col in ['ctermbg', 'ctermfg', 'guibg', 'guifg']
						if empty(mode_colors[col])
							let mode_colors[col] = base_color_mode[col]
						endif
					endfor
				else
					"" If we're outside a group we have dividers and must pad both sides
					let padding_left  = repeat(' ', s:PADDING)
					let padding_right = repeat(' ', s:PADDING)
				endif

				" Get main hl group for segment
				let hl_group = s:HlCreate(mode_colors)

				" Prepare segment text
				let text = '%(%#'. hl_group .'#'. padding_left . text . padding_right . '%)'

				if level == 0
					" Add divider to single segments
					let text = s:AddDivider(text, side, mode, colors, seg_prev, seg_curr, seg_next)
				endif

				call add(ret, text)
			endif
		elseif seg_curr.type == 'segment_group'
			" Recursively parse segment group
			let func_params = [mode, side, seg_curr.segments, level + 1]

			if has_key(seg_curr, 'colors')
				" Pass the base colors on to the child segments
				call add(func_params, seg_curr.colors)
			endif

			" Get segment group string
			let text = join(call('s:ParseSegments', func_params), '')

			" Pad on the opposite side of the divider
			let padding_right = (side == s:RIGHT_SIDE ? repeat(' ', s:PADDING) : '')
			let padding_left  = (side == s:LEFT_SIDE  ? repeat(' ', s:PADDING) : '')

			let text = s:AddDivider(padding_left . text . padding_right, side, mode, seg_curr.colors, seg_prev, seg_curr, seg_next)

			call add(ret, text)
		endif
	endfor

	return ret
endfunction " }}}
function! s:HlCreate(hl) " {{{
	" Create a short and unique highlighting group name
	" It uses the hex values of all the color properties and an attribute flag at the end
	" NONE colors are translated to NN for cterm and NNNNNN for gui colors
	let hi_group = printf('Pl%s%s%s%s%s'
		\ , (a:hl['ctermfg'] == 'NONE' ? 'NN'     : printf('%02x', a:hl['ctermfg']))
		\ , (a:hl['guifg']   == 'NONE' ? 'NNNNNN' : printf('%06x', a:hl['guifg']  ))
		\ , (a:hl['ctermbg'] == 'NONE' ? 'NN'     : printf('%02x', a:hl['ctermbg']))
		\ , (a:hl['guibg']   == 'NONE' ? 'NNNNNN' : printf('%06x', a:hl['guibg']  ))
		\ , substitute(a:hl['attr'], '\v([a-zA-Z])[a-zA-Z]*,?', '\1', 'g')
		\ )

	if ! s:HlExists(hi_group)
		let ctermbg = a:hl['ctermbg'] == 'NONE' ? 'NONE' : printf('%d', a:hl['ctermbg'])
		if (has('win32') || has('win64')) && !has('gui_running') && ctermbg != 'NONE' && ctermbg > 128
			let ctermbg -= 128
		endif
		let hi_cmd = printf('hi %s ctermfg=%s ctermbg=%s cterm=%s guifg=%s guibg=%s gui=%s'
			\ , hi_group
			\ , a:hl['ctermfg'] == 'NONE' ? 'NONE' : printf('%d', a:hl['ctermfg'])
			\ , ctermbg
			\ , a:hl['attr']
			\ , (a:hl['guifg'] == 'NONE' ? 'NONE' : printf('#%06x', a:hl['guifg']))
			\ , (a:hl['guibg'] == 'NONE' ? 'NONE' : printf('#%06x', a:hl['guibg']))
			\ , a:hl['attr']
			\ )

		exec hi_cmd

		" Add command to Pl#HL list for caching
		call add(g:Pl#HL, hi_cmd)
	endif

	" Return only the highlighting group name
	return hi_group
endfunction " }}}
function! s:HlExists(hl) " {{{
	if ! hlexists(a:hl)
		return 0
	endif

	redir => hlstatus
	silent exec 'hi' a:hl
	redir END

	return (hlstatus !~ 'cleared')
endfunction " }}}
function! s:AddDivider(text, side, mode, colors, prev, curr, next) " {{{
	let seg_prev = a:prev
	let seg_curr = a:curr
	let seg_next = a:next

	" Set default color/type for the divider
	let div_colors = get(a:colors, a:mode, a:colors['n'])
	let div_type = s:SOFT_DIVIDER

	" Define segment to compare
	let cmp_seg = a:side == s:LEFT_SIDE ? seg_next : seg_prev

	let cmp_all_colors = get(cmp_seg, 'colors', {})
	let cmp_colors = get(cmp_all_colors, a:mode, get(cmp_all_colors, 'n', {}))

	if ! empty(cmp_colors)
		" Compare the highlighting groups
		"
		" If the background color for cterm is equal, use soft divider with the current segment's highlighting
		" If not, use hard divider with a new highlighting group
		"
		" Note that if the previous/next segment is the split, a hard divider is always used
		if get(div_colors, 'ctermbg') != get(cmp_colors, 'ctermbg') || get(seg_next, 'name') ==# 'SPLIT' || get(seg_prev, 'name') ==# 'SPLIT'
			let div_type = s:HARD_DIVIDER

			" Create new highlighting group
			" Use FG = CURRENT BG, BG = CMP BG
			let div_colors['ctermfg'] = get(div_colors, 'ctermbg')
			let div_colors['guifg']   = get(div_colors, 'guibg')

			let div_colors['ctermbg'] = get(cmp_colors, 'ctermbg')
			let div_colors['guibg']   = get(cmp_colors, 'guibg')

			let div_colors['attr']    = 'NONE'
		endif
	endif

	" Prepare divider
	let divider_raw = deepcopy(g:Pl#Parser#Symbols[g:Powerline_symbols].dividers[a:side + div_type])
	let divider = Pl#Parser#ParseChars(divider_raw)

	" Don't add dividers for segments adjacent to split (unless it's a hard divider)
	if ((get(seg_next, 'name') ==# 'SPLIT' || get(seg_prev, 'name') ==# 'SPLIT') && div_type != s:HARD_DIVIDER)
		return ''
	endif

	if a:side == s:LEFT_SIDE
		" Left side
		" Divider to the right
		return printf('%%(%s%%#%s#%s%%)', a:text, s:HlCreate(div_colors), divider)
	else
		" Right side
		" Divider to the left
		return printf('%%(%%#%s#%s%s%%)', s:HlCreate(div_colors), divider, a:text)
	endif
endfunction " }}}