# -*- coding: utf-8 -*-
#######################################################################################
#   Photo is a part of Plinn - http://plinn.org                                       #
#   Copyright (C) 2008  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.   #
#######################################################################################
""" Jpeg plugin for xmp read/write support.


"""

from xmp import XMP
from types import StringType

class JpegXmpIO(object):
		
	JPEG_XMP_LEADIN = 'http://ns.adobe.com/xap/1.0/\x00'
	JPEG_XMP_LEADIN_LENGTH = len(JPEG_XMP_LEADIN)
		
	MARKERS = {
		0xFFC0: ("SOF0",	"Baseline DCT", True),
		0xFFC1: ("SOF1",	"Extended Sequential DCT", True),
		0xFFC2: ("SOF2",	"Progressive DCT", True),
		0xFFC3: ("SOF3",	"Spatial lossless", True),
		0xFFC4: ("DHT",		"Define Huffman table", True),
		0xFFC5: ("SOF5",	"Differential sequential DCT", True),
		0xFFC6: ("SOF6",	"Differential progressive DCT", True),
		0xFFC7: ("SOF7",	"Differential spatial", True),
		0xFFC8: ("JPG",		"Extension", False),
		0xFFC9: ("SOF9",	"Extended sequential DCT (AC)", True),
		0xFFCA: ("SOF10",	"Progressive DCT (AC)", True),
		0xFFCB: ("SOF11",	"Spatial lossless DCT (AC)", True),
		0xFFCC: ("DAC",		"Define arithmetic coding conditioning", True),
		0xFFCD: ("SOF13",	"Differential sequential DCT (AC)", True),
		0xFFCE: ("SOF14",	"Differential progressive DCT (AC)", True),
		0xFFCF: ("SOF15",	"Differential spatial (AC)", True),
		0xFFD0: ("RST0",	"Restart 0", False),
		0xFFD1: ("RST1",	"Restart 1", False),
		0xFFD2: ("RST2",	"Restart 2", False),
		0xFFD3: ("RST3",	"Restart 3", False),
		0xFFD4: ("RST4",	"Restart 4", False),
		0xFFD5: ("RST5",	"Restart 5", False),
		0xFFD6: ("RST6",	"Restart 6", False),
		0xFFD7: ("RST7",	"Restart 7", False),
		0xFFD8: ("SOI",		"Start of image", False),
		0xFFD9: ("EOI",		"End of image", False),
		0xFFDA: ("SOS",		"Start of scan", True),
		0xFFDB: ("DQT",		"Define quantization table", True),
		0xFFDC: ("DNL",		"Define number of lines", True),
		0xFFDD: ("DRI",		"Define restart interval", True),
		0xFFDE: ("DHP",		"Define hierarchical progression", True),
		0xFFDF: ("EXP",		"Expand reference component", True),
		0xFFE0: ("APP0",	"Application segment 0", True),
		0xFFE1: ("APP1",	"Application segment 1", True),
		0xFFE2: ("APP2",	"Application segment 2", True),
		0xFFE3: ("APP3",	"Application segment 3", True),
		0xFFE4: ("APP4",	"Application segment 4", True),
		0xFFE5: ("APP5",	"Application segment 5", True),
		0xFFE6: ("APP6",	"Application segment 6", True),
		0xFFE7: ("APP7",	"Application segment 7", True),
		0xFFE8: ("APP8",	"Application segment 8", True),
		0xFFE9: ("APP9",	"Application segment 9", True),
		0xFFEA: ("APP10",	"Application segment 10", True),
		0xFFEB: ("APP11",	"Application segment 11", True),
		0xFFEC: ("APP12",	"Application segment 12", True),
		0xFFED: ("APP13",	"Application segment 13", True),
		0xFFEE: ("APP14",	"Application segment 14", True),
		0xFFEF: ("APP15",	"Application segment 15", True),
		0xFFF0: ("JPG0",	"Extension 0", False),
		0xFFF1: ("JPG1",	"Extension 1", False),
		0xFFF2: ("JPG2",	"Extension 2", False),
		0xFFF3: ("JPG3",	"Extension 3", False),
		0xFFF4: ("JPG4",	"Extension 4", False),
		0xFFF5: ("JPG5",	"Extension 5", False),
		0xFFF6: ("JPG6",	"Extension 6", False),
		0xFFF7: ("JPG7",	"Extension 7", False),
		0xFFF8: ("JPG8",	"Extension 8", False),
		0xFFF9: ("JPG9",	"Extension 9", False),
		0xFFFA: ("JPG10",	"Extension 10", False),
		0xFFFB: ("JPG11",	"Extension 11", False),
		0xFFFC: ("JPG12",	"Extension 12", False),
		0xFFFD: ("JPG13",	"Extension 13", False),
		0xFFFE: ("COM",		"Comment", True)
	}
		
		
	@staticmethod
	def i16(c,o=0):
		return ord(c[o+1]) + (ord(c[o])<<8)
	
	@staticmethod
	def getBlockInfo(marker, f):
		start = f.tell()
		length = JpegXmpIO.i16(f.read(2))
		
		markerInfo = JpegXmpIO.MARKERS[marker]
		blockInfo = { 'name' : markerInfo[0]
					, 'description' : markerInfo[1]
					, 'start' : start
					, 'length' : length}
		
		jump = start + length
		f.seek(jump)
		
		return blockInfo
	
	@staticmethod
	def getBlockInfos(f) :
		f.seek(0)
		s  = f.read(1)
		
		blockInfos = []
		
		while 1:
			s = s + f.read(1)
			i = JpegXmpIO.i16(s)
			
			if JpegXmpIO.MARKERS.has_key(i):
				name, desciption, handle = JpegXmpIO.MARKERS[i]
				
				if handle:
					blockInfo = JpegXmpIO.getBlockInfo(i, f)
					blockInfos.append(blockInfo)
				if i == 0xFFDA: # start of scan
				   break
				s = f.read(1)
			elif i == 0 or i == 65535:
				# padded marker or junk; move on
				s = "\xff"
		
		return blockInfos
	
	
	@staticmethod
	def genJpegXmpBlock(uXmpData, paddingSize=2) :
		block = u''

		block += JpegXmpIO.JPEG_XMP_LEADIN
		block += XMP.genXMPPacket(uXmpData, paddingSize)
		# utf-8 mandatory in jpeg files (xmp specification)
		block = block.encode('utf-8')

		length = len(block) + 2

		# TODO : reduce padding size if this assertion occurs
		assert length <= 0xfffd, "Jpeg block too long: %d (max: 0xfffd)" % hex(length)

		chrlength = chr(length >> 8 & 0xff) + chr(length & 0xff)

		block = chrlength + block

		return block
	


	@staticmethod
	def read(f) :

		blockInfos = JpegXmpIO.getBlockInfos(f)
		app1BlockInfos = [b for b in blockInfos if b['name'] == 'APP1']

		xmpBlocks = []

		for info in app1BlockInfos :
			f.seek(info['start'])
			data = f.read(info['length'])[2:]
			if data.startswith(JpegXmpIO.JPEG_XMP_LEADIN) :
				xmpBlocks.append(data)

		assert len(xmpBlocks) <= 1, "Multiple xmp block data is not yet supported."

		if len(xmpBlocks) == 1 :
			data = xmpBlocks[0]
			packet = data[len(JpegXmpIO.JPEG_XMP_LEADIN):]
			return packet
		else :
			return None
	
	@staticmethod
	def write(original, new, uxmp) :
		
		blockInfos = JpegXmpIO.getBlockInfos(original)
		app1BlockInfos = [b for b in blockInfos if b['name'] == 'APP1']

		xmpBlockInfos = []

		for info in app1BlockInfos :
			original.seek(info['start'])
			lead = original.read(JpegXmpIO.JPEG_XMP_LEADIN_LENGTH+2)[2:]
			if lead == JpegXmpIO.JPEG_XMP_LEADIN :
				xmpBlockInfos.append(info)


		assert len(xmpBlockInfos) <= 1, "Multiple xmp block data is not yet supported."

		if isinstance(uxmp, StringType) :
			uxmp = unicode(uxmp, 'utf-8')
			
		if len(xmpBlockInfos) == 0 :
			blockInfo = [b for b in blockInfos if b['name'] == 'APP13']

			if not blockInfo :
				blockInfo = [b for b in blockInfos if b['name'] == 'APP1']

			if not blockInfo :
				blockInfo = [b for b in blockInfos if b['name'] == 'APP0']
			
			if not blockInfo : raise ValueError, "No suitable place to write xmp segment"
			
			info = blockInfo[0]
			print 'create xmp after: %s' % info['name']
			
			original.seek(0)
			before = original.read(info['start'] + info['length'])
			after = original.read()
			
			jpegBlock = '\xFF\xE1' + JpegXmpIO.genJpegXmpBlock(uxmp)

		else :
			info = xmpBlockInfos[0]

			original.seek(0)
			before = original.read(info['start'])

			original.seek(info['start'] + info['length'])
			after = original.read()

			jpegBlock = JpegXmpIO.genJpegXmpBlock(uxmp)
		
		new.seek(0)
		new.write(before)
		new.write(jpegBlock)
		new.write(after)

		# if original == new :
		# 	new.seek(0)
		# else :
		# 	new.close()
		# 	original.close()


XMP.registerReader('image/jpeg', JpegXmpIO.read)
XMP.registerWriter('image/jpeg', JpegXmpIO.write)
