/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sanselan.formats.psd;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.sanselan.ImageFormat;
import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.ImageParser;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.common.byteSources.ByteSource;
import org.apache.sanselan.formats.psd.dataparsers.DataParser;
import org.apache.sanselan.formats.psd.dataparsers.DataParserBitmap;
import org.apache.sanselan.formats.psd.dataparsers.DataParserCMYK;
import org.apache.sanselan.formats.psd.dataparsers.DataParserGrayscale;
import org.apache.sanselan.formats.psd.dataparsers.DataParserIndexed;
import org.apache.sanselan.formats.psd.dataparsers.DataParserLab;
import org.apache.sanselan.formats.psd.dataparsers.DataParserRGB;
import org.apache.sanselan.formats.psd.datareaders.CompressedDataReader;
import org.apache.sanselan.formats.psd.datareaders.DataReader;
import org.apache.sanselan.formats.psd.datareaders.UncompressedDataReader;
import org.apache.sanselan.util.Debug;

public class PsdImageParser extends ImageParser
{

	public PsdImageParser()
	{
		super.setByteOrder(BYTE_ORDER_MSB);
		// setDebug(true);
	}

	public String getName()
	{
		return "PSD-Custom";
	}

	public String getDefaultExtension()
	{
		return DEFAULT_EXTENSION;
	}

	private static final String DEFAULT_EXTENSION = ".psd";

	private static final String ACCEPTED_EXTENSIONS[] = { DEFAULT_EXTENSION, };

	protected String[] getAcceptedExtensions()
	{
		return ACCEPTED_EXTENSIONS;
	}

	protected ImageFormat[] getAcceptedTypes()
	{
		return new ImageFormat[] { ImageFormat.IMAGE_FORMAT_PSD, //
		};
	}

	private PSDHeaderInfo readHeader(ByteSource byteSource)
			throws ImageReadException, IOException
	{
		InputStream is = null;

		try
		{
			is = byteSource.getInputStream();

			return readHeader(is);
		} finally
		{
			try
			{
				is.close();
			} catch (Exception e)
			{
				Debug.debug(e);
			}

		}
	}

	private PSDHeaderInfo readHeader(InputStream is) throws ImageReadException,
			IOException
	{
		readAndVerifyBytes(is, new byte[] { 56, 66, 80, 83 },
				"Not a Valid PSD File");

		int Version = read2Bytes("Version", is, "Not a Valid PSD File");

		byte Reserved[] = readByteArray("Reserved", 6, is,
				"Not a Valid PSD File");

		int Channels = read2Bytes("Channels", is, "Not a Valid PSD File");
		int Rows = read4Bytes("Rows", is, "Not a Valid PSD File");
		int Columns = read4Bytes("Columns", is, "Not a Valid PSD File");
		int Depth = read2Bytes("Depth", is, "Not a Valid PSD File");
		int Mode = read2Bytes("Mode", is, "Not a Valid PSD File");

		PSDHeaderInfo result = new PSDHeaderInfo(Version, Reserved, Channels,
				Rows, Columns, Depth, Mode);

		return result;
	}

	private ImageContents readImageContents(InputStream is)
			throws ImageReadException, IOException
	{
		PSDHeaderInfo header = readHeader(is);

		int ColorModeDataLength = read4Bytes("ColorModeDataLength", is,
				"Not a Valid PSD File");
		skipBytes(is, ColorModeDataLength);
		// is.skip(ColorModeDataLength);
		// byte ColorModeData[] = readByteArray("ColorModeData",
		// ColorModeDataLength, is, "Not a Valid PSD File");

		int ImageResourcesLength = read4Bytes("ImageResourcesLength", is,
				"Not a Valid PSD File");
		skipBytes(is, ImageResourcesLength);
		// long skipped = is.skip(ImageResourcesLength);
		// byte ImageResources[] = readByteArray("ImageResources",
		// ImageResourcesLength, is, "Not a Valid PSD File");

		int LayerAndMaskDataLength = read4Bytes("LayerAndMaskDataLength", is,
				"Not a Valid PSD File");
		skipBytes(is, LayerAndMaskDataLength);
		// is.skip(LayerAndMaskDataLength);
		// byte LayerAndMaskData[] = readByteArray("LayerAndMaskData",
		// LayerAndMaskDataLength, is, "Not a Valid PSD File");

		int Compression = read2Bytes("Compression", is, "Not a Valid PSD File");

		// skip_bytes(is, LayerAndMaskDataLength);
		// byte ImageData[] = readByteArray("ImageData", LayerAndMaskDataLength,
		// is, "Not a Valid PSD File");

		// System.out.println("Compression: " + Compression);

		ImageContents result = new ImageContents(header, ColorModeDataLength,
		// ColorModeData,
				ImageResourcesLength,
				// ImageResources,
				LayerAndMaskDataLength,
				// LayerAndMaskData,
				Compression);

		return result;
	}

