/*
 * 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.tiff;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Map;

import org.apache.sanselan.FormatCompliance;
import org.apache.sanselan.ImageFormat;
import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.ImageParser;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.ImageWriteException;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.common.byteSources.ByteSource;
import org.apache.sanselan.formats.tiff.constants.TiffConstants;
import org.apache.sanselan.formats.tiff.write.TiffImageWriterLossy;

public class TiffImageParser extends ImageParser implements TiffConstants
{
	public TiffImageParser()
	{
		//        setDebug(true);
	}

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

	public String getDefaultExtension()
	{
		return DEFAULT_EXTENSION;
	}

	private static final String DEFAULT_EXTENSION = ".tif";

	private static final String ACCEPTED_EXTENSIONS[] = {
			".tif", ".tiff",
	};

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

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

	public byte[] getICCProfileBytes(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		FormatCompliance formatCompliance = FormatCompliance.getDefault();
		TiffContents contents = new TiffReader(isStrict(params)).readFirstDirectory(byteSource,
				params, false, formatCompliance);
		TiffDirectory directory = (TiffDirectory) contents.directories.get(0);

		TiffField field = directory.findField(EXIF_TAG_ICC_PROFILE);
		if (null == field)
			return null;
		return field.oversizeValue;
	}

	public Dimension getImageSize(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		FormatCompliance formatCompliance = FormatCompliance.getDefault();
		TiffContents contents = new TiffReader(isStrict(params)).readFirstDirectory(byteSource,
				params, false, formatCompliance);
		TiffDirectory directory = (TiffDirectory) contents.directories.get(0);

		int width = directory.findField(TIFF_TAG_IMAGE_WIDTH).getIntValue();
		int height = directory.findField(TIFF_TAG_IMAGE_LENGTH).getIntValue();

		return new Dimension(width, height);
	}

	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
	{
		FormatCompliance formatCompliance = FormatCompliance.getDefault();
		TiffContents contents = new TiffReader(isStrict(params)).readContents(byteSource,
				params, formatCompliance);

		ArrayList directories = contents.directories;

		TiffImageMetadata result = new TiffImageMetadata(contents);

		for (int i = 0; i < directories.size(); i++)
		{
			TiffDirectory dir = (TiffDirectory) directories.get(i);

			TiffImageMetadata.Directory metadataDirectory = new TiffImageMetadata.Directory(
					dir);

			ArrayList entries = dir.getDirectoryEntrys();

			for (int j = 0; j < entries.size(); j++)
			{
				TiffField entry = (TiffField) entries.get(j);
				metadataDirectory.add(entry);
			}

			result.add(metadataDirectory);
		}

		return result;
	}

	public ImageInfo getImageInfo(ByteSource byteSource, Map params)
			throws ImageReadException, IOException
	{
		FormatCompliance formatCompliance = FormatCompliance.getDefault();
		TiffContents contents = new TiffReader(isStrict(params)).readDirectories(byteSource,
				false, formatCompliance);
		TiffDirectory directory = (TiffDirectory) contents.directories.get(0);

		TiffField widthField = directory.findField(TIFF_TAG_IMAGE_WIDTH, true);
		TiffField heightField = directory
				.findField(TIFF_TAG_IMAGE_LENGTH, true);

		if ((widthField == null) || (heightField == null))
			throw new ImageReadException("TIFF image missing size info.");

		int height = heightField.getIntValue();
		int width = widthField.getIntValue();

		//-------------------

		TiffField resolutionUnitField = directory
				.findField(TIFF_TAG_RESOLUTION_UNIT);
		int resolutionUnit = 2; // Inch
		if ((resolutionUnitField != null)
				&& (resolutionUnitField.getValue() != null))
			resolutionUnit = resolutionUnitField.getIntValue();

		double unitsPerInch = -1;
		switch (resolutionUnit)
		{
			case 1 :
				break;
			case 2 : // Inch
				unitsPerInch = 1.0;
				break;
			case 3 : // Meter
				unitsPerInch = 0.0254;
				break;
			default :
				break;

		}
		TiffField xResolutionField = directory.findField(TIFF_TAG_XRESOLUTION);
		TiffField yResolutionField = directory.findField(TIFF_TAG_YRESOLUTION);

		int physicalWidthDpi = -1;
		float physicalWidthInch = -1;
		int physicalHeightDpi = -1;
		float physicalHeightInch = -1;

		if (unitsPerInch > 0)
		{
			if ((xResolutionField != null)
					&& (xResolutionField.getValue() != null))
			{
				double XResolutionPixelsPerUnit = xResolutionField
						.getDoubleValue();
				physicalWidthDpi = (int) (XResolutionPixelsPerUnit / unitsPerInch);
				physicalWidthInch = (float) (width / (XResolutionPixelsPerUnit * unitsPerInch));
			}
			if ((yResolutionField != null)
					&& (yResolutionField.getValue() != null))
			{
				double YResolutionPixelsPerUnit = yResolutionField
						.getDoubleValue();
				physicalHeightDpi = (int) (YResolutionPixelsPerUnit / unitsPerInch);
				physicalHeightInch = (float) (height / (YResolutionPixelsPerUnit * unitsPerInch));
			}
		}

		//-------------------

		TiffField bitsPerSampleField = directory
				.findField(TIFF_TAG_BITS_PER_SAMPLE);

		int bitsPerSample = -1;
		if ((bitsPerSampleField != null)
				&& (bitsPerSampleField.getValue() != null))
			bitsPerSample = bitsPerSampleField.getIntValueOrArraySum();

		int bitsPerPixel = bitsPerSample; // assume grayscale;
		// dunno if this handles colormapped images correctly.

		//-------------------

		ArrayList comments = new ArrayList();
		ArrayList entries = directory.entries;
		for (int i = 0; i < entries.size(); i++)
		{
			TiffField field = (TiffField) entries.get(i);
			String comment = field.toString();
			comments.add(comment);
		}

		ImageFormat format = ImageFormat.IMAGE_FORMAT_TIFF;
		String formatName = "TIFF Tag-based Image File Format";
		String mimeType = "image/tiff";
		int numberOfImages = contents.directories.size();
		// not accurate ... only reflects first
		boolean isProgressive = false;
		// is TIFF ever interlaced/progressive?

		String formatDetails = "Tiff v." + contents.header.tiffVersion;

		boolean isTransparent = false; // TODO: wrong
		boolean usesPalette = false;
		TiffField colorMapField = directory.findField(TIFF_TAG_COLOR_MAP);
		if (colorMapField != null)
			usesPalette = true;

		int colorType = ImageInfo.COLOR_TYPE_RGB;

		int compression = directory.findField(TIFF_TAG_COMPRESSION)
				.getIntValue();
		String compressionAlgorithm;

		switch (compression)
		{
			case TIFF_COMPRESSION_UNCOMPRESSED_1 :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_NONE;
				break;
			case TIFF_COMPRESSION_CCITT_1D :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_CCITT_1D;
				break;
			case TIFF_COMPRESSION_CCITT_GROUP_3 :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_CCITT_GROUP_3;
				break;
			case TIFF_COMPRESSION_CCITT_GROUP_4 :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_CCITT_GROUP_4;
				break;
			case TIFF_COMPRESSION_LZW :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_LZW;
				break;
			case TIFF_COMPRESSION_JPEG :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_JPEG;
				break;
			case TIFF_COMPRESSION_UNCOMPRESSED_2 :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_NONE;
				break;
			case TIFF_COMPRESSION_PACKBITS :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_PACKBITS;
				break;
			default :
				compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_UNKNOWN;
				break;
		}

		ImageInfo result = new ImageInfo(formatDetails, bitsPerPixel, comments,
				format, formatName, height, mimeType, numberOfImages,
				physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
				physicalWidthInch, width, isProgressive, isTransparent,
				usesPalette, colorType, compressionAlgorithm);

		return result;
	}

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

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

				imageData.toString(pw, "");
			}

			pw.println("");

			//		try
			{
				FormatCompliance formatCompliance = FormatCompliance
						.getDefault();
				Map params = null;
				TiffContents contents = new TiffReader(true).readContents(
						byteSource, params, formatCompliance);

				ArrayList directories = contents.directories;

				if (directories == null)
					return false;

				for (int d = 0; d < directories.size(); d++)
				{
					TiffDirectory directory = (TiffDirectory) directories
							.get(d);

					ArrayList entries = directory.entries;

					if (entries == null)
						return false;

					//					Debug.debug("directory offset", directory.offset);

					for (int i = 0; i < entries.size(); i++)
					{
						TiffField field = (TiffField) entries.get(i);

						field.dump(pw, d + "");
					}
				}

				pw.println("");
			}
			//		catch (Exception e)
			//		{
			//			Debug.debug(e);
			//			pw.println("");
			//			return false;
			//		}

			return true;
		}
		finally
		{
			pw.println("");
		}
	}

	public FormatCompliance getFormatCompliance(ByteSource byteSource)
			throws ImageReadException, IOException
	{
		FormatCompliance formatCompliance = FormatCompliance.getDefault();
		Map params = null;
		new TiffReader(isStrict(params)).readContents(byteSource, params, formatCompliance);
		return formatCompliance;
	}

	public void writeImage(BufferedImage src, OutputStream os, Map params)
			throws ImageWriteException, IOException
	{
		new TiffImageWriterLossy().writeImage(src, os, params);
	}

}