/*
 * Geotools 2 - OpenSource mapping toolkit
 * (C) 2003, Geotools Project Management Committee (PMC)
 * (C) 2001, Institut de Recherche pour le Développement
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 *    This package contains documentation from OpenGIS specifications.
 *    OpenGIS consortium's work is fully acknowledged here.
 */
package org.geotools.gce.gtopo30;




// J2SE dependencies
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderedImageFactory;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import javax.media.jai.CRIFImpl;
import javax.media.jai.ComponentSampleModelJAI;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptorImpl;
import javax.media.jai.OperationRegistry;
import javax.media.jai.PlanarImage;
import javax.media.jai.PointOpImage;
import javax.media.jai.iterator.RectIterFactory;
import javax.media.jai.iterator.WritableRectIter;
import javax.media.jai.registry.RenderedRegistryMode;

import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.AbstractGridCoverage;
import org.geotools.image.TransfertRectIter;
import org.geotools.resources.i18n.Logging;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.image.ImageUtilities;


/**
 * An image that contains transformed samples, specifically this method will transform the NoData value
 * using a new supplied one. A new layout is used in order to convert to the required image layout. Default
 * values for this operation can be used to set the NoData and the layout to the values needed for 
 * the GTOPO30 writer.
 *   
 * Images are created using the
 * {@code NoDataReplacerOpImage.NoDataReplacerCRIF} inner class, where "CRIF" stands for
 * {@link java.awt.image.renderable.ContextualRenderedImageFactory}. The image
 * operation name is "org.geotools.gce.NoDataReplacer".
 *
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/gtopo30/src/main/java/org/geotools/gce/gtopo30/NoDataReplacerOpImage.java $
 * @version $Id: NoDataReplacerOpImage.java 22817 2006-11-17 17:24:55Z desruisseaux $
 * @author Simone Giannecchini
 *
 * @since 2.2
 */
public final class NoDataReplacerOpImage extends PointOpImage {
    /**The operation name.*/
    public static final String OPERATION_NAME = "org.geotools.gce.gtopo30.NoDataReplacer";
    
    /**Constant that tell me the margin when checking for equality with floating point values*/
	private double EPS;
	
	/**Old no data value.*/
	private Number oldNoData;
	
	/**New no data value.*/
	private int newNoData;
	
	/**It tells me whether or not the old no data is NaN.*/
	private boolean oldNoDataIsNaN;



    
    /**
     * Constructs a new {@code NoDataReplacerOpImage}.
     *
     * @param image      The source image.
     * @param oldNoData The old NoData value to be substituted.
     * @param newNoData The new NoData value to be employed.
     * @param EPS Margin for equality checks.
     * @param hints Suplpied RenderingHints.
     */
    private NoDataReplacerOpImage(final RenderedImage  image,
                             final Number oldNoData,
                             final Short newNoData,
                             final Double EPS,
                             final RenderingHints hints)
    {
        super(image, NoDataReplacerOpImage.getRightLayout(image), hints, false);
        this.EPS = EPS.doubleValue();
        this.oldNoData=oldNoData;
        this.oldNoDataIsNaN=Double.isNaN(oldNoData.doubleValue());
        this.newNoData=newNoData.intValue();

        permitInPlaceOperation();
    }
    
    /**
     * @todo Creation of non banded sample models
	 * @param image Image to work on.
	 * @return New Image Layout.
	 */
	private static ImageLayout getRightLayout(RenderedImage image) {
		
		final SampleModel sm=image.getSampleModel();
		final int dataType=DataBuffer.TYPE_SHORT;
		if(sm.getDataType()==dataType)
			return new ImageLayout(image);
		final ColorModel cm=image.getColorModel();
		if(sm instanceof ComponentSampleModel)
		{
	        final ColorModel newCm = new ComponentColorModel(
	        		cm.getColorSpace(),
	        		false,
	        		false,
	        		cm.getTransparency(),
	        		dataType);
	        final SampleModel newSm = new ComponentSampleModelJAI(
	        		dataType,
	        		sm.getWidth(),
	        		sm.getHeight(),
	        		((ComponentSampleModel)sm).getPixelStride(),
	        		((ComponentSampleModel)sm).getScanlineStride(),
	        		((ComponentSampleModel)sm).getBankIndices(),
	        		((ComponentSampleModel)sm).getBandOffsets()
	        		);
		
		   final        ImageLayout layout = ImageUtilities.getImageLayout(image);
		   layout.setColorModel(newCm);
		   layout.setSampleModel(newSm);
		   return layout;
		}
		else
			;//do nothing for the moment
		
		return null;
	}



	/**
     * Computes one of the destination image tile.
     *
     * @todo There is two optimisations we could do here:
     *       <ul>
     *         <li>If source and destination are the same raster, then a single
     *             {@link WritableRectIter} object would be more efficient (the
     *             hard work is to detect if source and destination are the same).</li>
     *         <li>If the destination image is a single-banded, non-interleaved
     *             sample model, we could apply the transform directly in the
     *             {@link java.awt.image.DataBuffer}. We can even avoid to copy
     *             sample value if source and destination raster are the same.</li>
     *       </ul>
     *
     * @param sources  An array of length 1 with source image.
     * @param dest     The destination tile.
     * @param destRect the rectangle within the destination to be written.
     */
    protected void computeRect(final PlanarImage[] sources,
                               final WritableRaster   dest,
                               final Rectangle    destRect)
    {
        final PlanarImage source = sources[0];
        WritableRectIter iterator = RectIterFactory.createWritable(dest, destRect);
        if (true) {
            // TODO: Detect if source and destination rasters are the same. If they are
            //       the same, we should skip this block. Iteration will then be faster.
            iterator = TransfertRectIter.create(RectIterFactory.create(source, destRect), iterator);
        }
        formatRect(iterator);
    }