	private ArrayList readImageResourceBlocks(byte bytes[],
			int imageResourceIDs[], int maxBlocksToRead)
			throws ImageReadException, IOException
	{
		return readImageResourceBlocks(new ByteArrayInputStream(bytes),
				imageResourceIDs, maxBlocksToRead, bytes.length);
	}

	private boolean keepImageResourceBlock(int ID, int imageResourceIDs[])
	{
		if (imageResourceIDs == null)
			return true;

		for (int i = 0; i < imageResourceIDs.length; i++)
			if (ID == imageResourceIDs[i])
				return true;

		return false;
	}

	private ArrayList readImageResourceBlocks(InputStream is,
			int imageResourceIDs[], int maxBlocksToRead, int available)
			throws ImageReadException, IOException
	{
		ArrayList result = new ArrayList();

		while (available > 0)
		{
			readAndVerifyBytes(is, new byte[] { 56, 66, 73, 77 },
					"Not a Valid PSD File");
			available -= 4;

			int id = read2Bytes("ID", is, "Not a Valid PSD File");
			available -= 2;

			int nameLength = readByte("NameLength", is, "Not a Valid PSD File");

			available -= 1;
			byte nameBytes[] = readByteArray("NameData", nameLength, is,
					"Not a Valid PSD File");
			available -= nameLength;
			if (((nameLength + 1) % 2) != 0)
			{
				int NameDiscard = readByte("NameDiscard", is,
						"Not a Valid PSD File");
				available -= 1;
			}
			// String Name = readPString("Name", 6, is, "Not a Valid PSD File");
			int DataSize = read4Bytes("Size", is, "Not a Valid PSD File");
			available -= 4;
			// int ActualDataSize = ((DataSize % 2) == 0)
			// ? DataSize
			// : DataSize + 1; // pad to make even

			byte Data[] = readByteArray("Data", DataSize, is,
					"Not a Valid PSD File");
			available -= DataSize;

			if ((DataSize % 2) != 0)
			{
				int DataDiscard = readByte("DataDiscard", is,
						"Not a Valid PSD File");
				available -= 1;
			}

			if (keepImageResourceBlock(id, imageResourceIDs))
			{
				result.add(new ImageResourceBlock(id, nameBytes, Data));

				if ((maxBlocksToRead >= 0)
						&& (result.size() >= maxBlocksToRead))
					return result;
			}
			// debugNumber("ID", ID, 2);

		}

		return result;
	}

	private ArrayList readImageResourceBlocks(ByteSource byteSource,
			int imageResourceIDs[], int maxBlocksToRead)
			throws ImageReadException, IOException
	{
		InputStream is = null;

		try
		{
			is = byteSource.getInputStream();

			ImageContents imageContents = readImageContents(is);

			is.close();

			is = this.getInputStream(byteSource, PSD_SECTION_IMAGE_RESOURCES);
			byte ImageResources[] = readByteArray("ImageResources",
					imageContents.ImageResourcesLength, is,
					"Not a Valid PSD File");

			return readImageResourceBlocks(ImageResources, imageResourceIDs,
					maxBlocksToRead);
		} finally
		{
			try
			{
				is.close();
			} catch (Exception e)
			{
				Debug.debug(e);
			}

		}
	}

