from pygame import Rect, draw
from pygame.locals import K_LEFT, K_RIGHT, K_TAB
from widget import Widget, overridable_property
from theme import ThemeProperty
from utils import blit_in_rect, frame_rect
import resource

class Label(Widget):

	align = 'l'

	def __init__(self, text, width = None, **kwds):
		Widget.__init__(self, **kwds)
		font = self.font
		lines = text.split("\n")
		tw, th = 0, 0
		for line in lines:
			w, h = font.size(line)
			tw = max(tw, w)
			th += h
		if width is not None:
			tw = width
		else:
			tw = max(1, tw)
		d = 2 * self.margin
		self.size = (tw + d, th + d)
		self.text = text

	def draw(self, surface):
		self.draw_with(surface, self.fg_color)
	
	def draw_with(self, surface, fg, bg = None):
		if bg:
			r = surface.get_rect()
			b = self.border_width
			if b:
				e = - 2 * b
				r.inflate_ip(e, e)
			surface.fill(bg, r)
		m = self.margin
		align = self.align
		width = surface.get_width()
		y = m
		lines = self.text.split("\n")
		font = self.font
		dy = font.get_linesize()
		for line in lines:
			image = font.render(line, True, fg)
			r = image.get_rect()
			r.top = y
			if align == 'l':
				r.left = m
			elif align == 'r':
				r.right = width - m
			else:
				r.centerx = width // 2
			surface.blit(image, r)
			y += dy

#---------------------------------------------------------------------------

class BaseButton(object):

	align = 'c'

	highlighted = False
	_enabled = True
	
	def __init__(self, action, enable):
		if action:
			self.action = action
		if enable:
			self.get_enabled = enable
	
	def mouse_down(self, event):
		if self.enabled:
			self.highlighted = True
		
	def mouse_drag(self, event):
		state = event in self
		if state <> self.highlighted:
			self.highlighted = state
			self.invalidate()
	
	def mouse_up(self, event):
		if event in self:
			self.highlighted = False
			if self.enabled:
				self.call_handler('action')
	
	def action(self):
		pass
	
	def get_enabled(self):
		return self._enabled
	
	def set_enabled(self, x):
		self._enabled = x
	
	enabled = property(
		lambda self: self.get_enabled(),
		lambda self, x: self.set_enabled(x)
	)

#---------------------------------------------------------------------------

class Button(BaseButton, Label):

	highlight_color = ThemeProperty('highlight_color')
	disabled_color = ThemeProperty('disabled_color')
	highlight_bg_color = ThemeProperty('highlight_bg_color')
	enabled_bg_color = ThemeProperty('enabled_bg_color')
	disabled_bg_color = ThemeProperty('disabled_bg_color')

	def __init__(self, text, action = None, enable = None, **kwds):
		BaseButton.__init__(self, action, enable)
		Label.__init__(self, text, **kwds)
	
	def draw(self, surface):
		if not self.enabled:
			fg = self.disabled_color
			bg = self.disabled_bg_color
		elif self.highlighted:
			fg = self.highlight_color
			bg = self.highlight_bg_color
		else:
			fg = self.fg_color
			bg = self.enabled_bg_color
		self.draw_with(surface, fg, bg)

#---------------------------------------------------------------------------

class ImageButton(BaseButton, Widget):

	highlight_color = ThemeProperty('highlight_color')
	
	def __init__(self, image, action = None, enable = None, **kwds):
		if isinstance(image, basestring):
			image = resource.get_image(image)
		if image:
			self.image = image
		BaseButton.__init__(self, action, enable)
		Widget.__init__(self, image.get_rect(), **kwds)

	def draw(self, surf):
		frame = surf.get_rect()
		if self.highlighted:
			surf.fill(self.highlight_color)
		image = self.image
		r = image.get_rect()
		r.center = frame.center
		surf.blit(image, r)

#---------------------------------------------------------------------------

class ValueView(Widget):

	format = "%s"
	align = 'l'
	attribute = None
	model = None
	
	def __init__(self, width = 100, **kwds):
		Widget.__init__(self, **kwds)
		self.set_size_for_text(width)
	
	def draw(self, surf):
		value = self.get_value()
		text = self.format_value(value)
		buf = self.font.render(text, True, self.fg_color)
		frame = surf.get_rect()
		blit_in_rect(surf, buf, frame, self.align, self.margin)
	
	def format_value(self, value):
		if value is not None:
			return self.format % value
		else:
			return ""
	
	def get_value(self):
		return getattr(self.model, self.attribute)

	def set_value(self, x):
		setattr(self.model, self.attribute, x)

