# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)

import itertools

from powerline.segment import gen_segment_getter, process_segment, get_fallback_segment
from powerline.lib.unicode import u, safe_unicode


def requires_segment_info(func):
	func.powerline_requires_segment_info = True
	return func


def requires_filesystem_watcher(func):
	func.powerline_requires_filesystem_watcher = True
	return func


def new_empty_segment_line():
	return {
		'left': [],
		'right': []
	}


def add_spaces_left(pl, amount, segment):
	return (' ' * amount) + segment['contents']


def add_spaces_right(pl, amount, segment):
	return segment['contents'] + (' ' * amount)


def add_spaces_center(pl, amount, segment):
	amount, remainder = divmod(amount, 2)
	return (' ' * (amount + remainder)) + segment['contents'] + (' ' * amount)


expand_functions = {
	'l': add_spaces_right,
	'r': add_spaces_left,
	'c': add_spaces_center,
}


class Theme(object):
	def __init__(self,
	             ext,
	             theme_config,
	             common_config,
	             pl,
	             get_module_attr,
	             top_theme,
	             colorscheme,
	             main_theme_config=None,
	             run_once=False,
	             shutdown_event=None):
		self.colorscheme = colorscheme
		self.dividers = theme_config['dividers']
		self.dividers = dict((
			(key, dict((k, u(v))
			for k, v in val.items()))
			for key, val in self.dividers.items()
		))
		try:
			self.cursor_space_multiplier = 1 - (theme_config['cursor_space'] / 100)
		except KeyError:
			self.cursor_space_multiplier = None
		self.cursor_columns = theme_config.get('cursor_columns')
		self.spaces = theme_config['spaces']
		self.outer_padding = int(theme_config.get('outer_padding', 1))
		self.segments = []
		self.EMPTY_SEGMENT = {
			'contents': None,
			'highlight': {'fg': False, 'bg': False, 'attrs': 0}
		}
		self.pl = pl
		theme_configs = [theme_config]
		if main_theme_config:
			theme_configs.append(main_theme_config)
		get_segment = gen_segment_getter(
			pl,
			ext,
			common_config,
			theme_configs,
			theme_config.get('default_module'),
			get_module_attr,
			top_theme
		)
		for segdict in itertools.chain((theme_config['segments'],),
		                               theme_config['segments'].get('above', ())):
			self.segments.append(new_empty_segment_line())
			for side in ['left', 'right']:
				for segment in segdict.get(side, []):
					segment = get_segment(segment, side)
					if segment:
						if not run_once:
							if segment['startup']:
								try:
									segment['startup'](pl, shutdown_event)
								except Exception as e:
									pl.error('Exception during {0} startup: {1}', segment['name'], str(e))
									continue
						self.segments[-1][side].append(segment)

	def shutdown(self):
		for line in self.segments:
			for segments in line.values():
				for segment in segments:
					try:
						segment['shutdown']()
					except TypeError:
						pass

	def get_divider(self, side='left', type='soft'):
		'''Return segment divider.'''
		return self.dividers[side][type]

	def get_spaces(self):
		return self.spaces

	def get_line_number(self):
		return len(self.segments)

	def get_segments(self, side=None, line=0, segment_info=None, mode=None):
		'''Return all segments.

		Function segments are called, and all segments get their before/after
		and ljust/rjust properties applied.

		:param int line:
			Line number for which segments should be obtained. Is counted from 
			zero (botmost line).
		'''
		for side in [side] if side else ['left', 'right']:
			parsed_segments = []
			for segment in self.segments[line][side]:
				if segment['display_condition'](self.pl, segment_info, mode):
					process_segment(
						self.pl,
						side,
						segment_info,
						parsed_segments,
						segment,
						mode,
						self.colorscheme,
					)
			for segment in parsed_segments:
				self.pl.prefix = segment['name']
				try:
					width = segment['width']
					align = segment['align']
					if width == 'auto' and segment['expand'] is None:
						segment['expand'] = expand_functions.get(align)
						if segment['expand'] is None:
							self.pl.error('Align argument must be “r”, “l” or “c”, not “{0}”', align)

					try:
						segment['contents'] = segment['before'] + u(
							segment['contents'] if segment['contents'] is not None else ''
						) + segment['after']
					except Exception as e:
						self.pl.exception('Failed to compute segment contents: {0}', str(e))
						segment['contents'] = safe_unicode(segment.get('contents'))
					# Align segment contents
					if segment['width'] and segment['width'] != 'auto':
						if segment['align'] == 'l':
							segment['contents'] = segment['contents'].ljust(segment['width'])
						elif segment['align'] == 'r':
							segment['contents'] = segment['contents'].rjust(segment['width'])
						elif segment['align'] == 'c':
							segment['contents'] = segment['contents'].center(segment['width'])
					# We need to yield a copy of the segment, or else mode-dependent
					# segment contents can’t be cached correctly e.g. when caching
					# non-current window contents for vim statuslines
					yield segment.copy()
				except Exception as e:
					self.pl.exception('Failed to compute segment: {0}', str(e))
					fallback = get_fallback_segment()
					fallback.update(side=side)
					yield fallback