	private static final int PSD_SECTION_HEADER = 0;
	private static final int PSD_SECTION_COLOR_MODE = 1;
	private static final int PSD_SECTION_IMAGE_RESOURCES = 2;
	private static final int PSD_SECTION_LAYER_AND_MASK_DATA = 3;
	private static final int PSD_SECTION_IMAGE_DATA = 4;

	private static final int PSD_HEADER_LENGTH = 26;

	private InputStream getInputStream(ByteSource byteSource, int section)
			throws ImageReadException, IOException
	{
		InputStream is = byteSource.getInputStream();

		if (section == PSD_SECTION_HEADER)
			return is;

		skipBytes(is, PSD_HEADER_LENGTH);
		// is.skip(kHeaderLength);

		int ColorModeDataLength = read4Bytes("ColorModeDataLength", is,
				"Not a Valid PSD File");

		if (section == PSD_SECTION_COLOR_MODE)
			return is;

		skipBytes(is, ColorModeDataLength);
		// byte ColorModeData[] = readByteArray("ColorModeData",
		// ColorModeDataLength, is, "Not a Valid PSD File");

		int ImageResourcesLength = read4Bytes("ImageResourcesLength", is,
				"Not a Valid PSD File");

		if (section == PSD_SECTION_IMAGE_RESOURCES)
			return is;

		skipBytes(is, ImageResourcesLength);
		// byte ImageResources[] = readByteArray("ImageResources",
		// ImageResourcesLength, is, "Not a Valid PSD File");

		int LayerAndMaskDataLength = read4Bytes("LayerAndMaskDataLength", is,
				"Not a Valid PSD File");

		if (section == PSD_SECTION_LAYER_AND_MASK_DATA)
			return is;

		skipBytes(is, LayerAndMaskDataLength);
		// byte LayerAndMaskData[] = readByteArray("LayerAndMaskData",
		// LayerAndMaskDataLength, is, "Not a Valid PSD File");

		int Compression = read2Bytes("Compression", is, "Not a Valid PSD File");

		// byte ImageData[] = readByteArray("ImageData",
		// LayerAndMaskDataLength, is, "Not a Valid PSD File");

		if (section == PSD_SECTION_IMAGE_DATA)
			return is;

		throw new ImageReadException("getInputStream: Unknown Section: "
				+ section);
	}

	private byte[] getData(ByteSource byteSource, int section)
			throws ImageReadException, IOException
	{
		InputStream is = null;

		try
		{
			is = byteSource.getInputStream();

			// PSDHeaderInfo header = readHeader(is);
			if (section == PSD_SECTION_HEADER)
				return readByteArray("Header", PSD_HEADER_LENGTH, is,
						"Not a Valid PSD File");
			skipBytes(is, PSD_HEADER_LENGTH);

			int ColorModeDataLength = read4Bytes("ColorModeDataLength", is,
					"Not a Valid PSD File");

			if (section == PSD_SECTION_COLOR_MODE)
				return readByteArray("ColorModeData", ColorModeDataLength, is,
						"Not a Valid PSD File");

			skipBytes(is, ColorModeDataLength);
			// byte ColorModeData[] = readByteArray("ColorModeData",
			// ColorModeDataLength, is, "Not a Valid PSD File");

			int ImageResourcesLength = read4Bytes("ImageResourcesLength", is,
					"Not a Valid PSD File");

			if (section == PSD_SECTION_IMAGE_RESOURCES)
				return readByteArray("ImageResources", ImageResourcesLength,
						is, "Not a Valid PSD File");

			skipBytes(is, ImageResourcesLength);
			// byte ImageResources[] = readByteArray("ImageResources",
			// ImageResourcesLength, is, "Not a Valid PSD File");

			int LayerAndMaskDataLength = read4Bytes("LayerAndMaskDataLength",
					is, "Not a Valid PSD File");

			if (section == PSD_SECTION_LAYER_AND_MASK_DATA)
				return readByteArray("LayerAndMaskData",
						LayerAndMaskDataLength, is, "Not a Valid PSD File");

			skipBytes(is, LayerAndMaskDataLength);
			// byte LayerAndMaskData[] = readByteArray("LayerAndMaskData",
			// LayerAndMaskDataLength, is, "Not a Valid PSD File");

			int Compression = read2Bytes("Compression", is,
					"Not a Valid PSD File");

			// byte ImageData[] = readByteArray("ImageData",
			// LayerAndMaskDataLength, is, "Not a Valid PSD File");

			// if (section == kPSD_SECTION_IMAGE_DATA)
			// return readByteArray("LayerAndMaskData", LayerAndMaskDataLength,
			// is,
			// "Not a Valid PSD File");

		} finally
		{
			try
			{
				is.close();
			} catch (Exception e)
			{
				Debug.debug(e);
			}

		}
		throw new ImageReadException("getInputStream: Unknown Section: "
				+ section);
	}

