# -*- coding: utf-8 -*-
#######################################################################################
#   Plinn - http://plinn.org                                                          #
#   Copyright (C) 2005-2007  Benoît PIN <benoit.pin@ensmp.fr>                         #
#                                                                                     #
#   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.   #
#######################################################################################

from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from Products.CMFCore.permissions import View, ModifyPortalContent
from Products.CMFCore.utils import getToolByName
from Products.CMFDefault.Document import Document
from OFS.PropertyManager import PropertyManager
from OFS.Folder import Folder
from OFS.Image import File, cookId
from zope.component.factory import Factory
from zope.interface import implements
from Products.Photo import Photo
from Products.Plinn.utils import makeValidId
from interfaces import IPlinnDocument
from cStringIO import StringIO
from sets import Set
import xml.dom.minidom as minidom
import re

imgPattern = re.compile('<img(.*?)>', re.IGNORECASE)
imgWidthPattern = re.compile('style\s*=\s*".*width\s*:\s*([0-9]+)px')
imgHeightPattern = re.compile('style\s*=\s*".*height\s*:\s*([0-9]+)px')
imgSrcPattern = re.compile('src\s*=\s*"(.*)"')

imgOrLinkPattern = re.compile('<img(.*?)src(.*?)=(.*?)"(?P<src>(.*?))"(.*?)>|<a(.*?)href(.*?)=(.*?)"(?P<href>(.*?))"(.*?)>', re.IGNORECASE)
EMPTY_PLINN_DOCUMENT = '<plinn><rectangle width="800" height="600" elementKey="DIV_ELEMENT" ddOptions="2" ratio="undefined" visibility="visible"><upperLeftCorner><point x="0" y="0"/></upperLeftCorner><rawData/></rectangle></plinn>'


def addPlinnDocument(self, id, title='', description='', text=''):
	""" Add a Plinn Document """
	o = PlinnDocument(id, title, description, text)
	self._setObject(id,o)

