/**
 **
 ** 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.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
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_RATIONALS_TAG =   "TIFFRationals";
	public static String TIFF_RATIONAL_TAG =    "TIFFRational";
	public static String TIFF_FIELD_VALUE_ATTRIBUTE = "value";
	
	
	public static void saveTiffARGB32(
			ImagePlus imp,
			String path,
			boolean imageJTags,
			int debugLevel
			) {
//		ImageWriter     tiffWriter;
//	    ImageWriteParam tiffWriteParam;
	    IIOMetadata     tiffStreamMetadata;
	    IIOMetadata     primaryIFD;
	    BufferedImage   bufferedImage = (BufferedImage) imp.getImage(); // properties=null
	    BufferedImage   thumbnail;		
	    File outputFile = new File(path);
	    System.out.println("saveTiffARGB32(): will write to "+outputFile);
	    System.out.println("ElphelTiffWriter.saveTiffARGB32");
	       // Write only the primary IFD and image data.
	    // Specify uncompressed output.
	    ImageWriter tiffWriter = ImageIO.getImageWritersByFormatName("tiff").next();
	    ImageWriteParam tiffWriteParam = tiffWriter.getDefaultWriteParam();
//	    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
	    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
//	    tiffWriteParam.setSourceSubsampling(param.getSourceXSubsampling(), param.getSourceYSubsampling(), param.getSubsamplingXOffset(), param.getSubsamplingYOffset());
//	    tiffWriteParam.setSourceRegion(param.getSourceRegion());
//	    tiffWriteParam.setSourceBands(param.getSourceBands());
/*
 * https://docs.oracle.com/javase%2F9%2Fdocs%2Fapi%2F%2F/javax/imageio/metadata/doc-files/tiff_metadata.html
    Setting up the image metadata to write to a TIFF stream may be simplified by using
    the TIFFDirectory class which represents a TIFF IFD. A field in a TIFF IFD is represented
    by an instance of TIFFField. For each field to be written a TIFFField may be added to
    the TIFFDirectory and the latter converted to an IIOMetadata object by invoking
    TIFFDirectory.getAsMetadata. The IIOMetadata object so obtained may then be passed to
    the TIFF writer.
    
ImageTypeSpecifier
ImageTypeSpecifier(ColorModel colorModel, SampleModel sampleModel)
Constructs an ImageTypeSpecifier directly from a ColorModel and a SampleModel.
    
static long computeImageSize(RenderedImage image) {
  long bits = 0;
  final int bands = image.getSampleModel().getNumBands();
  for (int i = 0; i < bands; i++) {
    bits += image.getSampleModel().getSampleSize(i);
  }
  return (long) Math.ceil(bits / 8) * image.getWidth() * image.getHeight();
}

void 	setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
Sets an array of integer pixels in the default RGB color model (TYPE_INT_ARGB) and default sRGB color space, into a portion of the image data.
    
 */
//	    Raster 	raster = bufferedImage.getData(); // use pixels from IJ
	    BufferedImage 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);
	    /*
	    bufferedImageAlpha.setRGB( // slow
	    		0,                         // int startX,
	    		0,                         // int startY,
	    		bufferedImage.getWidth(),  // int w,
	    		bufferedImage.getHeight(), // int h,
	    		ipixels,                   // int[] rgbArray,
	    		0,                         // int offset,
	    		bufferedImage.getWidth()); // int scansize);
	    */
	    
	    
	    
//	    bufferedImageAlpha.setData(raster);
	    //BufferedImage.TYPE_INT_ARGB
//	    ColorModel colorModel = ColorModel.getRGBdefault();
//	    SampleModel samleModel = bufferedImage.getSampleModel();
//	    ImageTypeSpecifier imageType = ImageTypeSpecifier.createFromRenderedImage(bufferedImage);            
	    ImageTypeSpecifier imageType = ImageTypeSpecifier.createFromRenderedImage(bufferedImageAlpha);            
	    IIOMetadata imageMetadata = tiffWriter.getDefaultImageMetadata(imageType, tiffWriteParam);
