/**
 **
 ** ElphelTiffWriter - Write Tiff files w/o loci 
 **
 ** Copyright (C) 2023 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  ElphelTiffWriter.java 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 3 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, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */
package com.elphel.imagej.readers;

import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferFloat;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.PixelInterleavedSampleModel;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.plugins.tiff.TIFFDirectory;
import javax.imageio.plugins.tiff.TIFFTagSet;
import javax.imageio.plugins.tiff.ExifGPSTagSet;
import javax.imageio.plugins.tiff.ExifParentTIFFTagSet;
import javax.imageio.plugins.tiff.TIFFField;
import org.w3c.dom.Node;

import com.elphel.imagej.ims.Did_ins;

import ij.ImagePlus;

public class ElphelTiffWriter {
	public static String TIFF_METADATA_FORMAT =             "javax_imageio_tiff_image_1.0";
	public static String TIFF_FIELD_TAG =                   "TIFFField";
	public static String TIFF_ASCIIS_TAG =                  "TIFFAsciis";
	public static String TIFF_ASCII_TAG =                   "TIFFAscii";
	public static String TIFF_BYTES_TAG =                   "TIFFBytes";
	public static String TIFF_BYTE_TAG =                    "TIFFByte";
	public static String TIFF_SHORTS_TAG =                  "TIFFShorts";
	public static String TIFF_SHORT_TAG =                   "TIFFShort";
	public static String TIFF_RATIONALS_TAG =               "TIFFRationals";
	public static String TIFF_RATIONAL_TAG =                "TIFFRational";
	public static String TIFF_FIELD_VALUE_ATTRIBUTE =       "value";
	public static String TIFF_FIELD_DESCRIPTION_ATTRIBUTE = "description";
	
	
	public static void saveTiffARGBOrGray32(
			ImagePlus     imp,
			String        path, // full path to save image
			double []     lla,  // latitude, longitude, altitude (or null)
			double []     xy_center, // X,Y of top left to vertical (negative meters)
			LocalDateTime dt,   // local date/time or null
			double        pix_in_meters, // resolution or Double.NaN
			boolean       imageJTags, // later will use it to encode from properties
			int           debugLevel
			) {
	    File outputFile = new File(path);
	    if (debugLevel > -3) {
	    	System.out.println("ElphelTiffWriter.saveTiffARGB32(): will write to "+outputFile);
	    }
	    ImageWriter tiffWriter = ImageIO.getImageWritersByFormatName("tiff").next();
	    ImageWriteParam tiffWriteParam = tiffWriter.getDefaultWriteParam();
//	    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// https://docs.oracle.com/en/java/javase/17/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html	    
//	    tiffWriteParam.setCompressionType("Exif JPEG");
	    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
	    BufferedImage bufferedImageAlpha;
    	BufferedImage bufferedImage = (BufferedImage) imp.getImage(); // properties=null
	    if (imp.getType()==ImagePlus.COLOR_RGB) {
	    	/* ImagePlus image removes alpha, so we need to create a new ARGB BufferedImage
	    	 * and copy data (it is correct in imp.getImage() */
	    	bufferedImageAlpha = new BufferedImage(
	    			bufferedImage.getWidth(),
	    			bufferedImage.getHeight(),
	    			BufferedImage.TYPE_INT_ARGB);
	    	int [] ipixels = (int[])imp.getProcessor().getPixels();
	    	final int[] bia_as_arr = ( (DataBufferInt) bufferedImageAlpha.getRaster().getDataBuffer() ).getData();
	    	System.arraycopy(ipixels, 0, bia_as_arr, 0, bia_as_arr.length);
	    } else {
	    	bufferedImageAlpha = createFloatBufferedImage(
	    			bufferedImage.getWidth(),
	    			bufferedImage.getHeight()
	    			);
	    	float [] pixels = (float[]) imp.getProcessor().getPixels();
	    	final float[] bia_as_arr = ( (DataBufferFloat) bufferedImageAlpha.getRaster().getDataBuffer() ).getData();
	    	System.arraycopy(pixels, 0, bia_as_arr, 0, bia_as_arr.length);
	    }
	    ImageTypeSpecifier imageType = ImageTypeSpecifier.createFromRenderedImage(bufferedImageAlpha);            
	    IIOMetadata imageMetadata = tiffWriter.getDefaultImageMetadata(imageType, tiffWriteParam);
	    if (debugLevel > 0) {System.out.println("\n=== imageMetadata ===:");DumpImageMetadata.dumpMetadata(imageMetadata);}
// just for testing:
	    int cm_dig_after = 9;
	    // Here is a way adding tag sets through TIFFDirectory
	    /*
	    TIFFDirectory directory = null;
	    try {
			directory = TIFFDirectory.createFromMetadata(imageMetadata);
		} catch (IIOInvalidTreeException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		}
	    TIFFTagSet tag_set = ExifGPSTagSet.getInstance();
	    TIFFTagSet eptts =   ExifParentTIFFTagSet.getInstance();
	    // Try changing node attributes w/o need for directory
	    directory.addTagSet(tag_set); // ExifGPSTagSet.getInstance());
	    directory.addTagSet(eptts);   // ExifParentTIFFTagSet.getInstance());
	    //ExifGPSTagSet
	    imageMetadata = directory.getAsMetadata();
	    */
	    
	    IIOMetadataNode root_new = (IIOMetadataNode) imageMetadata.getAsTree(TIFF_METADATA_FORMAT);//?
	    if (debugLevel > 0) {System.out.println("\n=== root_new ===:");DumpImageMetadata.displayMetadataNode(root_new, 1);}
	    IIOMetadataNode ifd =    (IIOMetadataNode)  root_new.getFirstChild();
	    ifd.setAttribute("tagSets",
	    		"javax.imageio.plugins.tiff.BaselineTIFFTagSet,javax.imageio.plugins.tiff.ExifGPSTagSet,javax.imageio.plugins.tiff.ExifParentTIFFTagSet");
	    if ((lla != null) || (dt!=null)) { 
	    	IIOMetadataNode gpsRootNode = createGpsMetadata(lla, dt);
	    	if (debugLevel > 0) {System.out.println("\n=== gpsRootNode ===:");DumpImageMetadata.displayMetadataNode(gpsRootNode, 1);}
	    	ifd.appendChild(gpsRootNode);
	    }
	    addResolutionMetadata(ifd,pix_in_meters,cm_dig_after); // NOP if pix_in_meters is NaN 
	    addXYPositionMetadata(ifd,xy_center,3);
	    if (debugLevel > 0) {
	    	System.out.println("\n=== ifd ===:");DumpImageMetadata.displayMetadataNode(ifd, 1);
	    	System.out.println("\n=== root_new ===:"); DumpImageMetadata.displayMetadataNode(root_new, 1);
	    	System.out.println("\n=== imageMetadata - before merge ===:"); DumpImageMetadata.displayMetadataNode(imageMetadata.getAsTree(TIFF_METADATA_FORMAT), 1);
	    }
		try {
			imageMetadata.mergeTree(TIFF_METADATA_FORMAT, root_new); // dimension); // Root must have "TIFFIFD" child
		} catch (IIOInvalidTreeException e1) {
			e1.printStackTrace();
			return;
		}
	    
		if (debugLevel > 0) {System.out.println("\n=== imageMetadata - after merge ===:");DumpImageMetadata.displayMetadataNode(imageMetadata.getAsTree(TIFF_METADATA_FORMAT), 1);}
	    FileImageOutputStream outputStream;
	    try {
			outputStream = new FileImageOutputStream(outputFile);
		} catch (IOException e1) {
			e1.printStackTrace();
			return;
		}		
	    tiffWriter.setOutput(outputStream);
	    IIOImage image_with_metadata = new IIOImage(bufferedImageAlpha, null, imageMetadata);
	    System.out.println();
        try {
			tiffWriter.write(
					null, // tiffStreamMetadata,
					image_with_metadata,
			        tiffWriteParam);
		} catch (IOException e) {
			e.printStackTrace();
		}
	    System.out.println();
	}
	
