# -*- coding: utf-8 -*-
#######################################################################################
#   Photo is a part of Plinn - http://plinn.org                                       #
#   Copyright (C) 2004-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.   #
#######################################################################################
""" Photo zope object



"""

from Globals import InitializeClass, DTMLFile
from AccessControl import ClassSecurityInfo
from AccessControl.Permissions import manage_properties, view
from metadata import Metadata
from TileSupport import TileSupport
from xmputils import TIFF_ORIENTATIONS
from BTrees.OOBTree import OOBTree
from cache import memoizedmethod

from blobbases import Image, cookId, getImageInfo
import PIL.Image
import string
from math import floor
from types import StringType
from logging import getLogger
console = getLogger('Photo.Photo')



def _strSize(size) :
	return str(size[0]) + '_' + str(size[1])
	
def getNewSize(fullSize, maxNewSize) :
	fullWidth, fullHeight =	 fullSize
	maxWidth, maxHeight = maxNewSize
	
	widthRatio = float(maxWidth) / fullWidth
	if int(fullHeight * widthRatio) > maxWidth :
		heightRatio = float(maxHeight) / fullHeight
		return (int(fullWidth * heightRatio) , maxHeight)
	else :
		return (maxWidth, int(fullHeight * widthRatio))






class Photo(Image, TileSupport, Metadata):
	"Photo éditable en ligne"

	meta_type = 'Photo'
	
	security = ClassSecurityInfo()
		
	manage_editForm = DTMLFile('dtml/photoEdit',globals(),
							   Kind='Photo', kind='photo')
	manage_editForm._setName('manage_editForm')
	manage = manage_main = manage_editForm
	view_image_or_file = DTMLFile('dtml/photoView',globals())
	
	manage_options=(
		 {'label':'Edit', 'action':'manage_main',
		 'help':('OFSP','Image_Edit.stx')},
		 {'label':'View', 'action':'view_image_or_file',
		 'help':('OFSP','Image_View.stx')},) + Image.manage_options[2:]


	filters = ['NEAREST', 'BILINEAR', 'BICUBIC', 'ANTIALIAS']

	_properties = Image._properties[:2] + (
		{'id' : 'height',				'type' : 'int',		'mode' : 'w'},
		{'id' : 'width',				'type' : 'int',		'mode' : 'w'},
		{'id' : 'auto_update_thumb',	'type' : 'boolean', 'mode' : 'w'},
		{'id' : 'tiles_available',		'type' : 'int',		'mode' : 'r'},
		{'id' : 'thumb_height',			'type' : 'int',		'mode' : 'w'},
		{'id' : 'thumb_width',			'type' : 'int',		'mode' : 'w'},
		{'id' : 'prop_filter',
		 'label' : 'Filter',
		 'type' : 'selection',
		 'select_variable' : 'filters',
		 'mode' : 'w'},
		)
		
		
	security.declareProtected(manage_properties, 'manage_editProperties')
	def manage_editProperties(self, REQUEST=None, no_refresh = 0, **kw):
		"Save Changes and update the thumbnail"
		Image.manage_changeProperties(self, REQUEST, **kw)
		
		if hasattr(self, 'thumbnail') and self.auto_update_thumb == 1 and no_refresh == 0 :
			self.makeThumbnail()
				
		if REQUEST:
			message="Saved changes."
			return self.manage_propertiesForm(self,REQUEST,
											  manage_tabs_message=message)


	def __init__(self, id, title, file, content_type='', precondition='', **kw) :
		# 0 means: tiles are not generated
		# 1 means: tiles are all generated
		# 2 means: tiling is not available is this photo (deliberated choice of the owner)
		# -1 means: no data tiles cannot be generated
		self.tiles_available = 0
		self.auto_update_thumb = kw.get('auto_update_thumb', 1)
		self.thumb_height = kw.get('thumb_height', 180)
		self.thumb_width = kw.get('thumb_width', 180)
		self.prop_filter = kw.get('prop_filter', 'ANTIALIAS')
		super(Photo, self).__init__(id, title, file, content_type='', precondition='')

		defaultBlankThumbnail = kw.get('defaultBlankThumbnail', None)
		if defaultBlankThumbnail :
			blankThumbnail = Image('thumbnail', '',
								   getattr(defaultBlankThumbnail, '_data', getattr(defaultBlankThumbnail, 'data', None)))
			self.thumbnail = blankThumbnail
		
		self._methodResultsCache = OOBTree()
		TileSupport.__init__(self)
		
	def update_data(self, file, content_type=None) :
		super(Photo, self).update_data(file, content_type)
		
		if self.content_type != 'image/jpeg' and self.size :
			raw = self.open('r')
			im = PIL.Image.open(raw)
			self.content_type = 'image/%s' % im.format.lower()
			self.width, self.height = im.size
			
			if im.mode not in ('L', 'RGB'):
				im = im.convert('RGB')

			jpeg_image = Image('jpeg_image', '', '', content_type='image/jpeg')
			out = jpeg_image.open('w')
			im.save(out, 'JPEG', quality=90)
			jpeg_image.updateFormat(out.tell(), im.size, 'image/jpeg')
			out.close()
			self.jpeg_image = jpeg_image
			
		self._methodResultsCache = OOBTree()
		self._v__methodResultsCache = OOBTree()
		
		self._tiles = OOBTree()
		if self.tiles_available in [1, -1]:
			self.tiles_available = 0
		
		if hasattr(self, 'thumbnail') and self.auto_update_thumb == 1 :
			self.makeThumbnail()
		
		
	
	def _getJpegBlob(self) :
		if self.size :
			if self.content_type == 'image/jpeg' :
				return self.bdata
			else :
				return self.jpeg_image.bdata
		else :
			return None
		
	security.declareProtected(view, 'getJpegImage')
	def getJpegImage(self, REQUEST, RESPONSE) :
		""" return JPEG formated image """
		if self.content_type == 'image/jpeg' :
			return self.index_html(REQUEST, RESPONSE)
		elif self.jpeg_image :
			return	self.jpeg_image.index_html(REQUEST, RESPONSE)
	
	security.declareProtected(view, 'tiffOrientation')
	@memoizedmethod()
	def tiffOrientation(self) :
		tiffOrientation = self.getXmpValue('tiff:Orientation')
		if tiffOrientation :
			return int(tiffOrientation)
		else :
			# TODO : falling back to legacy Exif metadata
			return 1
	
	def _rotateOrFlip(self, im) :
		orientation = self.tiffOrientation()
		rotation, flip = TIFF_ORIENTATIONS.get(orientation, (0, False))
		if rotation :
			im = im.rotate(-rotation)
		if flip :
			im = im.transpose(PIL.Image.FLIP_LEFT_RIGHT)
		return im
	
	@memoizedmethod('size', 'keepAspectRatio')
	def _getResizedImage(self, size, keepAspectRatio) :
		""" returns a resized version of the raw image.
		"""

		fullSizeFile = self._getJpegBlob().open('r')
		fullSizeImage = PIL.Image.open(fullSizeFile)
		if fullSizeImage.mode not in ('L', 'RGB'):
			fullSizeImage.convert('RGB')
		fullSize = fullSizeImage.size

		if (keepAspectRatio) :
			newSize = getNewSize(fullSize, size)
		else :
			newSize = size
		
		fullSizeImage.thumbnail(newSize, PIL.Image.ANTIALIAS)
		fullSizeImage = self._rotateOrFlip(fullSizeImage)
		
		for hook in self._getAfterResizingHooks() :
			hook(self, fullSizeImage)
		
		
		resizedImage = Image(self.getId() + _strSize(size), 'resized copy of %s' % self.getId(), '')
		out = resizedImage.open('w')
		fullSizeImage.save(out, "JPEG", quality=90)
		resizedImage.updateFormat(out.tell(), fullSizeImage.size, 'image/jpeg')
		out.close()
		return resizedImage
	
	def _getAfterResizingHooks(self) :
		""" returns a list of hook scripts that are executed
			after the image is resized.
		"""
		return []
		
		
	security.declarePrivate('makeThumbnail')
	def makeThumbnail(self) :
		"make a thumbnail from jpeg data"
		b = self._getJpegBlob()
		if b is not None :
			# récupération des propriétés de redimentionnement
			thumb_size = []
			if int(self.width) >= int(self.height) :
				thumb_size.append(self.thumb_height)
				thumb_size.append(self.thumb_width)
			else :
				thumb_size.append(self.thumb_width)
				thumb_size.append(self.thumb_height)
			thumb_size = tuple(thumb_size)

			if thumb_size[0] <= 1 or thumb_size[1] <= 1 :
				thumb_size = (180, 180)
			thumb_filter = getattr(PIL.Image, self.prop_filter, PIL.Image.ANTIALIAS)
			
			# create a thumbnail image file
			original_file = b.open('r')
			image = PIL.Image.open(original_file)
			if image.mode not in ('L', 'RGB'):
				image = image.convert('RGB')
			
			image.thumbnail(thumb_size, thumb_filter)
			image = self._rotateOrFlip(image)
			
			thumbnail = Image('thumbnail', 'Thumbail', '', 'image/jpeg')
			out = thumbnail.open('w')
			image.save(out, "JPEG", quality=90)
			thumbnail.updateFormat(out.tell(), image.size, 'image/jpeg')
			out.close()
			original_file.close()
			self.thumbnail = thumbnail
			return True
		else :
			return False

	security.declareProtected(view, 'getThumbnail')
	def getThumbnail(self, REQUEST, RESPONSE) :
		"Return the thumbnail image and create it before if it does not exist yet."
		if not hasattr(self, 'thumbnail') :
			self.makeThumbnail()
		return self.thumbnail.index_html(REQUEST=REQUEST, RESPONSE=RESPONSE)
	
	security.declareProtected(view, 'getThumbnailSize')
	def getThumbnailSize(self) :
		""" return thumbnail size dict
		"""
		if not hasattr(self, 'thumbnail') :
			if not self.width :
				return {'height' : 0, 'width' : 0}
			else :
				thumbMaxFrame = []
				if int(self.width) >= int(self.height) :
					thumbMaxFrame.append(self.thumb_height)
					thumbMaxFrame.append(self.thumb_width)
				else :
					thumbMaxFrame.append(self.thumb_width)
					thumbMaxFrame.append(self.thumb_height)
				thumbMaxFrame = tuple(thumbMaxFrame)

				if thumbMaxFrame[0] <= 1 or thumbMaxFrame[1] <= 1 :
					thumbMaxFrame = (180, 180)
				
				th = self.height * thumbMaxFrame[0] / float(self.width)
				# resizing round limit is not 0.5 but seems to be strictly up to 0.75
				# TODO check algorithms
				if th > floor(th) + 0.75 :
					th = int(floor(th)) + 1
				else :
					th = int(floor(th))

				if th <= thumbMaxFrame[1] :
					thumbSize = (thumbMaxFrame[0], th)
				else :
					tw = self.width * thumbMaxFrame[1] / float(self.height)
					if tw > floor(tw) + 0.75 :
						tw = int(floor(tw)) + 1
					else :
						tw = int(floor(tw))
					thumbSize = (tw, thumbMaxFrame[1])
				
				if self.tiffOrientation() <= 4 :
					return {'width':thumbSize[0], 'height' : thumbSize[1]}
				else :
					return {'width':thumbSize[1], 'height' : thumbSize[0]}
					
		else :
			return {'height' : self.thumbnail.height, 'width' :self.thumbnail.width}
		

	security.declareProtected(view, 'getResizedImageSize')
	def getResizedImageSize(self, REQUEST=None, size=(), keepAspectRatio=True, asXml=False) :
		""" return the reel image size the after resizing """
		if not size :
			size = REQUEST.SESSION.get('preferedImageSize', (600, 600))
		elif type(size) == StringType :
			size = tuple([int(n) for n in size.split('_')])
		
		resizedImage = self._getResizedImage(size, keepAspectRatio)
		size = (resizedImage.width, resizedImage.height)
			
		if asXml :
			REQUEST.RESPONSE.setHeader('content-type', 'text/xml; charset=utf-8')
			return '<size><width>%d</width><height>%d</height></size>' % size
		else :
			return size
		
	
	security.declareProtected(view, 'getResizedImage')
	def getResizedImage(self, REQUEST, RESPONSE, size=(), keepAspectRatio=True) :
		"""
		Return a volatile resized image.
		The 'preferedImageSize' tuple (width, height) is looked up into SESSION data.
		Default size is 600 x 600 px
		"""
		if not size :
			size = REQUEST.SESSION.get('preferedImageSize', (600, 600))
		elif type(size) == StringType :
			size = size.split('_')
			if len(size) == 1 :
				i = int(size[0])
				size = (i, i)
				keepAspectRatio = True
			else :
				size = tuple([int(n) for n in size])
		
		return self._getResizedImage(size, keepAspectRatio).index_html(REQUEST=REQUEST, RESPONSE=RESPONSE)
		
						
InitializeClass(Photo)


# Factories
def addPhoto(dispatcher, id, file='', title='',
			 precondition='', content_type='', REQUEST=None, **kw) :
	"""
	Add a new Photo object.
	Creates a new Photo object 'id' with the contents of 'file'.
	"""
	id=str(id)
	title=str(title)
	content_type=str(content_type)
	precondition=str(precondition)

	id, title = cookId(id, title, file)
	parentContainer = dispatcher.Destination()

	parentContainer._setObject(id, Photo(id,title,file,content_type, precondition, **kw))

	if REQUEST is not None:
		try:	url=dispatcher.DestinationURL()
		except: url=REQUEST['URL1']
		REQUEST.RESPONSE.redirect('%s/manage_main' % url)
	return id

# creation form
addPhotoForm = DTMLFile('dtml/addPhotoForm', globals())
