"""a html renderer"""
import htmllib
import re
import pygame
from pygame.locals import *

from pgu import gui

_amap = {'left':-1,'right':1,'center':0,None:None,'':None,}
_vamap = {'top':-1,'bottom':1,'center':0,None:None,'':None,}

class _dummy:
	pass

class _flush:
	def __init__(self):
		self.style = _dummy()
		self.style.font = None
		self.style.color = None
		self.cls = None
	def add(self,w): pass
	def space(self,v): pass
	
class _hr(gui.Color):
	def __init__(self,h):
		gui.Color.__init__(self,(0,0,0),style={'height':1,'padding':h})
	def resize(self,width=None,height=None):
		self.rect.h = self.style.height
		#if width != None: self.rect.w = width
		#else: self.rect.w = 1
		xt,xr,xb,xl = self.getspacing()
		
		self.rect.w = max(1,width,self.container.rect.w-(xl+xr))
		
		#print self.rect
		#self.rect.w = 1

class _html(htmllib.HTMLParser):
	def init(self,doc,font,color,_globals,_locals):
		self.mystack = []
		self.document = doc
		self.myopen('document',self.document)
		
		self.myfont = self.font = font
		self.mycolor = self.color = color
		
		self.form = None
		
		self._globals = _globals
		self._locals = _locals
		
	def myopen(self,type_,w):
	
		self.mystack.append((type_,w))
		self.type,self.item = type_,w
		
		self.font = self.item.style.font
		self.color = self.item.style.color
		
		if not self.font: self.font = self.myfont
		if not self.color: self.color = self.mycolor
		
	def myclose(self,type_):
		t = None
		self.mydone()
		while t != type_:
			#if len(self.mystack)==0: return
			t,w = self.mystack.pop()
		t,w = self.mystack.pop()
		self.myopen(t,w)
		
	def myback(self,type_):
		if type(type_) == str: type_ = [type_,]
		self.mydone()
		#print 'myback',type_
		t = None
		while t not in type_:
			#if len(self.mystack)==0: return
			t,w = self.mystack.pop()
		self.myopen(t,w)
		
	def mydone(self):
		#clearing out the last </p>
		if not hasattr(self.item,'layout'): return 
		if len(self.item.layout._widgets) == 0: return 
		w = self.item.layout._widgets[-1]
		if type(w) == tuple:
			del self.item.layout._widgets[-1]

		
	def start_b(self,attrs): self.font.set_bold(1)
	def end_b(self): self.font.set_bold(0)
	def start_i(self,attrs): self.font.set_italic(1)
	def end_i(self): self.font.set_italic(0)
	def start_u(self,attrs): self.font.set_underline(1)
	def end_u(self): self.font.set_underline(0)
	def start_br(self,attrs): self.do_br(attrs)
	def do_br(self,attrs): self.item.br(self.font.size(" ")[1])
	def attrs_to_map(self,attrs):
		k = None
		r = {}
		for k,v in attrs: r[k] = v
		return r
		
	def map_to_params(self,r):
		anum = re.compile("\D")
		
		params = {'style':{}}
		style = params['style']
		
		if 'bgcolor' in r: style['background'] = pygame.Color(r['bgcolor'])
		if 'background' in r: style['background'] = pygame.image.load(r['background'])
		if 'border' in r: style['border'] = int(r['border'])
			
		for k in ['width','height','colspan','rowspan','size']:
			if k in r: params[k] = int(anum.sub("",r[k]))
			
		for k in ['name','value']:
			if k in r: params[k] = r[k]
		
		if 'class' in r: params['cls'] = r['class']
		
		if 'align' in r: 
			params['align'] = _amap[r['align']]
		if 'valign' in r:
			params['valign'] = _vamap[r['valign']]
			
		if 'style' in r:
			for st in r['style'].split(";"):
				#print st
				if ":" in st:
					#print st.split(":")
					k,v = st.split(":")
					k = k.replace("-","_")
					k = k.replace(" ","")
					v = v.replace(" ","")
					if k == 'color' or k == 'border_color' or k == 'background':
						v = pygame.Color(v)
					else:
						v = int(anum.sub("",v))
					style[k] = v
		return params
		
	def map_to_connects(self,e,r):
		for k,evt in [('onclick',gui.CLICK),('onchange',gui.CHANGE)]: #blah blah blah
			
			if k in r:
				#print k,r[k]
				e.connect(evt,self.myexec,(e,r[k]))

	def start_p(self,attrs):
		r = self.attrs_to_map(attrs)
		align = r.get("align","left")
		
		self.check_p()
		self.item.block(_amap[align])
		
	def check_p(self):
		if len(self.item.layout._widgets) == 0: return
		if type(self.item.layout._widgets[-1]) == tuple:
			w,h = self.item.layout._widgets[-1]
			if w == 0: return
		self.do_br(None)
		
	def end_p(self):
		#print 'end p'
		self.check_p()
		
		
	def start_block(self,t,attrs,align=-1):
		r = self.attrs_to_map(attrs)
		params = self.map_to_params(r)
		if 'cls' in params: params['cls'] = t+"."+params['cls']
		else: params['cls'] = t
		b = gui.Document(**params)
		if 'align' in params:
			align = params['align']
		self.item.block(align)
		self.item.add(b)
		self.myopen(t,b)

		
				
	def end_block(self,t):
		self.myclose(t)
		self.item.block(-1)
		
	def start_div(self,attrs): self.start_block('div',attrs)
	def end_div(self): self.end_block('div')
	def start_center(self,attrs): self.start_block('div',attrs,0)
	def end_center(self): self.end_block('div')
	
	def start_h1(self,attrs): self.start_block('h1',attrs)
	def end_h1(self): self.end_block('h1')
	def start_h2(self,attrs): self.start_block('h2',attrs)
	def end_h2(self): self.end_block('h2')
	def start_h3(self,attrs): self.start_block('h3',attrs)
	def end_h3(self): self.end_block('h3')
	def start_h4(self,attrs): self.start_block('h4',attrs)
	def end_h4(self): self.end_block('h4')
	def start_h5(self,attrs): self.start_block('h5',attrs)
	def end_h5(self): self.end_block('h5')
	def start_h6(self,attrs): self.start_block('h6',attrs)
	def end_h6(self): self.end_block('h6')

	def start_ul(self,attrs): self.start_block('ul',attrs)
	def end_ul(self): self.end_block('ul')
	def start_ol(self,attrs): 
		self.start_block('ol',attrs)
		self.item.counter = 0
	def end_ol(self): self.end_block('ol')
	def start_li(self,attrs): 
		self.myback(['ul','ol'])
		cur = self.item
		self.start_block('li',attrs)
		if hasattr(cur,'counter'):
			cur.counter += 1
			self.handle_data("%d. "%cur.counter)
		else:
			self.handle_data("- ")
	#def end_li(self): self.end_block('li') #this isn't needed because of how the parser works

	def start_pre(self,attrs): self.start_block('pre',attrs)
	def end_pre(self): self.end_block('pre')
	def start_code(self,attrs): self.start_block('code',attrs)
	def end_code(self): self.end_block('code')
			
	def start_table(self,attrs):
		r = self.attrs_to_map(attrs)
		params = self.map_to_params(r)
		
		align = r.get("align","left")
		self.item.block(_amap[align])

		t = gui.Table(**params)
		self.item.add(t)
		
		self.myopen('table',t)
		
	def start_tr(self,attrs):
		self.myback('table')
		self.item.tr()
		
	def _start_td(self,t,attrs):
		r = self.attrs_to_map(attrs)
		params = self.map_to_params(r)
		if 'cls' in params: params['cls'] = t+"."+params['cls']
		else: params['cls'] = t
		b = gui.Document(cls=t)
		
		self.myback('table')
		self.item.td(b,**params)
		self.myopen(t,b)
	
		self.font = self.item.style.font
		self.color = self.item.style.color
		
	def start_td(self,attrs):
		self._start_td('td',attrs)
	
	def start_th(self,attrs):
		self._start_td('th',attrs)
		
	def end_table(self):
		self.myclose('table')
		self.item.block(-1)
		
	def start_form(self,attrs):
		r = self.attrs_to_map(attrs)
		e = self.form = gui.Form()
		e.groups = {}
		
		self._locals[r.get('id',None)] = e
		
	def start_input(self,attrs):
		r = self.attrs_to_map(attrs)
		params = self.map_to_params(r) #why bother
		#params = {}
		
		type_,name,value = r.get('type','text'),r.get('name',None),r.get('value',None)
		f = self.form
		if type_ == 'text':
			e = gui.Input(**params)
			self.map_to_connects(e,r)
			self.item.add(e)
		elif type_ == 'radio':
			if name not in f.groups:
				f.groups[name] = gui.Group(name=name)
			g = f.groups[name]
			del params['name']
			e = gui.Radio(group=g,**params)
			self.map_to_connects(e,r)
			self.item.add(e)
			if 'checked' in r: g.value = value
		elif type_ == 'checkbox':
			if name not in f.groups:
				f.groups[name] = gui.Group(name=name)
			g = f.groups[name]
			del params['name']
			e = gui.Checkbox(group=g,**params)
			self.map_to_connects(e,r)
			self.item.add(e)
			if 'checked' in r: g.value = value

		elif type_ == 'button':
			e = gui.Button(**params)
			self.map_to_connects(e,r)
			self.item.add(e)
		elif type_ == 'submit':
			e = gui.Button(**params)
			self.map_to_connects(e,r)
			self.item.add(e)
			
		self._locals[r.get('id',None)] = e

	def start_object(self,attrs):
		r = self.attrs_to_map(attrs)
		params = self.map_to_params(r)
		code = "e = %s(**params)"%r['type']
		#print code
		exec(code)
		#print e
		#print e.style.width,e.style.height
		self.map_to_connects(e,r)
		self.item.add(e)
		
		self._locals[r.get('id',None)] = e
	
	def start_select(self,attrs):
		r = self.attrs_to_map(attrs)
		params = {}
		
		name,value = r.get('name',None),r.get('value',None)
		e = gui.Select(name=name,value=value,**params)
		self.map_to_connects(e,r)
		self.item.add(e)
		self.myopen('select',e)
		
	def start_option(self,attrs):
		r = self.attrs_to_map(attrs)
		params = {} #style = self.map_to_style(r)
		
		self.myback('select')
		e = gui.Document(**params)
		self.item.add(e,value=r.get('value',None))
		self.myopen('option',e)
		
		
	def end_select(self):
		self.myclose('select')
		
	def start_hr(self,attrs):
		self.do_hr(attrs)
	def do_hr(self,attrs):
		h = self.font.size(" ")[1]/2

		self.item.block(-1)
		self.item.add(_hr(h))
		self.item.block(-1)
		
	def anchor_begin(self,href,name,type_):
		pass

	def anchor_end(self):
		pass
		
	def start_title(self,attrs): self.myopen('title',_flush())
	def end_title(self): self.myclose('title')
			
	def myexec(self,value):
		w,code = value
		g = self._globals
		l = self._locals
		l['self'] = w
		exec(code,g,l)
		
	def handle_image(self,src,alt,ismap,align,width,height):
		try:
			w = gui.Image(pygame.image.load(src))
			if align != '':
				self.item.add(w,_amap[align])
			else:
				self.item.add(w)
		except:
			print 'handle_image: missing %s'%src
				
	def handle_data(self,txt):
		if self.type == 'table': return 
		elif self.type in ('pre','code'): 
			txt = txt.replace("\t","        ")
			ss = txt.split("\n")
			if ss[-1] == "": del ss[-1]
			for sentence in ss:
				img = self.font.render(sentence,1,self.color)
				w = gui.Image(img)
				self.item.add(w)
				self.item.block(-1)
			return

		txt = re.compile("^[\t\r\n]+").sub("",txt)
		txt = re.compile("[\t\r\n]+$").sub("",txt)
		
		tst = re.compile("[\t\r\n]+").sub("",txt)
		if tst == "": return
		
		txt = re.compile("\s+").sub(" ",txt)
		if txt == "": return
		
		if txt == " ":
			self.item.space(self.font.size(" "))
			return
		
		for word in txt.split(" "):
			word = word.replace(chr(160)," ") #&nbsp;
			#print self.item.cls
			w = gui.Image(self.font.render(word,1,self.color))
			self.item.add(w)
			self.item.space(self.font.size(" "))
			