#---------------------------------------------------------------------------

class TextField(Widget):

	upper = False
	tab_stop = True

	def __init__(self, width, upper = None, **kwds):
		Widget.__init__(self, **kwds)
		self.set_size_for_text(width)
		if upper is not None:
			self.upper = upper
		self.text = ""
		self.insertion_point = None
	
	def get_text(self):
		return self.text
	
	def set_text(self, text):
		self.text = text
	
	value = property(get_text, set_text)
	
	def draw(self, surface):
		frame = self.get_margin_rect()
		fg = self.fg_color
		font = self.font
		focused = self.has_focus()
		text, i = self.get_text_and_insertion_point()
		if focused and i is None:
			surface.fill(self.sel_color, frame)
		image = font.render(text, True, fg)
		surface.blit(image, frame)
		if focused and i is not None:
			x, h = font.size(text[:i])
			x += frame.left
			y = frame.top
			draw.line(surface, fg, (x, y), (x, y + h - 1))
	
	def key_down(self, event):
		k = event.key
		if k == K_LEFT:
			self.move_insertion_point(-1)
			return
		if k == K_RIGHT:
			self.move_insertion_point(1)
			return
		if k == K_TAB:
			self.attention_lost()
			self.tab_to_next()
			return
		try:
			c = event.unicode
		except ValueError:
			c = ""
		if self.insert_char(c) == 'pass':
			self.call_parent_handler('key_down', event)
	
	def get_text_and_insertion_point(self):
		text = self.get_text()
		i = self.insertion_point
		if i is not None:
			i = max(0, min(i, len(text)))
		return text, i
	
	def move_insertion_point(self, d):
		text, i = self.get_text_and_insertion_point()
		if i is None:
			if d > 0:
				i = len(text)
			else:
				i = 0
		else:
			i = max(0, min(i + d, len(text)))
		self.insertion_point = i
	
	def insert_char(self, c):
		if self.upper:
			c = c.upper()
		if c <= "\x7f":
			if c == "\x08" or c == "\x7f":
				text, i = self.get_text_and_insertion_point()
				if i is None:
					text = ""
					i = 0
				else:
					text = text[:i-1] + text[i:]
					i -= 1
				self.change_text(text)
				self.insertion_point = i
				return
			elif c == "\r" or c == "\x03":
				return self.call_handler('enter_action')
			elif c == "\x1b":
				return self.call_handler('escape_action')
			elif c >= "\x20":
				if self.allow_char(c):
					text, i = self.get_text_and_insertion_point()
					if i is None:
						text = c
						i = 1
					else:
						text = text[:i] + c + text[i:]
						i += 1
					self.change_text(text)
					self.insertion_point = i
					return
		return 'pass'
	
	def allow_char(self, c):
		return True

	def mouse_down(self, e):
		self.focus()
		x, y = e.local
		text = self.get_text()
		font = self.font
		n = len(text)
		def width(i):
			return font.size(text[:i])[0]
		i1 = 0
		i2 = len(text)
		x1 = 0
		x2 = width(i2)
		while i2 - i1 > 1:
			i3 = (i1 + i2) // 2
			x3 = width(i3)
			if x > x3:
				i1, x1 = i3, x3
			else:
				i2, x2 = i3, x3
		if x - x1 > (x2 - x1) // 2:
			i = i2
		else:
			i = i1
		self.insertion_point = i

	def change_text(self, text):
		self.set_text(text)
		self.call_handler('change_action')

#---------------------------------------------------------------------------