	private ImageContents readImageContents(ByteSource byteSource)
			throws ImageReadException, IOException
	{
		InputStream is = null;

		try
		{
			is = byteSource.getInputStream();

			ImageContents imageContents = readImageContents(is);
			return imageContents;
		} finally
		{
			try
			{
				is.close();
			} catch (Exception e)
			{
				Debug.debug(e);
			}

		}

	}

	public final static int IMAGE_RESOURCE_ID_ICC_PROFILE = 0x040F;

	public byte[] getICCProfileBytes(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		ArrayList blocks = readImageResourceBlocks(byteSource,
				new int[] { IMAGE_RESOURCE_ID_ICC_PROFILE, }, 1);

		if ((blocks == null) || (blocks.size() < 1))
			return null;

		ImageResourceBlock irb = (ImageResourceBlock) blocks.get(0);
		byte bytes[] = irb.data;
		if ((bytes == null) || (bytes.length < 1))
			return null;
		return bytes;
	}

	public Dimension getImageSize(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		PSDHeaderInfo bhi = readHeader(byteSource);
		if (bhi == null)
			throw new ImageReadException("PSD: couldn't read header");

		return new Dimension(bhi.Columns, bhi.Rows);

	}

	public byte[] embedICCProfile(byte image[], byte profile[])
	{
		return null;
	}

	public boolean embedICCProfile(File src, File dst, byte profile[])
	{
		return false;
	}

	public IImageMetadata getMetadata(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		return null;
	}

	private int getChannelsPerMode(int mode)
	{
		switch (mode)
		{
		case 0: // Bitmap
			return 1;
		case 1: // Grayscale
			return 1;
		case 2: // Indexed
			return -1;
		case 3: // RGB
			return 3;
		case 4: // CMYK
			return 4;
		case 7: // Multichannel
			return -1;
		case 8: // Duotone
			return -1;
		case 9: // Lab
			return 4;
		default:
			return -1;

		}
	}

	public ImageInfo getImageInfo(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		ImageContents imageContents = readImageContents(byteSource);
		// ImageContents imageContents = readImage(byteSource, false);

		if (imageContents == null)
			throw new ImageReadException("PSD: Couldn't read blocks");

		PSDHeaderInfo header = imageContents.header;
		if (header == null)
			throw new ImageReadException("PSD: Couldn't read Header");

		int Width = header.Columns;
		int Height = header.Rows;

		ArrayList Comments = new ArrayList();
		// TODO: comments...

		int BitsPerPixel = header.Depth * getChannelsPerMode(header.Mode);
		// System.out.println("header.Depth: " + header.Depth);
		// System.out.println("header.Mode: " + header.Mode);
		// System.out.println("getChannelsPerMode(header.Mode): " +
		// getChannelsPerMode(header.Mode));
		if (BitsPerPixel < 0)
			BitsPerPixel = 0;
		ImageFormat Format = ImageFormat.IMAGE_FORMAT_PSD;
		String FormatName = "Photoshop";
		String MimeType = "image/x-photoshop";
		// we ought to count images, but don't yet.
		int NumberOfImages = -1;
		// not accurate ... only reflects first
		boolean isProgressive = false;

		int PhysicalWidthDpi = 72;
		float PhysicalWidthInch = (float) ((double) Width / (double) PhysicalWidthDpi);
		int PhysicalHeightDpi = 72;
		float PhysicalHeightInch = (float) ((double) Height / (double) PhysicalHeightDpi);

		String FormatDetails = "Psd";

		boolean isTransparent = false; // TODO: inaccurate.
		boolean usesPalette = header.Mode == COLOR_MODE_INDEXED;
		int ColorType = ImageInfo.COLOR_TYPE_UNKNOWN;

		String compressionAlgorithm;
		switch (imageContents.Compression)
		{
		case 0:
			compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_NONE;
			break;
		case 1:
			compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_PSD;
			break;
		default:
			compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_UNKNOWN;
		}

		ImageInfo result = new ImageInfo(FormatDetails, BitsPerPixel, Comments,
				Format, FormatName, Height, MimeType, NumberOfImages,
				PhysicalHeightDpi, PhysicalHeightInch, PhysicalWidthDpi,
				PhysicalWidthInch, Width, isProgressive, isTransparent,
				usesPalette, ColorType, compressionAlgorithm);

		return result;
	}