// just for testing:
	    double [] lla = {	49.9476514222895,28.5547846609957,323.750316821841};
	    LocalDateTime dt =  Did_ins.getLocalDateTime(2286, 491072.634526699);
	    double pix_in_meters = 0.01;
	    
	    /*
	    ExifGPSTagSet exif_tag_set = ExifGPSTagSet.getInstance();
	    
	    
	    TIFFTagSet [] tag_sets = {ExifGPSTagSet.getInstance()} ;
	    ExifParentTIFFTagSet eptts = ExifParentTIFFTagSet.getInstance();
	    TIFFDirectory gps_directory = new TIFFDirectory(
	    		tag_sets, eptts.getTag(
				ExifParentTIFFTagSet.TAG_GPS_INFO_IFD_POINTER));
	    
	    TIFFTagSet tag_set = ExifGPSTagSet.getInstance();
	    TIFFField gps_ifd = null;
//	    int df = TIFFField.TIFF_IFD_POINTER; // 13
	    gps_ifd = new TIFFField(
	    		eptts.getTag(
	    				ExifParentTIFFTagSet.TAG_GPS_INFO_IFD_POINTER), // 'ExifIFDPointer', // TIFFTag tag,
	    		13,            // TIFFField.TIFF_IFD_POINTER, // int type,
	    		(long)         0, // long offset,
	    		(TIFFDirectory) gps_directory); // TIFFDirectory dir)
	    directory.addTIFFField(gps_ifd);
	    directory.addTagSet(tag_set); // ExifGPSTagSet.getInstance());
	    
	    */
	    
	    /* */
	    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();
	    directory.addTagSet(tag_set); // ExifGPSTagSet.getInstance());
	    directory.addTagSet(eptts);   // ExifParentTIFFTagSet.getInstance());
	    //ExifGPSTagSet
	    imageMetadata = directory.getAsMetadata();
	    /* */
	    
	    Node root_gps = imageMetadata.getAsTree(TIFF_METADATA_FORMAT);//?
	    
	    System.out.println("\n=== root_gps ===:");
	    DumpImageMetadata.displayMetadataNode(root_gps, 1);
	    
	    Node ifd =      root_gps.getFirstChild();	    
	    IIOMetadataNode gpsRootNode = new IIOMetadataNode("TIFFIFD");
	    gpsRootNode.setAttribute("parentTagNumber", "34853");
	    gpsRootNode.setAttribute("parentTagName",   "GPSInfoIFDPointer");
	    gpsRootNode.setAttribute("tagSets", "javax.imageio.plugins.tiff.ExifGPSTagSet");

	    IIOMetadataNode childNode = new IIOMetadataNode(TIFF_FIELD_TAG);
	    childNode.setAttribute("number", "1");
	    childNode.setAttribute("name", "GPSLatitudeRef");
	    IIOMetadataNode asciiNode = new IIOMetadataNode(TIFF_ASCIIS_TAG);
	    IIOMetadataNode childAsciiNode = new IIOMetadataNode(TIFF_ASCII_TAG);
	    childAsciiNode.setAttribute(TIFF_FIELD_VALUE_ATTRIBUTE, "N");
	    asciiNode.appendChild(childAsciiNode);
	    childNode.appendChild(asciiNode);
	    gpsRootNode.appendChild(childNode);
	    ifd.appendChild(gpsRootNode);
	    System.out.println("\n=== ifd ===:");
	    DumpImageMetadata.displayMetadataNode(ifd, 1);

	    /*
IIOMetadataNode childNode = new IIOMetadataNode(FileConstants.TIFF_FIELD_TAG);
childNode.setAttribute(FileConstants.TIFF_FIELD_NUMBER_ATTRIBUTE, "1");
childNode.setAttribute(FileConstants.TIFF_FIELD_NAME_ATTRIBUTE, "GPSLatitudeRef");
IIOMetadataNode asciiNode = new IIOMetadataNode("TIFFAsciis");
IIOMetadataNode childAsciiNode = new IIOMetadataNode("TIFFAscii");
childAsciiNode.setAttribute(FileConstants.TIFF_FIELD_VALUE_ATTRIBUTE, "N");
asciiNode.appendChild(childAsciiNode);
childNode.appendChild(asciiNode);
gpsRootNode.appendChild(childNode);
ifd.appendChild(gpsRootNode);
//Update metadata with new tree
metadata.setFromTree(FileConstants.TIFF_METADATA_FORMAT, root);
ImageOutputStream outstr = ImageIO.createImageOutputStream(writeTarget);
writer.setOutput(outstr);
//Write the image
IIOImage img = new IIOImage(renderedImage, Collections.<BufferedImage> emptyList(), metadata);
writer.write(img);
outstr.close();

	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_RATIONALS_TAG =   "TIFFRationals";
	public static String TIFF_RATIONAL_TAG =    "TIFFRational";
	public static String TIFF_FIELD_VALUE_ATTRIBUTE = "value";




	     */
	    
	    
	    
	    IIOMetadataNode dimension = createResolutionMetadata(pix_in_meters);
