/*-
 * #%L
 * Fiji distribution of ImageJ for the life sciences.
 * %%
 * Copyright (C) 2007 - 2017 Fiji developers.
 * %%
 * This program 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 2 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/gpl-2.0.html>.
 * #L%
 */
package spim.fiji.spimdata.imgloaders;

import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.GenericDialog;
import ij.io.Opener;
import ij.process.ImageProcessor;

import java.io.File;
import java.util.Date;

import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription;
import mpicbg.spim.data.sequence.ViewId;
import mpicbg.spim.io.IOFunctions;
import net.imglib2.Cursor;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.converter.RealUnsignedShortConverter;
import net.imglib2.img.Img;
import net.imglib2.img.ImgFactory;
import net.imglib2.img.array.ArrayImg;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.img.planar.PlanarImg;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;
import spim.fiji.datasetmanager.StackListImageJ;
import spim.fiji.plugin.resave.Generic_Resave_HDF5;
import spim.fiji.plugin.resave.Generic_Resave_HDF5.Parameters;
import spim.fiji.plugin.util.GUIHelper;
import spim.process.fusion.export.ExportSpimData2HDF5;

public class LegacyStackImgLoaderIJ extends LegacyStackImgLoader
{
	Parameters params = null;

	public LegacyStackImgLoaderIJ(
			final File path, final String fileNamePattern, final ImgFactory< ? extends NativeType< ? > > imgFactory,
			final int layoutTP, final int layoutChannels, final int layoutIllum, final int layoutAngles,
			final AbstractSequenceDescription< ?, ?, ? > sequenceDescription )
	{
		super( path, fileNamePattern, imgFactory, layoutTP, layoutChannels, layoutIllum, layoutAngles, sequenceDescription );
	}

	public static ImagePlus open( File file )
	{
		final ImagePlus imp = new Opener().openImage( file.getAbsolutePath() );

		if ( imp == null )
		{
			IOFunctions.printlnSafe( "Could not open file with ImageJ TIFF reader: '" + file.getAbsolutePath() + "'" );
			return null;
		}

		return imp;
	}

	/**
	 * Get {@link FloatType} image normalized to the range [0,1].
	 *
	 * @param view
	 *            timepoint and setup for which to retrieve the image.
	 * @param normalize
	 * 			  if the image should be normalized to [0,1] or not
	 * @return {@link FloatType} image normalized to range [0,1]
	 */
	@Override
	public RandomAccessibleInterval< FloatType > getFloatImage( final ViewId view, final boolean normalize )
	{
		final File file = getFile( view );

		if ( file == null )
			throw new RuntimeException( "Could not find file '" + file + "'." );

		IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Loading '" + file + "' ..." );

		final ImagePlus imp = open( file );

		if ( imp == null )
			throw new RuntimeException( "Could not load '" + file + "'." );

		final long[] dim = new long[]{ imp.getWidth(), imp.getHeight(), imp.getStack().getSize() };
		final Img< FloatType > img = this.instantiateImg( dim, new FloatType() );

		if ( img == null )
			throw new RuntimeException( "Could not instantiate " + getImgFactory().getClass().getSimpleName() + " for '" + file + "', most likely out of memory." );
		else
			IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Opened '" + file + "' [" + dim[ 0 ] + "x" + dim[ 1 ] + "x" + dim[ 2 ] + " image=" + img.getClass().getSimpleName() + "<FloatType>]" );

		imagePlus2ImgLib2Img( imp, img, normalize );

		// update the MetaDataCache of the AbstractImgLoader
		// this does not update the XML ViewSetup but has to be called explicitly before saving
		updateMetaDataCache( view, imp.getWidth(), imp.getHeight(), imp.getStack().getSize(),
				imp.getCalibration().pixelWidth, imp.getCalibration().pixelHeight, imp.getCalibration().pixelDepth );

		imp.close();

		return img;
	}