	private static BufferedImage createFloatBufferedImage(
			int width,
			int height
			) {
		//https://stackoverflow.com/questions/26766751/java-create-bufferedimage-with-float-precision/26775056#26775056	
//        int bands = 4; // 4 bands for ARGB, 3 for RGB etc
//        int[] bandOffsets = {0, 1, 2, 3}; // length == bands, 0 == R, 1 == G, 2 == B and 3 == A
        int bands = 1; // 4 bands for ARGB, 3 for RGB etc
        int[] bandOffsets = {0}; // length == bands, 0 == R, 1 == G, 2 == B and 3 == A
        // Create a TYPE_FLOAT sample model (specifying how the pixels are stored)
//        SampleModel sampleModel0 = new PixelInterleavedSampleModel(
//        		DataBuffer.TYPE_FLOAT,
//        		width,
//        		height,
//        		bands,
//        		width  * bands,
//        		bandOffsets);

        // Create a color model compatible with this sample model/raster (TYPE_FLOAT)
        // Note that the number of bands must equal the number of color components in the 
        // color space (3 for RGB) + 1 extra band if the color model contains alpha 
        ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); // CS_sRGB);
//        ColorModel colorModel = new ComponentColorModel(colorSpace, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_FLOAT);
        ColorModel colorModel = new ComponentColorModel(
        		colorSpace,
        		false, // true,
        		false,
        		Transparency.TRANSLUCENT, // ?
        		DataBuffer.TYPE_FLOAT);
        SampleModel sampleModel = colorModel.createCompatibleSampleModel (width, height);       
        // ...and data buffer (where the pixels are stored)
        DataBuffer buffer = new DataBufferFloat(width * height * bands);
        // Wrap it in a writable raster
        WritableRaster raster = Raster.createWritableRaster(sampleModel, buffer, null);