	private ImageResourceBlock findImageResourceBlock(ArrayList blocks, int ID)
			throws ImageReadException, IOException
	{
		for (int i = 0; i < blocks.size(); i++)
		{
			ImageResourceBlock block = (ImageResourceBlock) blocks.get(i);

			if (block.id == ID)
				return block;
		}
		return null;
	}

	public boolean dumpImageFile(PrintWriter pw, ByteSource byteSource)
			throws ImageReadException, IOException
	{
		pw.println("gif.dumpImageFile");

		{
			ImageInfo fImageData = getImageInfo(byteSource);
			if (fImageData == null)
				return false;

			fImageData.toString(pw, "");
		}
		{
			ImageContents imageContents = readImageContents(byteSource);

			imageContents.dump(pw);
			imageContents.header.dump(pw);

			ArrayList blocks = readImageResourceBlocks(byteSource,
			// fImageContents.ImageResources,
					null, -1);

			pw.println("blocks.size(): " + blocks.size());

			// System.out.println("gif.blocks: " + blocks.blocks.size());
			for (int i = 0; i < blocks.size(); i++)
			{
				ImageResourceBlock block = (ImageResourceBlock) blocks.get(i);
				pw.println("\t" + i + " (" + Integer.toHexString(block.id)
						+ ", " + "'"
						+ new String(block.nameData)
						+ "' ("
						+ block.nameData.length
						+ "), "
						// + block.getClass().getName()
						// + ", "
						+ " data: " + block.data.length + " type: '"
						+ new PSDConstants().getDescription(block.id) + "' "
						+ ")");

			}

		}

		pw.println("");

		return true;
	}

	private static final int COLOR_MODE_INDEXED = 2;

