# -*- coding: utf-8 -*-
# (c) 2003 Centre de Recherche en Informatique ENSMP Fontainebleau <http://cri.ensmp.fr>
# (c) 2003 Benoît PIN <mailto:pin@cri.ensmp.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as published
# by the Free Software Foundation.
#
# 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., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.


from Products.CMFCore.PortalFolder import PortalFolder
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Permission import Permission
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.permissions import View, ModifyPortalContent, AccessContentsInformation
from random import randrange
from DateTime import DateTime
from types import InstanceType, StringType, DictType
from MosaicBlockInformation import RuleError

from OFS.Moniker import Moniker, loadMoniker
from OFS.CopySupport import _cb_encode, _cb_decode#, cookie_path
from MosaicBlockInformation import RuleError


class MosaicBlock(PortalFolder) :
	""" Block class for 'Mosaic Document' """

	meta_type = 'Mosaic Block'
	_properties = ({'id' : 'xpos', 'type' : 'int', 'mode' : 'w'},
				   {'id' : 'minimized', 'type' : 'boolean', 'mode' : 'w'},)

	def __init__( self, id, title='', xpos = 0):
		PortalFolder.__init__(self, id, title)
		self.manage_changeProperties(xpos=xpos)
		self.manage_changeProperties(minimized=0)

	security = ClassSecurityInfo()


	## Utils methods

	def _getRootBlock(self) :
		"""Return the root block object ie : 
			the first block in the tree which have a "_isRootBlock = 1" flag"""
			
		urlTool = getToolByName(self, 'portal_url')
		portalObject = urlTool.getPortalObject()
		
		block = self
		while block != portalObject :
			if hasattr(block.aq_self, '_isRootBlock') :
				return block
			else :
				block = block.aq_parent
		return None
	
	def _getSelfRules(self) :
		"""Return block rules informations"""
		mosTool = getToolByName(self, 'mosaic_tool')
		myTi = mosTool.getTypeInfo(self)
		ruleDic = {} 
		for rule in myTi.objectValues(['Rule Information']) :
			ruleDic[rule.getId()] = rule
		return ruleDic

	def _allowedMoves(self, block) :
		if type(block) == StringType :
			block = getattr(self, block)
		rules = self._getSelfRules()[block.portal_type]
		move_dic = {'global' : rules.allowMove,
					'rightLeft' : rules.allowMoveRightAndLeft,
					'upDown' : rules.allowMoveUpAndDown,
					}
		return move_dic

	security.declarePrivate('_getParentRules')
	def _getParentRules(self) :
		"""Return block rules informations"""
		mosTool = getToolByName(self, 'mosaic_tool')
		parentTi = mosTool.getTypeInfo(self.aq_parent)
		return parentTi.objectValues(['Rule Information'])
	
	def _redirectAfterEdit(self, REQUEST, rootBlock=None, blockId=None) :
		if rootBlock is None :
			rootBlock = self._getRootBlock()
		
		if REQUEST.get('ajax') :
			url = rootBlock.getActionInfo('object_ajax/edit')['url']
		elif REQUEST.SESSION.get('editBoxes') :
			utool = getToolByName(self, 'portal_url')
			url = utool() + '/manage_boxes'
		else :
			url = rootBlock.getActionInfo('object/edit')['url'] + (blockId and '#' + blockId or '')
		return REQUEST.RESPONSE.redirect(url)

	security.declareProtected(ModifyPortalContent, 'getAllowedBlocks')
	def getAllowedBlocks(self) :
		"""Return a list with allowed blocks"""
		rules = self._getSelfRules()
		mosTool = getToolByName(self, 'mosaic_tool')
		allowedBlocks = ()
		for ruleId in rules.keys() :
			ti = mosTool.getTypeInfo(ruleId)
			try :
				if ti.isConstructionAllowed(self) :
					allowedBlocks += ({'id' : ti.id, 'title' : ti.title},)
			except :
				continue
		return allowedBlocks

	security.declareProtected(ModifyPortalContent, 'haveRules')
	def haveRules(self) :
		""" return 1 if type info from self have rules """
		mosTool = getToolByName(self, 'mosaic_tool')
		myTi = mosTool.getTypeInfo(self)
		if myTi.objectValues(['Rule Information']) :
			return 1
		else :
			return 0		
	

	security.declareProtected(View, 'getSlots')
	def getSlots(self) :
		"""return slots"""
		return [ob for ob in self.objectValues() if hasattr(ob, '_isMosaicSlot')]

	security.declareProtected(View, 'getSlotsDic')
	def getSlotsDic(self) :
		"""return slots in dictionary"""
		slots = self.getSlots()
		slotDic = {}
		for slot in slots :
			slotDic[slot.getId()] = slot
		return slotDic
	
	security.declarePublic('Title')
	def Title(self) :
		"""Return title"""
		return self.getProperty('title', d='') or (hasattr(self, 'caption') and self.caption.text) or ''
	
	title = Title
	
	security.declareProtected(ModifyPortalContent, 'setTitle')
	def setTitle(self, title) :
		if hasattr(self, 'caption') :
			self.caption.text = title
		self.title = title
		
	def title_or_id(self) :
		"""Return title or id"""
		return self.Title() or self.id

	## Methods for displaying
	
	security.declareProtected(View, 'getBlocksTable')
	def getBlocksTable(self, filteredTypes=[]) :
		"""return blocks ordered in a 2 dimensions table"""
		blocks = self.objectValues(['Mosaic Block',])

		if filteredTypes :
			blocks = [block for block in blocks if block.portal_type in filteredTypes]
			
		#blocks.sort(lambda x, y : cmp(x.xpos, y.xpos)) inutile ???
		rows = 0
		try :
			cols = blocks[-1].xpos
		except :
			cols = 0
		columnsTable = [] # columns list
		rules = self._getSelfRules()
		for xpos in range(cols + 1) :
			colBlockList = [ block for block in blocks if block.xpos == xpos ] # opt : baliser les debuts de colonne
			colBlockListLength = len(colBlockList)

			# build a column by iterating over blocks with the position xpos
			colBlockInfoList = []
			for blockIndex in range(colBlockListLength) :
				block = colBlockList[blockIndex]
				blockRule = rules[block.portal_type]
				moveDic = {'up' : 0,
						   'down' : 0,
						   'left' : 0,
						   'right' : 0}
				if blockRule.allowMoveUpAndDown :
					moveDic['up']	 = blockIndex > 0
					moveDic['down']	 = blockIndex < colBlockListLength - 1
				if blockRule.allowMoveRightAndLeft :
					moveDic['left']	 = xpos > 0
					moveDic['right'] = 1

				# every block will be displayed in a cell in a table
				colBlockInfoList.append({'block' : block,
										 'col' : block.xpos,
										 'moves' : moveDic,
										 'mode' : blockRule.mode})
				
			# append the new column in the column list ie : columnsTable
			if colBlockListLength > rows :
				rows = colBlockListLength
			columnsTable.append(colBlockInfoList)
			
		# Now the max number of rows in known.
		# We can determine rowspan attributes,
		# Building lines for an easy iterating over <tr> tag
		
		linesTable = []
		cols += 1
		for lineIndex in range(rows) :
			line = []			 
			for columnIndex in range(cols) :
				try :
					blockInfo = columnsTable[columnIndex][lineIndex]					
					if lineIndex == rows - 1 :
						blockInfo.update({'lastOne' : 1})
					line.append(blockInfo)
						
				except :
					if lineIndex and linesTable[lineIndex - 1][columnIndex]['block'] is not None :
						linesTable[lineIndex - 1][columnIndex].update({'rowspan' : rows - lineIndex + 1,
																	   'lastOne' : 1})
						
					if lineIndex and linesTable[lineIndex - 1][columnIndex].get('rowspan') :
						rowspan = -1 # flag for ignoring td insertion
					else :
						rowspan = rows - lineIndex
					line.append({'block' : None,
								 'col' : columnIndex,
								 'rowspan' : rowspan})					  
							
			linesTable.append(line)

		tableInfo = {'rows' : rows,
					 'cols' : cols,
					 'lines' : linesTable,
					 'columns' : columnsTable}
		return tableInfo

	security.declareProtected(View, 'callTemplate')
	def callTemplate(self, displayAction='renderer', **kw) :
		""" Return block template name from block meta fti """
		mosTool = getToolByName(self, 'mosaic_tool')
		mfti = mosTool.getTypeInfo(self)
		templateObj = self.restrictedTraverse(mfti.template)
		return templateObj(block = self, displayAction = displayAction, **kw)

	security.declareProtected(ModifyPortalContent, 'toggle_minimized')
	def toggle_minimized(self, REQUEST = None) :
		"toggle minimized property"
		if not self.minimized :
			self.minimized = 1
		else :
			self.minimized = 0
		if REQUEST is not None:
			return self._redirectAfterEdit(REQUEST, blockId = self.id)
	
	security.declareProtected(View, 'ypos')
	def ypos(self) :
		""" Return the position of self into 
			the parent block """
		return self.aq_parent.getObjectPosition(self.id)

	## Block edition methods
	   
	security.declareProtected(ModifyPortalContent, 'addBlock')
	def addBlock(self, blockType, xpos, id='', beforeBlock='', afterBlock='', REQUEST=None) :
		""" add a new block type """
		mosTool = getToolByName(self, 'mosaic_tool')
		blockId = id or str(int(DateTime()))+str(randrange(1000,10000))
		mosTool.constructContent(blockType,
								   self,
								   blockId,
								   xpos=xpos,
								   beforeBlock=beforeBlock,
								   afterBlock=afterBlock)
		if REQUEST is not None :	
			return self._redirectAfterEdit(REQUEST, blockId = blockId)
		else :
			return blockId

	security.declareProtected(ModifyPortalContent, 'saveBlock')
	def saveBlock(self, REQUEST=None, **kw) :
		""" Save block content """
		mosTool = getToolByName(self, 'mosaic_tool')
		ti = mosTool.getTypeInfo(self)
		slotIds = ti.objectIds(['Slot Information',])

		if REQUEST is not None: kw.update(REQUEST.form)
		dicArgsListItems = [ (argKey, kw[argKey]) for argKey in kw.keys() if type(kw[argKey]) in [InstanceType, DictType] ]

		# iteration over slots for applying edit method
		# an exception is raised when a slot name is not defined in the portal type
		for slotId, kwords in dicArgsListItems :
			if slotId in slotIds :
				slot = getattr(self, slotId) # could raise an exception
				kwArgs = {}
				for k in kwords.keys() : #kwords is an InstanceType not a DictType...
					kwArgs[k] = kwords[k]
				slot.edit(**kwArgs)
			else :
				raise KeyError, "this slot : '%s' is not defined in the type information" % slotId

		rootBlock = self._getRootBlock()
		if rootBlock is not None :
			rootBlock.reindexObject()
		
		if REQUEST is not None :
			return self._redirectAfterEdit(REQUEST, rootBlock = rootBlock, blockId = self.getId())

	security.declareProtected(View, 'SearchableText')
	def SearchableText(self) :
		blocks = self.objectValues(['Mosaic Block',])
		slots = [ slot for slot in self.objectValues() if hasattr(slot, '_isMosaicSlot') ]
		text = ''

		for slot in slots :
			text += ' %s' % slot.SearchableText()
		for block in blocks :
			text += ' %s' % block.SearchableText()

		return text

	security.declareProtected(ModifyPortalContent, 'deleteBlock')
	def deleteBlock(self, blockId, REQUEST=None) :
		""" Delete the blockId """
		old_pos = self.getObjectPosition(blockId)
		if old_pos != 0 :
			redirectBlockId = self._objects[old_pos -1]['id']
		else :
			redirectBlockId = self.getId()
			
		self.manage_delObjects([blockId,])
		
		if REQUEST :
			# évite des appels répétitifs à reindexObject lorsque deleBlock est appelée
			# par deleteBlocks
			rootBlock = self._getRootBlock()
			rootBlock.reindexObject()
			return self._redirectAfterEdit(REQUEST, blockId = redirectBlockId)
		else :
			return redirectBlockId

	security.declareProtected(ModifyPortalContent, 'deleteBlocks')
	def deleteBlocks(self, blockIds, REQUEST=None) :
		""" Delete block id list"""
		redirectBlockId = ''
		for blockId in blockIds :
			redirectBlockId = self.deleteBlock(blockId)

		rootBlock = self._getRootBlock()
		rootBlock.reindexObject()
		if REQUEST :
			return self._redirectAfterEdit(REQUEST, rootBlock = rootBlock, blockId = redirectBlockId)			


	## cut and paste methods

	security.declareProtected(ModifyPortalContent, 'pushCp')
	def pushCp(self, blockId, REQUEST) :
		""" push block in clipboard """
		previousCp = None
		oblist = []
		if REQUEST.has_key('__cp') :
			previousCp = REQUEST['__cp']
			previousCp = _cb_decode(previousCp)
			oblist = previousCp[1]

		block = getattr(self, blockId)		  
		m = Moniker(block)
		oblist.append(m.dump())
		cp=(0, oblist)
		cp=_cb_encode(cp)
		
		resp=REQUEST['RESPONSE']
		resp.setCookie('__cp', cp, path='/')
		REQUEST['__cp'] = cp
		return self._redirectAfterEdit(REQUEST, blockId = blockId)

	security.declareProtected(ModifyPortalContent, 'pasteBlocks')
	def pasteBlocks(self, REQUEST) :
		""" check rules and paste blocks from cp """
		
		if REQUEST.has_key('__cp') :
			cp = REQUEST['__cp']
			cp = _cb_decode(cp)
			mosTool = getToolByName(self, 'mosaic_tool')
			app = self.getPhysicalRoot()
			self_fti = getattr(mosTool, self.portal_type)
			

			# paste element one by one for
			# checking rules on every iteration
			for mdata in cp[1] :
				m = loadMoniker(mdata)
				ob = m.bind(app)
				ob_type = ob.portal_type
				ob_fti = getattr(mosTool, ob_type)
				isBlock = (ob_fti.meta_type == 'Mosaic Block Information')
				isRootBlock = getattr(ob, '_isRootBlock', 0) # a block witch have this attribute is a content type
				
				if isBlock and not isRootBlock and ob_fti.isConstructionAllowed(self) :
					# internal copy and paste handling
					cp = (0, [m.dump(),])
					cp=_cb_encode(cp)
					self.manage_pasteObjects(cb_copy_data = cp)

			_reOrderBlocks(self)

		self.flushCp(REQUEST)
		return self._redirectAfterEdit(REQUEST, blockId = self.getId())

	security.declarePublic('flushCp')
	def flushCp(self, REQUEST) :
		""" Expire cp cookie """
		REQUEST.RESPONSE.expireCookie('__cp', path='/')

	security.declareProtected(ModifyPortalContent, 'getCpInfos')
	def getCpInfos(self, REQUEST) :
		""" Return information about loaded objects in cp """
		if REQUEST.has_key('__cp') :
			cp = REQUEST['__cp']
			cp = _cb_decode(cp)
			return len(cp[1])
		else :
			return 0


	## moves methods
	
	security.declareProtected(ModifyPortalContent, 'moveLeft')
	def moveLeft(self, blockId, REQUEST=None) :
		"""Move block left"""
		move_dic = self._allowedMoves(blockId)
		if not(move_dic['global'] or move_dic['rightLeft']) :
			raise RuleError, "It's not allowed to move this block in this context"
		block = getattr(self, blockId)
		if block.xpos > 0 :
			block.xpos -= 1
			_reOrderBlocks(self)
		if REQUEST is not None :
			return self._redirectAfterEdit(REQUEST, blockId = blockId)



	security.declareProtected(ModifyPortalContent, 'moveRight')
	def moveRight(self, blockId, REQUEST=None) :
		"""Move block Right"""
		move_dic = self._allowedMoves(blockId)
		if not (move_dic['global'] or move_dic['rightLeft']) :
			raise RuleError, "It's not allowed to move this block in this context"
		block = getattr(self, blockId)
		block.xpos += 1
		_reOrderBlocks(self)
		if REQUEST is not None :
			return self._redirectAfterEdit(REQUEST, blockId = blockId)


	security.declareProtected(ModifyPortalContent, 'moveUp')
	def moveUp(self, blockId, REQUEST=None) :
		"""Move block Up"""
		move_dic = self._allowedMoves(blockId)
		if not(move_dic['global'] or move_dic['upDown']) :
			raise RuleError, "It's not allowed to move this block in this context"
		self.moveObjectsUp(blockId)
		if REQUEST is not None :
			return self._redirectAfterEdit(REQUEST, blockId = blockId)


	security.declareProtected(ModifyPortalContent, 'moveDown')
	def moveDown(self, blockId, REQUEST=None) :
		"""Move block left"""
		move_dic = self._allowedMoves(blockId)
		if not(move_dic['global'] or move_dic['upDown']) :
			raise RuleError, "It's not allowed to move this block in this context"
		self.moveObjectsDown(blockId)
		if REQUEST is not None :
			return self._redirectAfterEdit(REQUEST, blockId = blockId)
	
	security.declareProtected(ModifyPortalContent, 'movesUp')
	def movesUp(self, blockIds = [], REQUEST=None) :
		"""move blocks up"""
		for blockId in blockIds :
			self.moveUp(blockId)
		if REQUEST is not None :
			return self._redirectAfterEdit(REQUEST, blockId = blockId)
	
	security.declareProtected(ModifyPortalContent, 'movesDown')
	def movesDown(self, blockIds = [], REQUEST=None) :
		"""move blocks down"""
		for blockId in blockIds :
			self.moveDown(blockId)
		if REQUEST is not None :
			return self._redirectAfterEdit(REQUEST, blockId = blockId)