	public static void imagePlus2ImgLib2Img( final ImagePlus imp, final Img< FloatType > img, final boolean normalize )
	{
		final ImageStack stack = imp.getStack();
		final int sizeZ = imp.getStack().getSize();

		if ( img instanceof ArrayImg || img instanceof PlanarImg )
		{
			final Cursor< FloatType > cursor = img.cursor();
			final int sizeXY = imp.getWidth() * imp.getHeight();

			if ( normalize )
			{
				float min = Float.MAX_VALUE;
				float max = -Float.MAX_VALUE;

				for ( int z = 0; z < sizeZ; ++z )
				{
					final ImageProcessor ip = stack.getProcessor( z + 1 );

					for ( int i = 0; i < sizeXY; ++i )
					{
						final float v = ip.getf( i );

						if ( v < min )
							min = v;

						if ( v > max )
							max = v;

						cursor.next().set( v );
					}
				}

				for ( final FloatType t : img )
					t.set( ( t.get() - min ) / ( max - min ) );
			}
			else
			{
				for ( int z = 0; z < sizeZ; ++z )
				{
					final ImageProcessor ip = stack.getProcessor( z + 1 );

					for ( int i = 0; i < sizeXY; ++i )
						cursor.next().set( ip.getf( i ) );
				}
			}
		}
		else
		{
			final int width = imp.getWidth();

			if ( normalize )
			{
				float min = Float.MAX_VALUE;
				float max = -Float.MAX_VALUE;

				for ( int z = 0; z < sizeZ; ++z )
				{
					final Cursor< FloatType > cursor = Views.iterable( Views.hyperSlice( img, 2, z ) ).localizingCursor();
					final ImageProcessor ip = stack.getProcessor( z + 1 );

					while ( cursor.hasNext() )
					{
						cursor.fwd();
						final float v = ip.getf( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width );

						if ( v < min )
							min = v;

						if ( v > max )
							max = v;

						cursor.get().set( v );
					}
				}

				for ( final FloatType t : img )
					t.set( ( t.get() - min ) / ( max - min ) );
			}
			else
			{
				for ( int z = 0; z < sizeZ; ++z )
				{
					final Cursor< FloatType > cursor = Views.iterable( Views.hyperSlice( img, 2, z ) ).localizingCursor();
					final ImageProcessor ip = stack.getProcessor( z + 1 );

					while ( cursor.hasNext() )
					{
						cursor.fwd();
						cursor.get().set( ip.getf( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width ) );
					}
				}
			}
		}
	}

	/**
	 * Get {@link UnsignedShortType} un-normalized image.
	 *
	 * @param view
	 *            timepoint and setup for which to retrieve the image.
	 * @return {@link UnsignedShortType} image.
	 */
	@Override
	public RandomAccessibleInterval< UnsignedShortType > getImage( final ViewId view )
	{
		final File file = getFile( view );

		if ( file == null )
			throw new RuntimeException( "Could not find file '" + file + "'." );

		IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Loading '" + file + "' ..." );

		final ImagePlus imp = open( file );

		if ( imp == null )
			throw new RuntimeException( "Could not load '" + file + "'." );

		final boolean is32bit;
		final RealUnsignedShortConverter< FloatType > converter;

		if ( imp.getType() == ImagePlus.GRAY32 )
		{
			is32bit = true;
			IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Image '" + file + "' is 32bit, opening as 16bit with scaling" );

			if ( params == null )
				params = queryParameters();

			if ( params == null )
				return null;

			final double[] minmax = ExportSpimData2HDF5.updateAndGetMinMax( ImageJFunctions.wrapFloat( imp ), params );
			converter = new RealUnsignedShortConverter< FloatType >( minmax[ 0 ], minmax[ 1 ] );
		}
		else
		{
			is32bit = false;
			converter = null;
		}

		final long[] dim = new long[]{ imp.getWidth(), imp.getHeight(), imp.getStack().getSize() };
		final Img< UnsignedShortType > img = instantiateImg( dim, new UnsignedShortType() );

		if ( img == null )
			throw new RuntimeException( "Could not instantiate " + getImgFactory().getClass().getSimpleName() + " for '" + file + "', most likely out of memory." );
		else
			IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Opened '" + file + "' [" + dim[ 0 ] + "x" + dim[ 1 ] + "x" + dim[ 2 ] + " image=" + img.getClass().getSimpleName() + "<UnsignedShortType>]" );

		final ImageStack stack = imp.getStack();
		final int sizeZ = imp.getStack().getSize();

		if ( img instanceof ArrayImg || img instanceof PlanarImg )
		{
			final Cursor< UnsignedShortType > cursor = img.cursor();
			final int sizeXY = imp.getWidth() * imp.getHeight();

			for ( int z = 0; z < sizeZ; ++z )
			{
				final ImageProcessor ip = stack.getProcessor( z + 1 );

				if( is32bit )
				{
					final FloatType input = new FloatType();
					final UnsignedShortType output = new UnsignedShortType();

					for ( int i = 0; i < sizeXY; ++i )
					{
						input.set( ip.getf( i ) );
						converter.convert( input, output );
						cursor.next().set( output.get() );
					}
				}
				else
				{
					for ( int i = 0; i < sizeXY; ++i )
						cursor.next().set( ip.get( i ) );
				}
			}
		}
		else
		{
			final int width = imp.getWidth();

			for ( int z = 0; z < sizeZ; ++z )
			{
				final Cursor< UnsignedShortType > cursor = Views.iterable( Views.hyperSlice( img, 2, z ) ).localizingCursor();
				final ImageProcessor ip = stack.getProcessor( z + 1 );

				if ( is32bit )
				{
					final FloatType input = new FloatType();
					final UnsignedShortType output = new UnsignedShortType();

					while ( cursor.hasNext() )
					{
						cursor.fwd();
						input.set( ip.getf( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width ) );
						converter.convert( input, output );
						cursor.get().set( output );
					}
				}
				else
				{
					while ( cursor.hasNext() )
					{
						cursor.fwd();
						cursor.get().set( ip.get( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width ) );
					}
				}
			}
		}

		// update the MetaDataCache of the AbstractImgLoader
		// this does not update the XML ViewSetup but has to be called explicitly before saving
		updateMetaDataCache( view, imp.getWidth(), imp.getHeight(), imp.getStack().getSize(),
				imp.getCalibration().pixelWidth, imp.getCalibration().pixelHeight, imp.getCalibration().pixelDepth );

		imp.close();

		return img;
	}