        // And finally create an image with this raster
        BufferedImage image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
        return image;
        
		
/*
 * color space (ColorSpace.CS_GRAY) and no transparency may make more sense.
 public class FloatImage {
    public static void main(String[] args) {
        // Define dimensions and layout of the image
        int w = 300;
        int h = 200;
        int bands = 4; // 4 bands for ARGB, 3 for RGB etc
        int[] bandOffsets = {0, 1, 2, 3}; // length == bands, 0 == R, 1 == G, 2 == B and 3 == A

        // Create a TYPE_FLOAT sample model (specifying how the pixels are stored)
        SampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_FLOAT, w, h, bands, w  * bands, bandOffsets);
        // ...and data buffer (where the pixels are stored)
        DataBuffer buffer = new DataBufferFloat(w * h * bands);

        // Wrap it in a writable raster
        WritableRaster raster = Raster.createWritableRaster(sampleModel, buffer, null);

        // Create a color model compatible with this sample model/raster (TYPE_FLOAT)
        // Note that the number of bands must equal the number of color components in the 
        // color space (3 for RGB) + 1 extra band if the color model contains alpha 
        ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
        ColorModel colorModel = new ComponentColorModel(colorSpace, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_FLOAT);

        // And finally create an image with this raster
        BufferedImage image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);

        System.out.println("image = " + image);
    }
}


 */
	}
	
	
	private static IIOMetadataNode createRationalDegrees(double d, int digits_after) { // 8
		int denom = 1;
		for (int i = 0; i < digits_after; i++) denom *= 10;
		int degs =  (int) Math.floor(d);
		d = 60* (d - degs);
		int mins =  (int) Math.floor(d);
		d = 60* (d - mins);
		int fsec =  (int) Math.round(d*denom);
	    IIOMetadataNode node_rationals = new IIOMetadataNode(TIFF_RATIONALS_TAG);
	    IIOMetadataNode node_degs =      new IIOMetadataNode(TIFF_RATIONAL_TAG);
	    node_degs.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, degs+"/1");
	    IIOMetadataNode node_mins =      new IIOMetadataNode(TIFF_RATIONAL_TAG);
	    node_mins.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, mins+"/1");
	    IIOMetadataNode node_secs =      new IIOMetadataNode(TIFF_RATIONAL_TAG);
	    node_secs.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, fsec+"/"+denom);
	    node_rationals.appendChild(node_degs);
	    node_rationals.appendChild(node_mins);
	    node_rationals.appendChild(node_secs);
	    return node_rationals;
	}
	
	private static IIOMetadataNode createRationalMeters(double d, int digits_after) { // 3
		int denom = 1;
		for (int i = 0; i < digits_after; i++) denom *= 10;
		int fmeters =  (int) Math.round(d*denom);
	    IIOMetadataNode node_rationals = new IIOMetadataNode(TIFF_RATIONALS_TAG);
	    IIOMetadataNode node_meters =      new IIOMetadataNode(TIFF_RATIONAL_TAG);
	    node_meters.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, fmeters+"/"+denom);
	    node_rationals.appendChild(node_meters);
	    return node_rationals;
	}

	private static IIOMetadataNode createTimeStamp(LocalDateTime dt, int digits_after) { // 3
		int denom = 1;
		for (int i = 0; i < digits_after; i++) denom *= 10;
		int fsec = dt.getSecond()*denom+((int) Math.round(denom * (dt.getNano()*1E-9)));
		
	    IIOMetadataNode node_rationals = new IIOMetadataNode(TIFF_RATIONALS_TAG);
	    IIOMetadataNode node_hrs =       new IIOMetadataNode(TIFF_RATIONAL_TAG);
	    node_hrs.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, dt.getHour()+"/1");
	    IIOMetadataNode node_mins =      new IIOMetadataNode(TIFF_RATIONAL_TAG);
	    node_mins.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, dt.getMinute()+"/1");
	    IIOMetadataNode node_secs =      new IIOMetadataNode(TIFF_RATIONAL_TAG);
	    node_secs.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, fsec+"/"+denom);
	    node_rationals.appendChild(node_hrs);
	    node_rationals.appendChild(node_mins);
	    node_rationals.appendChild(node_secs);
	    return node_rationals;
	}
	
	private static IIOMetadataNode createDateStamp(LocalDateTime dt) {
	    IIOMetadataNode node_date = new IIOMetadataNode(TIFF_ASCIIS_TAG);
	    IIOMetadataNode node_date_ascii = new IIOMetadataNode(TIFF_ASCII_TAG);
	    node_date_ascii.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE,
	    		String.format("%04d:%02d:%02d", dt.getYear(), dt.getMonthValue(), dt.getDayOfMonth()));
	    node_date.appendChild(node_date_ascii);
	    return node_date;
	}
	
	private static IIOMetadataNode createGpsMetadata(
			double [] lla,
			LocalDateTime dt) {
		int ang_sec_after = 5;
		int meters_after =  3;
		int seconds_after = 3;
		
		// WGS84 latitude, longitude, height above ellipsoid (degrees,degrees,meters)
	    IIOMetadataNode gpsRootNode = new IIOMetadataNode("TIFFIFD");
	    gpsRootNode.setAttribute("parentTagNumber", "34853");
	    gpsRootNode.setAttribute("parentTagName",   "GPSInfoIFDPointer");
	    gpsRootNode.setAttribute("tagSets", "javax.imageio.plugins.tiff.ExifGPSTagSet");
        boolean lat_south, long_west, alt_down;
        double lat_abs, long_abs, alt_abs;
        if (lla != null) {
        	lat_south = lla[0] < 0;
        	lat_abs = Math.abs(lla[0]);
        	long_west = lla[1] < 0;
        	long_abs = Math.abs(lla[1]);
        	alt_down = lla[2] < 0;
        	alt_abs = Math.abs(lla[2]);
        	// Latitude
        	IIOMetadataNode node_lat_ref = new IIOMetadataNode(TIFF_FIELD_TAG);
        	node_lat_ref.setAttribute("number", "1");
        	node_lat_ref.setAttribute("name", "GPSLatitudeRef");
        	IIOMetadataNode node_lat_ref_asciis = new IIOMetadataNode(TIFF_ASCIIS_TAG);
        	IIOMetadataNode node_lat_ref_ascii = new IIOMetadataNode(TIFF_ASCII_TAG);
        	node_lat_ref_ascii.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, lat_south?"S":"N");
        	node_lat_ref_asciis.appendChild(node_lat_ref_ascii);
        	node_lat_ref.appendChild(node_lat_ref_asciis);
        	gpsRootNode.appendChild(node_lat_ref);
        	IIOMetadataNode node_lat =  new IIOMetadataNode(TIFF_FIELD_TAG);
        	node_lat.setAttribute("number", "2");
        	node_lat.setAttribute("name", "GPSLatitude");
        	node_lat.appendChild(createRationalDegrees(lat_abs, ang_sec_after));
        	gpsRootNode.appendChild(node_lat);
        	// Longitude
        	IIOMetadataNode node_long_ref = new IIOMetadataNode(TIFF_FIELD_TAG);
        	node_long_ref.setAttribute("number", "3");
        	node_long_ref.setAttribute("name", "GPSLongitudeRef");
        	IIOMetadataNode node_long_ref_asciis = new IIOMetadataNode(TIFF_ASCIIS_TAG);
        	IIOMetadataNode node_long_ref_ascii = new IIOMetadataNode(TIFF_ASCII_TAG);
        	node_long_ref_ascii.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, long_west?"W":"E");
        	node_long_ref_asciis.appendChild(node_long_ref_ascii);
        	node_long_ref.appendChild(node_long_ref_asciis);
        	gpsRootNode.appendChild(node_long_ref);
        	IIOMetadataNode node_long =  new IIOMetadataNode(TIFF_FIELD_TAG);
        	node_long.setAttribute("number", "4");
        	node_long.setAttribute("name", "GPSLongitude");
        	node_long.appendChild(createRationalDegrees(long_abs, ang_sec_after));
        	gpsRootNode.appendChild(node_long);
        	// Altitude
        	IIOMetadataNode node_alt_ref = new IIOMetadataNode(TIFF_FIELD_TAG);
        	node_alt_ref.setAttribute("number", "5");
        	node_alt_ref.setAttribute("name", "GPSAltitudeRef");
        	IIOMetadataNode node_alt_ref_asciis = new IIOMetadataNode(TIFF_BYTES_TAG);
        	IIOMetadataNode node_alt_ref_ascii =  new IIOMetadataNode(TIFF_BYTE_TAG);
        	node_alt_ref_ascii.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, alt_down?"1":"0");
        	node_alt_ref_ascii.setAttribute(TIFF_FIELD_DESCRIPTION_ATTRIBUTE, "Sea level");
        	node_alt_ref_asciis.appendChild(node_alt_ref_ascii);
        	node_alt_ref.appendChild(node_alt_ref_asciis);
        	gpsRootNode.appendChild(node_alt_ref);
        	IIOMetadataNode node_alt =  new IIOMetadataNode(TIFF_FIELD_TAG);
        	node_alt.setAttribute("number", "6");
        	node_alt.setAttribute("name", "GPSAltitude");
        	node_alt.appendChild(createRationalMeters(alt_abs, meters_after));
        	gpsRootNode.appendChild(node_alt);
        }
	    if (dt != null) {
		    // Time
		    IIOMetadataNode node_time = new IIOMetadataNode(TIFF_FIELD_TAG);
		    node_time.setAttribute("number", "7");
		    node_time.setAttribute("name", "GPSTimeStamp");
		    node_time.appendChild(createTimeStamp(dt,seconds_after));
		    gpsRootNode.appendChild(node_time);
		    // Date
		    IIOMetadataNode node_date = new IIOMetadataNode(TIFF_FIELD_TAG);
		    node_date.setAttribute("number", "29");
		    node_date.setAttribute("name", "GPSDateStamp");
		    node_date.appendChild(createDateStamp(dt));
		    gpsRootNode.appendChild(node_date);
	    }
	    return gpsRootNode;
	}
	
	private static void addResolutionMetadata(
			IIOMetadataNode ifd,
			double          resolutionPixelInMeters, // NaN for undefined
			int             digits_after) {
		if (Double.isNaN(resolutionPixelInMeters)) {
			return;
		}
		int denom = 1;
		for (int i = 0; i < digits_after; i++) denom *= 10;
		int fppcm = (int) Math.round(denom * 0.01/resolutionPixelInMeters); // pixels per cm 
		
	    IIOMetadataNode node_resolution_unit = new IIOMetadataNode(TIFF_FIELD_TAG);
	    node_resolution_unit.setAttribute("number", "296");
	    node_resolution_unit.setAttribute("name", "ResolutionUnit");
    	IIOMetadataNode node_ru_shorts = new IIOMetadataNode(TIFF_SHORTS_TAG);
    	IIOMetadataNode node_ru_short =  new IIOMetadataNode(TIFF_SHORT_TAG);
    	node_ru_short.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, "3"); // cm
    	node_ru_short.setAttribute(TIFF_FIELD_DESCRIPTION_ATTRIBUTE, "Centimeter"); // cm
    	node_ru_shorts.appendChild(node_ru_short);
    	node_resolution_unit.appendChild(node_ru_shorts);
    	ifd.appendChild(node_resolution_unit);
    	
	    IIOMetadataNode node_x_resolution = new IIOMetadataNode(TIFF_FIELD_TAG);
	    node_x_resolution.setAttribute("number", "282");
	    node_x_resolution.setAttribute("name", "XResolution");
    	IIOMetadataNode node_x_rationals = new IIOMetadataNode(TIFF_RATIONALS_TAG);
    	IIOMetadataNode node_x_rational =  new IIOMetadataNode(TIFF_RATIONAL_TAG);
    	node_x_rational.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, fppcm+"/"+denom);
    	node_x_rationals.appendChild(node_x_rational);
    	node_x_resolution.appendChild(node_x_rationals);
    	ifd.appendChild(node_x_resolution);
    	
	    IIOMetadataNode node_y_resolution = new IIOMetadataNode(TIFF_FIELD_TAG);
	    node_y_resolution.setAttribute("number", "283");
	    node_y_resolution.setAttribute("name", "YResolution");
    	IIOMetadataNode node_y_rationals = new IIOMetadataNode(TIFF_RATIONALS_TAG);
    	IIOMetadataNode node_y_rational =  new IIOMetadataNode(TIFF_RATIONAL_TAG);
    	node_y_rational.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, fppcm+"/"+denom);
    	node_y_rationals.appendChild(node_y_rational);
    	node_y_resolution.appendChild(node_y_rationals);
    	ifd.appendChild(node_y_resolution);
	}
	
	private static void addXYPositionMetadata(
			IIOMetadataNode ifd,
			double []       xy_position, // both should be negative
			int             digits_after) {
		if (xy_position == null) {
			return;
		}
		double xpos = Math.max(-xy_position[0] * 100, 0); // cm, prevent negative rationals, use 0 if negative 
		double ypos = Math.max(-xy_position[1] * 100, 0); // cm, prevent negative rationals, use 0 if negative 
		int denom = 1;
		for (int i = 0; i < digits_after; i++) denom *= 10;
		int ixpos = (int) Math.round(denom * xpos); // XPosition numerator
		int iypos = (int) Math.round(denom * ypos); // YPosition numerator
    	
	    IIOMetadataNode node_x_position = new IIOMetadataNode(TIFF_FIELD_TAG);
	    node_x_position.setAttribute("number", "286");
	    node_x_position.setAttribute("name", "XPosition");
    	IIOMetadataNode node_x_rationals = new IIOMetadataNode(TIFF_RATIONALS_TAG);
    	IIOMetadataNode node_x_rational =  new IIOMetadataNode(TIFF_RATIONAL_TAG);
    	node_x_rational.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, ixpos+"/"+denom);
    	node_x_rationals.appendChild(node_x_rational);
    	node_x_position.appendChild(node_x_rationals);
    	ifd.appendChild(node_x_position);
    	
	    IIOMetadataNode node_y_position = new IIOMetadataNode(TIFF_FIELD_TAG);
	    node_y_position.setAttribute("number", "287");
	    node_y_position.setAttribute("name", "YPosition");
    	IIOMetadataNode node_y_rationals = new IIOMetadataNode(TIFF_RATIONALS_TAG);
    	IIOMetadataNode node_y_rational =  new IIOMetadataNode(TIFF_RATIONAL_TAG);
    	node_y_rational.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, iypos+"/"+denom);
    	node_y_rationals.appendChild(node_y_rational);
    	node_y_position.appendChild(node_y_rationals);
    	ifd.appendChild(node_y_position);
	}
	
	
	/*
	 * 
	FileImageOutputStream outputStream = new FileImageOutputStream(outputFile);
	  writer.setOutput(outputStream);
	  IIOImage image = new IIOImage(outputImage, null, null);
	  writer.write(null, image, imageWriteParam);
	  writer.dispose();
	 * 
	 * 
	 * 
	image	BufferedImage  (id=191)	
		accelerationPriority	0.5	
		colorModel	DirectColorModel  (id=192)	
		imageType	1	
		osis	null	
		properties	null	
		raster	IntegerInterleavedRaster  (id=196)	
		surfaceManager	null	
			
	 */