//	    imageMetadata.appendChild(dimension);
	    
	    
////		IIOMetadataNode root_dimension = new IIOMetadataNode(TIFF_METADATA_FORMAT); // IIOMetadataFormatImpl.standardMetadataFormatName);
//		root_dimension.appendChild(dimension);
////		root_dimension.appendChild(root_gps);
	    System.out.println("\n=== root_gps ===:"); // should have 2 tagSets, but has only baseline
	    /*
	    <javax_imageio_tiff_image_1.0>
        <TIFFIFD tagSets='javax.imageio.plugins.tiff.BaselineTIFFTagSet,javax.imageio.plugins.tiff.GeoTIFFTagSet'>
	    */
	    DumpImageMetadata.displayMetadataNode(root_gps, 1);
	    System.out.println("\n=== imageMetadata - before merge ===:");
	    DumpImageMetadata.displayMetadataNode(imageMetadata.getAsTree(TIFF_METADATA_FORMAT), 1);
		
		// lost GPS data after merge
		try {
			imageMetadata.mergeTree(TIFF_METADATA_FORMAT, root_gps); // dimension); // Root must have "TIFFIFD" child
//			imageMetadata.setFromTree(TIFF_METADATA_FORMAT, root_dimension); // dimension);
		} catch (IIOInvalidTreeException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return;
		}
	    
	    System.out.println("\n=== imageMetadata - after merge ===:");
	    DumpImageMetadata.displayMetadataNode(imageMetadata.getAsTree(TIFF_METADATA_FORMAT), 1);
		
		
		
		
	    System.out.println();
	    FileImageOutputStream outputStream;
	    try {
			outputStream = new FileImageOutputStream(outputFile);
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return;
		}		
	    tiffWriter.setOutput(outputStream);
//	    IIOImage image_with_metadata = new IIOImage(bufferedImage, null, primaryIFD);
//	    IIOImage image_with_metadata = new IIOImage(bufferedImage, null, null);
	    IIOImage image_with_metadata = new IIOImage(bufferedImageAlpha, null, imageMetadata);
	    System.out.println();
        try {
			tiffWriter.write(
					null, // tiffStreamMetadata,
					image_with_metadata,
			        tiffWriteParam);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    System.out.println();
        
	}
	
	private static IIOMetadataNode createResolutionMetadata(double resolutionPixelInMeters) {
//		String pixelSize = Double.toString(25.4 / resolutionDPI);
		String pixelSize = Double.toString(1000*resolutionPixelInMeters); // millimeters
		IIOMetadataNode horizontal = new IIOMetadataNode("HorizontalPixelSize");
		horizontal.setAttribute("value", pixelSize);
		IIOMetadataNode vertical = new IIOMetadataNode("VerticalPixelSize");
		vertical.setAttribute("value", pixelSize);
		IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
		dimension.appendChild(horizontal);
		dimension.appendChild(vertical);
		return dimension;
//		IIOMetadataNode root = new IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName);
//		root.appendChild(dimension);
//		return root;
	}
	private static IIOMetadataNode createGpsMetadata(double [] lla, LocalDateTime dt) {
		IIOMetadataNode gpsRootNode = new IIOMetadataNode("TIFFIFD");
//		gpsRootNode.setAttribute(FileConstants.TIFF_FIELD_NUMBER_ATTRIBUTE, "34853");		
		
		return gpsRootNode;
	}
	
	/*
	 * 
	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)
	}

 */
}