class ValueEditor(TextField):

	empty = NotImplemented
	format = "%s"

	def __init__(self, width = None, format = None, min = None, max = None,
			empty = NotImplemented, attribute = None, model = None, **kwds):
		if format:
			self.format = format
		self.empty = empty
		self.attribute = attribute
		self.model = model
		self.min = min
		self.max = max
		self.editing = False
		if width is None:
			w1 = w2 = ""
			if min is not None:
				w1 = self.format_value(min)
			if max is not None:
				w2 = self.format_value(max)
			if w2:
				if len(w1) > len(w2):
					width = w1
				else:
					width = w2
		if width is None:
			width = 100
		TextField.__init__(self, width, **kwds)

	def format_value(self, x):
		if x == self.empty:
			return ""
		else:
			return self.format % x

	def get_text(self):
		if self.editing:
			return self.text
		else:
			value = getattr(self.model, self.attribute)
			return self.format_value(value)
	
	def set_text(self, text):
		self.editing = True
		self.text = text

	def enter_action(self):
		if self.editing:
			self.commit()
		else:
			return 'pass'
	
	def escape_action(self):
		if self.editing:
			self.editing = False
			self.insertion_point = None
		else:
			return 'pass'
	
	def attention_lost(self):
		self.commit()
	
	def commit(self):
		#print "ValueEditor.commit:", self ###
		if self.editing:
			#print "...updating value" ###
			text = self.text
			if text:
				try:
					value = self.type(text)
				except ValueError:
					return
				if self.min is not None:
					value = max(self.min, value)
				if self.max is not None:
					value = min(self.max, value)
			else:
				value = self.empty
				if value is NotImplemented:
					return
			setattr(self.model, self.attribute, value)
			#print "...leaving edit mode" ###
			self.editing = False
			self.insertion_point = None
		else:
			self.insertion_point = None

#---------------------------------------------------------------------------

class TextEditor(ValueEditor):
	type = str

class IntEditor(ValueEditor):
	type = int

class FloatEditor(ValueEditor):
	type = float

#---------------------------------------------------------------------------

class ValueField(ValueEditor):

	_value = None
	
	def __init__(self, *args, **kwds):
		ValueEditor.__init__(self, attribute = '_value', model = self,
			*args, **kwds)
	
	def get_value(self):
		#print "ValueField.get_value:", self ###
		self.commit()
		return self._value
	
	def set_value(self, x):
		self._value = x
		self.editing = False
	
	value = property(get_value, set_value)

#---------------------------------------------------------------------------

class IntField(ValueField):
	type = int

class FloatField(ValueField):
	type = float

#---------------------------------------------------------------------------

class BoolEditor(Widget):

	default_size = (16, 16)
	margin = 4
	border_width = 1
	check_mark_tweak = 2

	def __init__(self, attribute, model, **kwds):
		self.attribute = attribute
		self.model = model
		Widget.__init__(self, Rect((0, 0), self.default_size), **kwds)
	
	def draw(self, surf):
		if self.get_checked():
			r = self.get_margin_rect()
			fg = self.fg_color
			d = self.check_mark_tweak
			p1 = (r.left, r.centery - d)
			p2 = (r.centerx - d, r.bottom)
			p3 = (r.right, r.top - d)
			draw.aalines(surf, fg, False, [p1, p2, p3])
	
	def mouse_down(self, e):
		self.set_checked(not self.get_checked())
	
	def get_checked(self):
		return getattr(self.model, self.attribute)
	
	def set_checked(self, x):
		setattr(self.model, self.attribute, x)

#---------------------------------------------------------------------------

class RadioButton(BoolEditor):

	value = None
	
	def get_checked(self):
		return getattr(self.model, self.attribute) == self.value
	
	def mouse_down(self, e):
		setattr(self.model, self.attribute, self.value)

#---------------------------------------------------------------------------

class BoolField(BoolEditor):

	_value = False

	def __init__(self, **kwds):
		BoolEditor.__init__(self, '_value', self, **kwds)
	
	def get_value(self):
		return self._value

	def set_value(self, x):
		self._value = x

	value = property(get_value, set_value)

#---------------------------------------------------------------------------