	public BufferedImage getBufferedImage(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		ImageContents imageContents = readImageContents(byteSource);
		// ImageContents imageContents = readImage(byteSource, false);

		if (imageContents == null)
			throw new ImageReadException("PSD: Couldn't read blocks");

		PSDHeaderInfo header = imageContents.header;
		if (header == null)
			throw new ImageReadException("PSD: Couldn't read Header");

		// ImageDescriptor id = (ImageDescriptor)
		// findBlock(fImageContents.blocks,
		// kImageSeperator);
		// if (id == null)
		// throw new ImageReadException("PSD: Couldn't read Image Descriptor");
		// GraphicControlExtension gce = (GraphicControlExtension) findBlock(
		// fImageContents.blocks, kGraphicControlExtension);

		ArrayList blocks = readImageResourceBlocks(byteSource,
		// fImageContents.ImageResources,
				null, -1);

		int width = header.Columns;
		int height = header.Rows;
		// int height = header.Columns;

		// int transfer_type;

		// transfer_type = DataBuffer.TYPE_BYTE;

		boolean hasAlpha = false;
		BufferedImage result = getBufferedImageFactory(params)
				.getColorBufferedImage(width, height, hasAlpha);

		DataParser dataParser;
		switch (imageContents.header.Mode)
		{
		case 0: // bitmap
			dataParser = new DataParserBitmap();
			break;
		case 1:
		case 8: // Duotone=8;
			dataParser = new DataParserGrayscale();
			break;
		case 3:
			dataParser = new DataParserRGB();
			break;
		case 4:
			dataParser = new DataParserCMYK();
			break;
		case 9:
			dataParser = new DataParserLab();
			break;
		case COLOR_MODE_INDEXED:
			// case 2 : // Indexed=2;
		{

			byte ColorModeData[] = getData(byteSource, PSD_SECTION_COLOR_MODE);

			// ImageResourceBlock block = findImageResourceBlock(blocks,
			// 0x03EB);
			// if (block == null)
			// throw new ImageReadException(
			// "Missing: Indexed Color Image Resource Block");

			dataParser = new DataParserIndexed(ColorModeData);
			break;
		}
		case 7: // Multichannel=7;
			// fDataParser = new DataParserStub();
			// break;

			// case 1 :
			// fDataReader = new CompressedDataReader();
			// break;
		default:
			throw new ImageReadException("Unknown Mode: "
					+ imageContents.header.Mode);
		}
		DataReader fDataReader;
		switch (imageContents.Compression)
		{
		case 0:
			fDataReader = new UncompressedDataReader(dataParser);
			break;
		case 1:
			fDataReader = new CompressedDataReader(dataParser);
			break;
		default:
			throw new ImageReadException("Unknown Compression: "
					+ imageContents.Compression);
		}
		{
			InputStream is = null;

			try
			{
				is = getInputStream(byteSource, PSD_SECTION_IMAGE_DATA);
				fDataReader.readData(is, result, imageContents, this);

				fDataReader.dump();
				// is.
				// ImageContents imageContents = readImageContents(is);
				// return imageContents;
			} finally
			{
				try
				{
					if (is != null)
						is.close();
				} catch (Exception e)
				{
					Debug.debug(e);
				}

			}

		}

		return result;

	}

	public final static int IMAGE_RESOURCE_ID_XMP = 0x0424;

	public final static String BLOCK_NAME_XMP = "XMP";

	/**
	 * Extracts embedded XML metadata as XML string.
	 * <p>
	 * 
	 * @param file
	 *            File containing image data.
	 * @param params
	 *            Map of optional parameters, defined in SanselanConstants.
	 * @return Xmp Xml as String, if present. Otherwise, returns null..
	 */
	public String getXmpXml(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{

		ImageContents imageContents = readImageContents(byteSource);

		if (imageContents == null)
			throw new ImageReadException("PSD: Couldn't read blocks");

		PSDHeaderInfo header = imageContents.header;
		if (header == null)
			throw new ImageReadException("PSD: Couldn't read Header");

		ArrayList blocks = readImageResourceBlocks(byteSource,
				new int[] { IMAGE_RESOURCE_ID_XMP, }, -1);

		if ((blocks == null) || (blocks.size() < 1))
			return null;

		List xmpBlocks = new ArrayList();
		if (false)
		{
			// TODO: for PSD 7 and later, verify "XMP" name.
			for (int i = 0; i < blocks.size(); i++)
			{
				ImageResourceBlock block = (ImageResourceBlock) blocks.get(i);
				if (!block.getName().equals(BLOCK_NAME_XMP))
					continue;
				xmpBlocks.add(block);
			}
		} else
			xmpBlocks.addAll(blocks);

		if (xmpBlocks.size() < 1)
			return null;
		if (xmpBlocks.size() > 1)
			throw new ImageReadException(
					"PSD contains more than one XMP block.");

		ImageResourceBlock block = (ImageResourceBlock) xmpBlocks.get(0);

		try
		{
			// segment data is UTF-8 encoded xml.
			String xml = new String(block.data, 0, block.data.length, "utf-8");
			return xml;
		} catch (UnsupportedEncodingException e)
		{
			throw new ImageReadException("Invalid JPEG XMP Segment.");
		}
	}

}