InitializeClass(MosaicBlock)

def _reOrderBlocks(container) :
	# This method order blocks.
	# It's useful when a left or right move or a block creation in a middle of a column happens.
	blocks = list(container.objectValues(['Mosaic Block',]))

	# get the maximum value for xpos attribute
	blocks.sort(lambda b1, b2 : cmp(b1.xpos, b2.xpos))
	rows = 0
	try :
		cols = blocks[-1].xpos
	except :
		cols = 0
		
	blockPosition = 0
	for xpos in range(cols + 1) :
		colBlockList = [ block for block in blocks if block.xpos == xpos ]
		colBlockListLength = len(colBlockList)

		for blockIndex in range(colBlockListLength) :
			block = colBlockList[blockIndex]
			container.moveObjectToPosition(block.getId(), blockPosition)
			blockPosition += 1


def addMosaicBlock(dispatcher, id, xpos=0, beforeBlock='', afterBlock='') :
	"""Add a new mosaicBlock"""
	parentBlock = dispatcher.Destination()
	parentBlock._setObject(id, MosaicBlock(id, xpos=xpos))
	if beforeBlock :
		position = parentBlock.getObjectPosition(beforeBlock)
		parentBlock.moveObjectToPosition(id, position)
		
	elif afterBlock :
		position = parentBlock.getObjectPosition(afterBlock)
		parentBlock.moveObjectToPosition(id, position + 1)
	else :
		try : _reOrderBlocks(parentBlock)
		except : pass
		