	 /**
     * Transform a raster. Only the current band in {@code iterator} will be transformed.
     * The transformed value are write back in the {@code iterator}. If a different
     * destination raster is wanted, a {@link org.geotools.resources.image.DualRectIter}
     * may be used.
     *
     * @param  iterator An iterator to iterate among the samples to transform.
     */
	private void formatRect(WritableRectIter iterator) {

	      
		double actualValue=0.0;
        iterator.startLines();
        if (!iterator.finishedLines()) 
        	do {
        		iterator.startPixels();
        		if (!iterator.finishedPixels())
        			do {
            	
		            	//get the actual value
		                actualValue = iterator.getSampleDouble();
	                    
		                //substituting a NaN
		                if(oldNoDataIsNaN)
		                	if(Double.isNaN(actualValue))
		                			iterator.setSample(newNoData);
		                else
		                	if(Math.abs(oldNoData.doubleValue()-actualValue)<=EPS)
		                		iterator.setSample(newNoData);
		                	else
		                		iterator.setSample(actualValue);
		                		
        			}
	                while (!iterator.nextPixelDone());
            }
            while (!iterator.nextLineDone());
	

		
	}




	/////////////////////////////////////////////////////////////////////////////////
    ////////                                                                 ////////
    ////////        REGISTRATION OF "NoDataReplacer" IMAGE OPERATION        ////////
    ////////                                                                 ////////
    /////////////////////////////////////////////////////////////////////////////////
    /**
     * The operation descriptor for the "NoDataReplacer" operation. This operation
     * is used to change the format of an Image while replacing the NoData value with a new
     * one as requested. The difference between this method and the usual format operation presents
     * in JAI is the possibility to replace the NoData value directly when it is like Double.NaN or
     * Float.NaN.
     * 
     */
    private static final class NoDataReplacerDescriptor extends OperationDescriptorImpl {
		/**
		 * Comment for <code>serialVersionUID</code>
		 */
		private static final long serialVersionUID = 1L;

		/**
         * Construct the descriptor.
         */
        public NoDataReplacerDescriptor() {
            super(new String[][]{{"GlobalName",  OPERATION_NAME},
                                 {"LocalName",   OPERATION_NAME},
                                 {"Vendor",      "Geotools 2"},
                                 {"Description", "Nodata replacement and layout adjustment."},
                                 {"DocURL",      "http://www.geotools.org/"},
                                 {"Version",     "1.0"}},
                  new String[]   {RenderedRegistryMode.MODE_NAME}, 1,
                  new String[]   {"oldNoData","newNoData","EPS"},          // Argument names
                  new Class []   {Number.class,Short.class,Double.class}, // Argument classes
                  new Object[]   {new Double(Double.NaN),new Short((short)-9999),new Double(10.0E-6)},        // Default values for parameters,
                  null // No restriction on valid parameter values.
            );
        }

        /**
         * Returns {@code true} if the parameters are valids. This implementation check
         * that the number of bands in the source image is equals to the number of supplied
         * sample dimensions, and that all sample dimensions has categories.
         *
         * @param modeName The mode name (usually "Rendered").
         * @param param The parameter block for the operation to performs.
         * @param message A buffer for formatting an error message if any.
         */
        protected boolean validateParameters(final String      modeName,
                                             final ParameterBlock param,
                                             final StringBuffer message)
        {
            if (!super.validateParameters(modeName, param, message)) {
                return false;
            }
            try{
//            	param.
//	            final RenderedImage source = (RenderedImage) param.getSource(0);
//	            final Number  oldNoData= (Number) param.getObjectParameter(0);
//	            final Number  newNoData= (Number) param.getObjectParameter(1);
//	            final Double  EPS=  (Double) param.getObjectParameter(1);
            }
            catch(Exception e){
            	message.append(e.getMessage());
            	return false;
            }
            return true;
        }
    }

    /**
     * The {@link RenderedImageFactory} for the "SampleTranscode" operation.
     */
    private static final class NoDataReplacerCRIF extends CRIFImpl {
        /**
         * Creates a {@link RenderedImage} representing the results of an imaging
         * operation for a given {@link ParameterBlock} and {@link RenderingHints}.
         */
        public RenderedImage create(final ParameterBlock param,
                                    final RenderingHints hints)
        {
            final RenderedImage source = (RenderedImage) param.getSource(0);
            
            final Number  oldNoData= (Number) param.getObjectParameter(0);
            final Short  newNoData= (Short) param.getObjectParameter(1);
            final Double  EPS=  (Double) param.getObjectParameter(2);
            return new NoDataReplacerOpImage(source, oldNoData,newNoData,EPS, hints);
        }


    }

    /**
     * Register the "SampleTranscode" image operation to the operation registry of
     * the specified JAI instance. This method is invoked by the static initializer
     * of {@link GridSampleDimension}.
     */
    public static void register(final JAI jai) {
        final OperationRegistry registry = jai.getOperationRegistry();
        try {
            registry.registerDescriptor(new NoDataReplacerDescriptor());
            registry.registerFactory(RenderedRegistryMode.MODE_NAME, OPERATION_NAME,
                                     "gce.geotools.org", new NoDataReplacerCRIF());
        } catch (IllegalArgumentException exception) {
            final LogRecord record = Logging.format(Level.SEVERE,
                   LoggingKeys.CANT_REGISTER_JAI_OPERATION_$1, OPERATION_NAME);
            record.setSourceClassName("GridSampleDimension");
            record.setSourceMethodName("<classinit>");
            record.setThrown(exception);
            AbstractGridCoverage.LOGGER.log(record);
        }
    }
}