/*
	public static void saveTiffARGB32(
			ImagePlus imp,
			String path,
			boolean imageJTags,
			int debugLevel
			) throws IOException, FormatException, ServiceException, DependencyException{
		int IFDImageFullWidth= 0x8214; // not defined in loci.formats.tiff.IFD
		int IFDImageFullLength=0x8215; // not defined in loci.formats.tiff.IFD
		//		public static final int META_DATA_BYTE_COUNTS = 50838; // private tag registered with Adobe
		//		public static final int META_DATA = 50839; // private tag registered with Adobe

		int IFDImageJByteCounts= 0xc696; // was array {12( if no slices, roi, etc.), bytes in info}
		int IFDImageJInfo=       0xc697; // ImageJ info, starting with magic IJIJinfo,
		byte [] ImageJInfoMagic={73,74,73,74,105,110,102,111,0,0,0,1};

		int pixelsDenominator=1000;
		String description=(imp.getProperty("description")!=null)?((String) imp.getProperty("description")):"Elphel Eyesis4pi";
		int [] iPixels= (int []) imp.getProcessor().getPixels();
		byte [] bytes=new byte[iPixels.length*4];
		int pIndex=0;
		for (int i=0;i<iPixels.length;i++){
			bytes[pIndex++]=(byte) ((iPixels[i]>>16)& 0xff); // R
			bytes[pIndex++]=(byte) ((iPixels[i]>> 8)& 0xff); // G
			bytes[pIndex++]=(byte) ((iPixels[i]>> 0)& 0xff); // B
			bytes[pIndex++]=(byte) ((iPixels[i]>>24)& 0xff); // alpha
		}
		IFD ifd=new IFD();
		ifd.put(IFD.LITTLE_ENDIAN, false);
		//        ifd.put(new Integer(IFD.LITTLE_ENDIAN), new Boolean(true));
		ifd.put(IFD.IMAGE_WIDTH, imp.getWidth());
		ifd.put(IFD.IMAGE_LENGTH, imp.getHeight());
		ifd.put(IFD.SAMPLES_PER_PIXEL, 4);
		ifd.putIFDValue(IFD.SOFTWARE, "Elphel Eyesis");
		ifd.putIFDValue(IFD.IMAGE_DESCRIPTION, description);
		// copy some other data?
		ifd.putIFDValue(IFD.COMPRESSION, 1); //TiffCompression.UNCOMPRESSED);
		ifd.putIFDValue(IFD.PHOTOMETRIC_INTERPRETATION,2); // RGB
		ifd.putIFDValue(IFD.EXTRA_SAMPLES,2); // extra 2 bytes (over 3) meaning Unassociated alpha data
//		ifd.putIFDValue(IFD.EXTRA_SAMPLES,1); // extra 1 byte (over 3) meaning Associated alpha data
		ifd.putIFDValue(IFD.SAMPLE_FORMAT,1); // 
		//        int [] bpsArray={8,8,8,8};
		//        ifd.putIFDValue(IFD.BITS_PER_SAMPLE, bpsArray); // will be done automatically
		if (imp.getProperty("XPosition")!=null) {
			ifd.putIFDValue(IFD.X_POSITION,
					new TiffRational((int) Math.round(pixelsDenominator*Double.parseDouble((String) imp.getProperty("XPosition"))) , pixelsDenominator));
		}
		if (imp.getProperty("YPosition")!=null) {
			ifd.putIFDValue(IFD.Y_POSITION,
					new TiffRational((int) Math.round(pixelsDenominator*Double.parseDouble((String) imp.getProperty("YPosition"))) , pixelsDenominator));
		}
		if (imp.getProperty("ImageFullWidth")!=null){
			ifd.putIFDValue(IFDImageFullWidth, 	(long) Integer.parseInt((String) imp.getProperty("ImageFullWidth")));
		}
		if (imp.getProperty("ImageFullLength")!=null){
			ifd.putIFDValue(IFDImageFullLength, (long) Integer.parseInt((String) imp.getProperty("ImageFullLength")));
		}
		//TODO: Seems to match ImageJ Info, but it is not recognized :-(
		if (imageJTags &&  (imp.getProperty("Info")!=null) && (imp.getProperty("Info") instanceof String)){
			int skipFirstBytes=2;
			String info=(String) imp.getProperty("Info");
			byte [] bInfoBody=info.getBytes("UTF-16");
			int [] bInfo = new int [ImageJInfoMagic.length+bInfoBody.length-skipFirstBytes];
			int index=0;
			for (int i=0;i<ImageJInfoMagic.length;i++) bInfo[index++]=ImageJInfoMagic[i];
			for (int i=skipFirstBytes;i<bInfoBody.length;      i++) bInfo[index++]=bInfoBody[i]; // first 2 bytes {-2, -1} ???
			long [] imageJcounts={12, bInfoBody.length-skipFirstBytes};
			ifd.putIFDValue(IFDImageJByteCounts, imageJcounts);
			ifd.putIFDValue(IFDImageJInfo, bInfo);
		}
		(new File(path)).delete(); // Otherwise TiffSaver appends!
		TiffSaver tiffSaver = new TiffSaver(path);
		tiffSaver.setWritingSequentially(true);
		tiffSaver.setLittleEndian(false);
		tiffSaver.writeHeader();
		//        tiffSaver.writeIFD(ifd,0); //* SHould not write here, some fields are calculated during writeImage, that writes IFD too
		System.out.println("bytes.length="+bytes.length);
		tiffSaver.writeImage(bytes,
				ifd,
				0, //int no,
				0, //int pixelType:INT8, INT16, INT32, UINT8, UINT16, UINT32, FLOAT, BIT, DOUBLE, COMPLEX, DOUBLECOMPLEX; - used to find bpp
				true); // boolean last)
	}

 */
}