class RowOrColumn(Widget):

	def __init__(self, size, items, kwds):
		align = kwds.pop('align', 'c')
		spacing = kwds.pop('spacing', 10)
		expand = kwds.pop('expand', None)
		if isinstance(expand, int):
			expand = items[expand]
		#if kwds:
		#	raise TypeError("Unexpected keyword arguments to Row or Column: %s"
		#		% kwds.keys())
		Widget.__init__(self, **kwds)
		#print "albow.controls: RowOrColumn: size =", size, "expand =", expand ###
		d = self.d
		longways = self.longways
		crossways = self.crossways
		axis = self.axis
		k, attr2, attr3 = self.align_map[align]
		w = 0
		length = 0
		if isinstance(expand, int):
			expand = items[expand]
		elif not expand:
			expand = items[-1]
		move = ''
		for item in items:
			r = item.rect
			w = max(w, getattr(r, crossways))
			if item is expand:
				item.set_resizing(axis, 's')
				move = 'm'
			else:	
				item.set_resizing(axis, move)
				length += getattr(r, longways)
		if size is not None:
			n = len(items)
			if n > 1:
				length += spacing * (n - 1)
			#print "albow.controls: expanding size from", length, "to", size ###
			setattr(expand.rect, longways, max(1, size - length))
		h = w * k // 2
		m = self.margin
		px = h * d[1] + m
		py = h * d[0] + m
		sx = spacing * d[0]
		sy = spacing * d[1]
		for item in items:
			setattr(item.rect, attr2, (px, py))
			self.add(item)
			p = getattr(item.rect, attr3)
			px = p[0] + sx
			py = p[1] + sy
		self.shrink_wrap()

class Row(RowOrColumn):

	d = (1, 0)
	axis = 'h'
	longways = 'width'
	crossways = 'height'
	align_map = {
		't': (0, 'topleft', 'topright'),
		'c': (1, 'midleft', 'midright'),
		'b': (2, 'bottomleft', 'bottomright'),
	}

	def __init__(self, items, width = None, **kwds):
		"""
		Row(items, align = alignment, spacing = 10, width = None, expand = None)
		align = 't', 'c' or 'b'
		"""
		RowOrColumn.__init__(self, width, items, kwds)

class Column(RowOrColumn):

	d = (0, 1)
	axis = 'v'
	longways = 'height'
	crossways = 'width'
	align_map = {
		'l': (0, 'topleft', 'bottomleft'),
		'c': (1, 'midtop', 'midbottom'),
		'r': (2, 'topright', 'bottomright'),
	}

	def __init__(self, items, height = None, **kwds):
		"""
		Column(items, align = alignment, spacing = 10, height = None, expand = None)
		align = 'l', 'c' or 'r'
		"""
		RowOrColumn.__init__(self, height, items, kwds)

#---------------------------------------------------------------------------

class Grid(Widget):

	def __init__(self, rows, row_spacing = 10, column_spacing = 10, **kwds):
		col_widths = [0] * len(rows[0])
		row_heights = [0] * len(rows)
		for j, row in enumerate(rows):
			for i, widget in enumerate(row):
				if widget:
					col_widths[i] = max(col_widths[i], widget.width)
					row_heights[j] = max(row_heights[j], widget.height)
		row_top = 0
		for j, row in enumerate(rows):
			h = row_heights[j]
			y = row_top + h // 2
			col_left = 0
			for i, widget in enumerate(row):
				if widget:
					w = col_widths[i]
					x = col_left
					widget.midleft = (x, y)
				col_left += w + column_spacing
			row_top += h + row_spacing
		width = max(1, col_left - column_spacing)
		height = max(1, row_top - row_spacing)
		r = Rect(0, 0, width, height)
		#print "albow.controls.Grid: r =", r ###
		#print "...col_widths =", col_widths ###
		#print "...row_heights =", row_heights ###
		Widget.__init__(self, r, **kwds)
		self.add(rows)

#---------------------------------------------------------------------------

class Frame(Widget):
	#  margin  int        spacing between border and widget

	border_width = 1
	margin = 2

	def __init__(self, client, border_spacing = None, **kwds):
		Widget.__init__(self, **kwds)
		self.client = client
		if border_spacing is not None:
			self.margin = self.border_width + border_spacing
		d = self.margin
		w, h = client.size
		self.size = (w + 2 * d, h + 2 * d)
		client.topleft = (d, d)
		self.add(client)

#---------------------------------------------------------------------------

class Image(Widget):
	#  image   Image to display
	
	image = overridable_property('image')
	
	def get_image(self):
		return self._image
	
	def set_image(self, x):
		self._image = x

	def __init__(self, image = None, rect = None, **kwds):
		Widget.__init__(self, rect, **kwds)
		if image:
			w, h = image.get_size()
			d = 2 * self.margin
			self.size = w + d, h + d
			self._image = image
	
	def draw(self, surf):
		frame = self.get_margin_rect()
		surf.blit(self.image, frame)