	@Override
	protected void loadMetaData( final ViewId view )
	{
		final File file = getFile( view );
		final ImagePlus imp = open( file );

		if ( imp == null )
			throw new RuntimeException( "Could not load '" + file + "'." );

		// update the MetaDataCache of the AbstractImgLoader
		// this does not update the XML ViewSetup but has to be called explicitly before saving
		updateMetaDataCache( view, imp.getWidth(), imp.getHeight(), imp.getStack().getSize(),
				imp.getCalibration().pixelWidth, imp.getCalibration().pixelHeight, imp.getCalibration().pixelDepth );

		imp.close();
	}

	@Override
	public String toString()
	{
		return new StackListImageJ().getTitle() + ", ImgFactory=" + imgFactory.getClass().getSimpleName();
	}

	protected static Parameters queryParameters()
	{
		final GenericDialog gd = new GenericDialog( "Opening 32bit TIFF as 16bit" );

		gd.addMessage( "You are trying to open 32-bit images as 16-bit (resaving as HDF5 maybe). Please define how to convert to 16bit.", GUIHelper.mediumstatusfont );
		gd.addMessage( "Note: This dialog will only show up once for the first image.", GUIHelper.mediumstatusfont );
		gd.addChoice( "Convert_32bit", Generic_Resave_HDF5.convertChoices, Generic_Resave_HDF5.convertChoices[ Generic_Resave_HDF5.defaultConvertChoice ] );

		gd.showDialog();

		if ( gd.wasCanceled() )
			return null;

		Generic_Resave_HDF5.defaultConvertChoice = gd.getNextChoiceIndex();

		if ( Generic_Resave_HDF5.defaultConvertChoice == 2 )
		{
			if ( Double.isNaN( Generic_Resave_HDF5.defaultMin ) )
				Generic_Resave_HDF5.defaultMin = 0;

			if ( Double.isNaN( Generic_Resave_HDF5.defaultMax ) )
				Generic_Resave_HDF5.defaultMax = 5;

			final GenericDialog gdMinMax = new GenericDialog( "Define min/max" );

			gdMinMax.addNumericField( "Min_Intensity_for_16bit_conversion", Generic_Resave_HDF5.defaultMin, 1 );
			gdMinMax.addNumericField( "Max_Intensity_for_16bit_conversion", Generic_Resave_HDF5.defaultMax, 1 );
			gdMinMax.addMessage( "Note: the typical range for multiview deconvolution is [0 ... 10] & for fusion the same as the input intensities., ",GUIHelper.mediumstatusfont );

			gdMinMax.showDialog();

			if ( gdMinMax.wasCanceled() )
				return null;

			Generic_Resave_HDF5.defaultMin = gdMinMax.getNextNumber();
			Generic_Resave_HDF5.defaultMax = gdMinMax.getNextNumber();
		}
		else
		{
			Generic_Resave_HDF5.defaultMin = Generic_Resave_HDF5.defaultMax = Double.NaN;
		}

		return new Parameters( false, null, null, null, null, false, false, 0, 0, false, 0, Generic_Resave_HDF5.defaultConvertChoice, Generic_Resave_HDF5.defaultMin, Generic_Resave_HDF5.defaultMax );
	}
}