class HTML(gui.Document):
	def __init__(self,data,globals=None,locals=None,**params):
		gui.Document.__init__(self,**params)
		
		_globals,_locals = globals,locals
		
		if _globals == None: _globals = {}
		if _locals == None: _locals = {}
		self._globals = _globals
		self._locals = _locals
		
		#font = gui.theme.get("label","","font")
		p = _html(htmllib.AS_IS,0)
		p.init(self,self.style.font,self.style.color,_globals,_locals)
		p.feed(data) 
		p.close() 
		p.mydone()
		
		
	def __getitem__(self,k):
		return self._locals[k]

def render(font,rect,text,aa,color,bgcolor=(0,0,0,0)):
	"""render some html"""
	fnt,r,txt,a,fg,bg = font,rect,text,aa,color,bgcolor
	
	e = HTML(txt,font=fnt,color=fg)
	e.resize(width=rect.w)
	s = pygame.Surface((e.rect.w,e.rect.h),SWSURFACE|SRCALPHA,32)
	s.fill(bg)
	e.paint(s)
	
	return s

def rendertrim(font,rect,text,aa,color,bgcolor=(0,0,0,0)):
	"""render html, and make sure to trim the size"""
	fnt,r,txt,a,fg,bg = font,rect,text,aa,color,bgcolor
	#print r
	w = HTML(txt,font=fnt,color=fg)
	w.resize(width=rect.w)
	s = pygame.Surface((w.rect.w,w.rect.h),SWSURFACE|SRCALPHA,32)
	s.fill(bg)
	w.paint(s)
	
	minx,miny,maxx,maxy = 1024,1024,-1024,-1024
	for e in w.layout.widgets:
		x,y,w,h = e.rect.x,e.rect.y,e.rect.w,e.rect.h
		minx = min(minx,x)
		miny = min(miny,y)
		x,y = x+w,y+h
		maxx = max(maxx,x)
		maxy = max(maxy,y)
		
	r = pygame.Rect(minx,miny,maxx-minx,maxy-miny)
		
	return s.subsurface((r))

	
def write(s,font,rect,text,aa=0,color=(0,0,0)):
	"""write html to a surface"""
	fnt,r,txt,a,fg = font,rect,text,aa,color

	e = HTML(txt)
	
	e.resize(width=rect.w)
	s = s.subsurface(rect)
	e.paint(s)
	
# vim: set filetype=python sts=4 sw=4 noet si :
