/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2006, Geotools Project Managment Committee (PMC)
 *
 *    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.
 */
package org.geotools.coverage.processing.operation;

import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;

import javax.media.jai.ImageLayout;
import javax.media.jai.NullOpImage;
import javax.media.jai.OpImage;

import org.opengis.coverage.Coverage;
import org.opengis.coverage.SampleDimension;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.parameter.ParameterValueGroup;

import org.geotools.factory.Hints;
import org.geotools.coverage.FactoryFinder;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.processing.Operation2D;
import org.geotools.parameter.DefaultParameterDescriptorGroup;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.resources.image.ColorUtilities;
import org.geotools.resources.Utilities;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;


/**
 * Operation applied only on image's colors. This operation work only for source
 * image using an {@link IndexColorModel}.
 * 
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/processing/operation/IndexColorOperation.java $
 * @version $Id: IndexColorOperation.java 26601 2007-08-19 23:16:35Z desruisseaux $
 * @author Martin Desruisseaux
 *
 * @todo Consider moving this class to the {@link org.geotools.coverage.processing} package.
 */
abstract class IndexColorOperation extends Operation2D {
    /**
     * Constructs an operation.
     */
    public IndexColorOperation(final DefaultParameterDescriptorGroup descriptor) {
        super(descriptor);
    }

    /**
     * Performs the color transformation. This method invokes the
     * {@link #transformColormap transformColormap(...)} method with current RGB
     * colormap, the source {@link SampleDimension} and the supplied parameters.
     * 
     * @param parameters The parameters.
     * @param hints Rendering hints (ignored in this implementation).
     *
     * @throws IllegalArgumentException if the candidate image do not use an
     *         {@link IndexColorModel}.
     */
    public Coverage doOperation(final ParameterValueGroup parameters, final Hints hints) {
        final GridCoverage2D source = (GridCoverage2D) parameters.parameter("Source").getValue();
        final GridCoverage2D visual = source.geophysics(false);
        final RenderedImage  image  = visual.getRenderedImage();
        final GridSampleDimension[] bands = visual.getSampleDimensions();
        final int visibleBand = CoverageUtilities.getVisibleBand(image);
        ColorModel model = image.getColorModel();
        boolean colorChanged = false;
        for (int i=0; i<bands.length; i++) {
            /*
             * Extracts the ARGB codes from the IndexColorModel and invokes the
             * transformColormap(...) method, which needs to be defined by subclasses.
             */
            GridSampleDimension band = bands[i];
            final ColorModel candidate = (i == visibleBand) ? image.getColorModel() : band.getColorModel();
            if (!(candidate instanceof IndexColorModel)) {
                // Current implementation supports only sources that use an index color model.
                throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_CLASS_$2,
                        Utilities.getShortClassName(candidate), Utilities.getShortName(IndexColorModel.class)));
            }
            final IndexColorModel colors = (IndexColorModel) candidate;
            final int mapSize = colors.getMapSize();
            final int[] ARGB = new int[mapSize];
            colors.getRGBs(ARGB);
            band = transformColormap(ARGB, i, band, parameters);
            /*
             * Checks if there is any change, either as a new GridSampleDimension instance or in
             * the ARGB array. Note that if the new GridSampleDimension is equals to the old one,
             * then the new one will be discarted since the old one is more likely to be a shared
             * instance.
             */
            if (!bands[i].equals(band)) {
                bands[i] = band;
                colorChanged = true;
            } else if (!colorChanged) {
                for (int j=0; j<mapSize; j++) {
                    if (ARGB[j] != colors.getRGB(j)) {
                        colorChanged = true;
                        break;
                    }
                }
            }
            /*
             * If we changed the color of the visible band, then create immediately a new
             * color model for this band. The new color model will be given later to the
             * image operator.
             */
            if (colorChanged && (i == visibleBand)) {
                model = ColorUtilities.getIndexColorModel(ARGB, bands.length, visibleBand);
            }
        }
        if (!colorChanged) {
            return source;
        }
        /*
         * Gives the color model to the image layout and creates a new image using the Null
         * operation, which merely propagates its first source along the operation chain
         * unmodified (except for the ColorModel given in the layout in this case).
         */
        final ImageLayout layout = new ImageLayout().setColorModel(model);
        final RenderedImage newImage = new NullOpImage(image, layout, null, OpImage.OP_COMPUTE_BOUND);
        final GridCoverage2D target = FactoryFinder.getGridCoverageFactory(null).create(
                    visual.getName(), newImage,
                    visual.getCoordinateReferenceSystem2D(),
                    visual.getGridGeometry().getGridToCRS(),
                    bands, new GridCoverage[] { visual }, null);

        if (source != visual) {
            return target.geophysics(true);
        }
        return target;
    }

    /**
     * Transforms the supplied RGB colors. This method is automatically invoked
     * by {@link #doOperation(ParameterList)} for each band in the source
     * {@link GridCoverage2D}. The {@code ARGB} array contains the ARGB values
     * from the current source and should be overridden with new ARGB values
     * for the destination image.
     * 
     * @param ARGB
     *            Alpha, Red, Green and Blue components to transform.
     * @param band
     *            The band number, from 0 to the number of bands in the image -1.
     * @param sampleDimension
     *            The sample dimension of band {@code band}.
     * @param parameters
     *            The user-supplied parameters.
     * @return A sample dimension identical to {@code sampleDimension} except for the
     *         colors. Subclasses may conservatively returns {@code sampleDimension}.
     */
    protected abstract GridSampleDimension transformColormap(final int[] ARGB, final int band,
            final GridSampleDimension sampleDimension, final ParameterValueGroup parameters);
}
