
import com.drew.lang.Rational;
import com.drew.metadata.exif.ExifDirectory;
import se.datadosen.imaging.exif.ImageInfoFormatter;
import se.datadosen.jalbum.AlbumBean;
import se.datadosen.util.SmartResourceBundle;

import java.util.*;

public class PhotographicMetaInformation extends Base
{
	//private ImageInfoFormatter formatter = new ImageInfoFormatter();

	public PhotographicMetaInformation(AlbumBean engine)
	{
		super(engine);
	}

	protected String transformMetadataValue(String key, Object val)
	{
		if( isEmptyString(val) )
			return "";

		if( key.equalsIgnoreCase("Flash") )
		{
			try
			{
				Double.parseDouble(val.toString());
			}
			catch( NumberFormatException e )
			{
				return val.toString();
			}
			double flash = convertToDouble(val);

			if (flash == 0x0000)
			  return "Flash did not fire";
			else if (flash == 0x0001)
			  return "Flash fired";
			else if (flash == 0x0005)
			  return "Strobe return light not detected";
			else if (flash == 0x0007)
			  return "Strobe return light detected";
			else if (flash == 0x0009)
			  return "Flash fired, compulsory flash mode";
			else if (flash == 0x000D)
			  return "Flash fired, compulsory flash mode, return light not detected";
			else if (flash == 0x000F)
			  return "Flash fired, compulsory flash mode, return light detected";
			else if (flash == 0x0010)
			  return "Flash did not fire, compulsory flash mode";
			else if (flash == 0x0018)
			  return "Flash did not fire, auto mode";
			else if (flash == 0x0019)
			  return "Flash fired, auto mode";
			else if (flash == 0x001D)
			  return "Flash fired, auto mode, return light not detected";
			else if (flash == 0x001F)
			  return "Flash fired, auto mode, return light detected";
			else if (flash == 0x0020)
			  return "No flash function";
			else if (flash == 0x0041)
			  return "Flash fired, red-eye reduction mode";
			else if (flash == 0x0045)
			  return "Flash fired, red-eye reduction mode, return light not detected";
			else if (flash == 0x0047)
			  return "Flash fired, red-eye reduction mode, return light detected";
			else if (flash == 0x0049)
			  return "Flash fired, compulsory flash mode, red-eye reduction mode";
			else if (flash == 0x004D)
			  return "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected";
			else if (flash == 0x004F)
			  return "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected";
			else if (flash == 0x0059)
			  return "Flash fired, auto mode, red-eye reduction mode";
			else if (flash == 0x005D)
			  return "Flash fired, auto mode, return light not detected, red-eye reduction mode";
			else if (flash == 0x005F)
			  return "Flash fired, auto mode, return light detected, red-eye reduction mode";
			else
			  return "Unknown flash mode";
		}
		else if( key.equalsIgnoreCase("Canon Makernote.Subject Distance") )
		{
			int dist = convertToInteger(val);

			if (dist == 6553)
				return "Infinity";
			else
				return (dist/100.0) + "m";
		}
		else if( key.equalsIgnoreCase("Focal Length") )
		{
			int equivalent = calculateFocus35mm();

			if( equivalent!=0 )
				return val + " (35mm equivalent: " + equivalent + "mm)";
		}
		// This section added muttznutz, 2006-02-25
		// "Fix" for values of 1/100 sec, 1/50 sec etc
		else if(  key.equalsIgnoreCase("Exposure Time") )
		{
			// just change the items which haven't already been converted - normally in "0.nns" format
			if( val.toString().indexOf(".")!=-1 )
			{
				try
				{
					// convert the string to a numeric value
					Double tDbl = new Double((val.toString().substring(0, (val.toString()).length() - 4))) ;
					// convert to Rational - should work for exposures in excess of 1/10000 sec
					Rational tRat = new Rational((int)(tDbl.doubleValue() * 10000), 10000) ;
					// create formatter object
					ImageInfoFormatter formatter = new ImageInfoFormatter();
					// format the value
					String tFmt = (String)formatter.format(tRat, ExifDirectory.TAG_EXPOSURE_TIME);
					// show that it's in seconds
					val = tFmt.substring(0, tFmt.length() - 1) + " sec";
				}
				// catch is required but I'm not sure that this is the correct exception
				catch( Exception e )
				{
					log("Can not format the Exposure Time '" + val + "'!", e);
					//val = key.toString();
				}
			}
			// end of added lines
		}

		/*try
		{
			if( key.equalsIgnoreCase("Flash") )
				return formatter.format(val, ExifDirectory.TAG_FLASH).toString();
			else if( key.equalsIgnoreCase("Exposure Time") )
				return formatter.format(val, ExifDirectory.TAG_EXPOSURE_TIME).toString();
		}
		catch( Exception e )
		{
			log("Can not format the metadata information '" + key + "=" + val + "'!", e);
		}*/

		return val.toString();
	}

	// Remove the extra prefixes from the EXIF key for display!
	protected String transformMetadataKey(SmartResourceBundle cameraResource, String val)
	{
		try
		{
			return cameraResource.getString(val);
		}
		catch( MissingResourceException e )
		{
			String strippedVal;

			strippedVal = stripString(val, "Makernote.");
			strippedVal = stripString(strippedVal, "Iptc.");
			strippedVal = stripString(strippedVal, "Jpeg.");

			try
			{
				return cameraResource.getString(strippedVal);
			}
			catch( MissingResourceException ex )
			{
				return strippedVal;
			}
		}
	}