class PlinnDocument(Document) :
	""" Plinn document - WYSIWYG editor
		based on XML and javascript
	"""
	implements(IPlinnDocument)
	
	security = ClassSecurityInfo()
	
	_cookedTexts = {}
	
	def __init__(self, id, title='', description='', text='') :
		self.attachments = Folder('attachments')
		Document.__init__(self, id, title=title, description=description, text_format='html', text=text)
	
	security.declareProtected(View, 'EditableBody')
	def EditableBody(self, mergeLayers=True):
		""" Transforms XML to HTML """
		
		if self.text :
			if not self._cookedTexts.has_key(self.absolute_url()) :
				plinnElement = minidom.parseString(self.text).documentElement
				
				htmlDom = minidom.parseString('<div class="plinn_document"/>')
				htmlDomDoc = htmlDom.documentElement
				
				self._transformRectangles(plinnElement, htmlDomDoc)
				firstChildStyle = htmlDomDoc.firstChild.getAttribute('style')
				htmlDomDoc.setAttribute('style', firstChildStyle.replace('absolute', 'relative'))
				
				if mergeLayers :
					mergedDom = minidom.parseString('<div class="plinn_document"/>')
					mergedDomDoc = mergedDom.documentElement
					for layer in htmlDomDoc.childNodes :
						for foreignchild in layer.childNodes :
							child = mergedDom.importNode(foreignchild, True)
							mergedDomDoc.appendChild(child)
	
					mergedDomDoc.setAttribute('style', htmlDomDoc.getAttribute('style'))
					htmlDom = mergedDom
				
				htmlText = htmlDom.toprettyxml().replace('&lt;', '<').replace('&gt;', '>').replace('&quot;', '"').replace('&amp;', '&')
				htmlText = htmlText.encode('utf8')
				htmlText = htmlText.split('\n', 1)[1]
				
				htmlText = imgOrLinkPattern.sub(self._convertSrcOrHref, htmlText)
				self._cookedTexts[self.absolute_url()] = htmlText
				return htmlText
			else :
				return self._cookedTexts[self.absolute_url()]
		else :
			return ''
	
	def _convertSrcOrHref(self, m) :
		dict = m.groupdict()
		if dict['src'] :
			tag = m.group().replace(dict['src'], self._genAbsoluteUrl(dict['src']))
			if not tag.endswith('/>') :
				tag = tag[:-1] + '/>'
			return tag
		elif dict['href'] :
			return m.group().replace(dict['href'], self._genAbsoluteUrl(dict['href']))
		else:
			return m.group()

	def _genAbsoluteUrl(self, relUrl) :
		if relUrl.find('attachments/') >=0 :
			return self.absolute_url() + '/' + relUrl[relUrl.rindex('attachments/'):]
		else :
			return relUrl

	
	security.declareProtected(ModifyPortalContent, 'XMLBody')
	def XMLBody(self, REQUEST=None) :
		""" return raw xml text """
		
		if REQUEST is not None :
			RESPONSE = REQUEST['RESPONSE']
			RESPONSE.setHeader('content-type', 'text/xml; charset=utf-8')
			
			manager = getToolByName(self, 'caching_policy_manager', None)
			if manager is not None:
				view_name = 'XMLBody'
				headers = manager.getHTTPCachingHeaders(
								  self, view_name, {}
								  )
				
				for key, value in headers:
					if key == 'ETag':
						RESPONSE.setHeader(key, value, literal=1)
					else:
						RESPONSE.setHeader(key, value)
				if headers:
					RESPONSE.setHeader('X-Cache-Headers-Set-By',
									   'CachingPolicyManager: %s' %
									   '/'.join(manager.getPhysicalPath()))


		return Document.EditableBody(self) or EMPTY_PLINN_DOCUMENT
		
	
	security.declareProtected(ModifyPortalContent, 'addAttachment')
	def addAttachment(self, file, formId) :
		""" Add attachment """
		id, title = cookId('', '', file)
		
		id = makeValidId(self.attachments, id)
		
		if formId == 'ImageUploadForm':
			fileOb = Photo(id, title, file, thumb_height=300, thumb_width=300)
		else :
			fileOb = File(id, title, '')
			fileOb.manage_upload(file)

		self.attachments._setObject(id, fileOb)
		fileOb = getattr(self.attachments, id)
		return fileOb


	def _transformRectangles(self, inNode, outNode) :

		for node in [ node for node in inNode.childNodes if node.nodeName == 'rectangle' ] :
			if node.getAttribute('visibility') == 'hidden' :
				continue

			divRect = outNode.ownerDocument.createElement('div')
			outNode.appendChild(divRect)

			styleAttr = 'position:absolute'
			styleAttr += ';width:%spx'	% node.getAttribute('width')
			styleAttr += ';height:%spx' % node.getAttribute('height')
			
			for subNode in node.childNodes :
				if subNode.nodeName == 'upperLeftCorner' :
					for point in subNode.childNodes :
						if point.nodeName == 'point' :
							styleAttr += ';left:%spx'	% point.getAttribute('x')
							styleAttr += ';top:%spx'	% point.getAttribute('y')
							divRect.setAttribute('style', styleAttr)
							break

				elif subNode.nodeName == 'rawData' :
					rawData = subNode.firstChild
					if rawData :
						textNode = outNode.ownerDocument.createTextNode(self.getElementTransform(node.getAttribute('elementKey'))(node, rawData.nodeValue))
						divRect.appendChild(textNode)

			self._transformRectangles(node, divRect)
	

	security.declarePrivate('renderImg')
	def renderImg(self, node, raw) :
		width = int(node.getAttribute('width'))
		height = int(node.getAttribute('height'))
		
		photoId = raw.split('/')[-2]
		photo = self._resizePhoto(photoId, width, height)

		alt = 'image'
		return '<img src="%(src)s/getThumbnail" width="%(width)s" height="%(height)s" alt="%(alt)s" />' % \
			{'src' : photo.absolute_url(), 'width' : width, 'height' : height, 'alt' : alt}
	

	security.declarePrivate('renderEpozImg')
	def renderEpozImg(self, node, raw):
		for img in imgPattern.findall(raw) :
			width = imgWidthPattern.findall(img)
			if width : width = int(width[0])
			
			height = imgHeightPattern.findall(img)
			if height : height = int(height[0])
			
			if not (width or height) : continue # default size
			
			photoId = imgSrcPattern.findall(img)[0].split('/')[-2]
			self._resizePhoto(photoId, width, height)

		return raw
	

	def _resizePhoto(self, photoId, width, height):
		photo = getattr(self.attachments, photoId)
		
		nts	 = [width, height]
		landscape = width > height
		if landscape :
			nts.reverse()
			
		thumbSize = {'width': photo.thumb_width, 'height': photo.thumb_height}

		if thumbSize['width'] != nts[0] or thumbSize['height'] != nts[1] > 1 :
			photo.manage_editProperties(thumb_width=nts[0], thumb_height=nts[1])

		return photo
	

	security.declarePrivate('getElementTransform')
	def getElementTransform(self, elementKey) :
		transforms = {'IMG_ELEMENT': self.renderImg,
					  'EPOZ_ELEMENT': self.renderEpozImg}
		return transforms.get(elementKey, lambda node, raw : raw)

	def _edit(self, text):
		""" Edit the Document and cook the body.
		"""
		Document._edit(self, text)
		self._removeUnusedAttachments()
		self._cookedTexts = {}

	
	def _removeUnusedAttachments(self) :
		if not(self.attachments.objectIds() and self.XMLBody()) : return
		
		reAttachments = re.compile('|'.join( [r'\b%s\b' % id for id in self.attachments.objectIds()] ))
		xml = self.XMLBody()
		attSet = Set(self.attachments.objectIds())
		useSet = Set(reAttachments.findall(xml))
		self.attachments.manage_delObjects([att for att in attSet - useSet])


InitializeClass(PlinnDocument)
PlinnDocumentFactory = Factory(PlinnDocument)