	private String stripString(String text, String strToStrip)
	{
		int pos = text.toLowerCase().indexOf(strToStrip.toLowerCase());

		if( pos!=-1 )
			return text.substring(pos + strToStrip.length());
		return text;
	}

	private String stripFirstChar(String str)
	{
		if( isEmptyString(str) || str.length()<2 )
			return "";
		else
			return str.substring(1);
	}

	private double getCameraInfo(String cameraModel, String informationName)
	{
		Properties prop = getFolderConfig(skinDirectory, Constants.SKIN_CAMERA_INFORMATION);
		String info = prop.getProperty(cameraModel + "." + informationName);

		return convertToDouble(info);
	}

	private String getCameraModel()
	{
		String model = (String) meta.get("Model");
		String make = (String) meta.get("Make");

		if( isEmptyString(make) )
			return model;
		else if( isEmptyString(model) )
			return make;

		if( model.startsWith(make) )
			return model;
		else
			return make + " " + model;
	}

	private int calculateFocus35mm()
	{
		double fl = convertToDouble(meta.get("Focal Length"));

		// There are 2 ways of computing the 35mm equivalent focus:
		// 1. Read the conversion parameters from cameras.properties
		String cameraModel = getCameraModel().trim();

		if( !isEmptyString(cameraModel) )
		{
			double multiplier = getCameraInfo(cameraModel, "focalLengthMultiplier");
			double offset = getCameraInfo(cameraModel, "focalLengthOffset");

			if( multiplier>0.0 )
				return (int)(fl * multiplier + offset + 0.5);
		}

		log("Camera model name or brand not found in cameras.properties: '" + cameraModel + "'");
		// 2. Use the Focal Plane X/Y resolution (if present)
		// Problem: if image is already rotated, the sensorWidth will return the
		//          actual sensorHeight and render the calculation invalid
		// Solution: take height if height > width
		if( exifImageHeight() > exifImageWidth() )
		{
			double sensorHeight = sensorHeight();

			if( sensorHeight>0.001 )
				return (int)(fl * 35.0 / sensorHeight + 0.5);
		}
		else
		{
			double sensorWidth = sensorWidth();

			if( sensorWidth>0.001 )
				return (int)(fl * 35.0 / sensorWidth + 0.5);
		}

		// Fallback:
		return 0;
	}

	// the following functions assume that the meta object exists.
	// this will ususally be the case, as this will only be called if it exists.
	private double exifImageWidth()
	{
		return exifImageSizeImpl(true);
	}

	private double exifImageHeight()
	{
		return exifImageSizeImpl(false);
	}

	private double exifImageSizeImpl(boolean isWidth)
	{
		String eis;

		//exif image size; complete string
		if( isWidth )
			eis = (String) meta.get("Exif Image Width");
		else
			eis = (String) meta.get("Exif Image Height");

		if( eis==null )
			return 0.0;

		//just the number
		StringTokenizer t = new StringTokenizer(eis);

		if( t.hasMoreTokens() )
			return convertToDouble( t.nextToken() );
		else
			return 0.0;
	}

	// return the width of the ccd in mm
	private double sensorWidth()
	{
		return sensorSizeImpl(true);
	}

	// return the height of the ccd in mm
	private double sensorHeight()
	{
		return sensorSizeImpl(false);
	}

	private double sensorSizeImpl(boolean isWidth)
	{
		String focalPlaneResolution;

		if( isWidth )
			focalPlaneResolution = (String) meta.get("Focal Plane X Resolution");
		else
			focalPlaneResolution = (String) meta.get("Focal Plane Y Resolution");

		if( focalPlaneResolution==null )
			return 0.0;

		// the resolution is a string, containing a fraction
		// [num]/[den] [unit]
		// for Canon camera's, [unit] is "inches".
		// Are there any camera's whose unit is "mm"?

		// decode the resolution string
		try
		{
			int slashIndex = focalPlaneResolution.indexOf('/');
			int spaceIndex = focalPlaneResolution.indexOf(' ', slashIndex==-1 ? 0 : slashIndex);
			double num, den;
			String unit;

			if( slashIndex>0 ) // "aaa/bbb unit" format
			{
				num = convertToDouble( focalPlaneResolution.substring(0, slashIndex) );
				den = convertToDouble( focalPlaneResolution.substring(slashIndex+1, spaceIndex) );
			}
			else // "aaa unit" format
			{
				num = 1;
				den = convertToDouble( focalPlaneResolution.substring(0, spaceIndex) );
			}

			unit = focalPlaneResolution.substring(spaceIndex + 1);

			double imageLength = isWidth ? exifImageWidth() : exifImageHeight();

			// convert from inches to mm, if needed. this is experimental
			if (unit.equalsIgnoreCase("mm"))
				return (num / den * imageLength);
			else if (unit.equalsIgnoreCase("cm"))
				return (num / den * 10 * imageLength);
			else // the default is "inches"
				return (num / den * 25.4 * imageLength);
		}
		catch( Exception e )
		{
			log("Error calculating the camera sensor size!", e);
			return 0.0;
		}
	}
}
