/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2007, Geotools Project Managment Committee (PMC)
 *    (C) 2007, Geomatys
 *
 *    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.image.io.netcdf;

import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import javax.imageio.ImageReader;

import ucar.nc2.dataset.AxisType;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.CoordinateSystem;
import ucar.nc2.dataset.VariableEnhanced;

import org.geotools.image.io.GeographicImageReadParam;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;


/**
 * Default parameters for {@link NetcdfImageReader}.
 *
 * @since 2.4
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio-netcdf/src/main/java/org/geotools/image/io/netcdf/NetcdfReadParam.java $
 * @version $Id: NetcdfReadParam.java 27071 2007-09-19 19:02:54Z desruisseaux $
 * @author Martin Desruisseaux
 */
public class NetcdfReadParam extends GeographicImageReadParam {
    /**
     * The default source band to read from the NetCDF file. Also the default indice for
     * any additional dimension after the one assigned to bands. We use the same default
     * for consistency, because the third dimension (typically <var>z</var>) could be
     * selected either by {@link #setSourceBands} or by {@link #setSliceIndice}; the
     * relevant method depends on {@link NetcdfImageReader#getBandDimension} value.
     */
    static final int DEFAULT_INDICE = 0;

    /**
     * The default source bands to read from the NetCDF file.
     * Also the default destination bands in the buffered image.
     */
    private static final int[] DEFAULT_BANDS = new int[] {DEFAULT_INDICE};

    /**
     * The types of the dimension to use as image bands. If more than one type is specified,
     * only the first dimension with a suitable type will be assigned to bands.
     */
    private Set/*<AxisType>*/ bandDimensionTypes;

    /**
     * For <var>n</var>-dimensional images, the indices to use for dimensions above 2.
     * Will be created only when first needed.
     */
    private Map/*<AxisType,Integer>*/ sliceIndices;

    /**
     * Creates a new, initially empty, set of parameters.
     *
     * @param reader The reader for which this parameter block is created
     */
    public NetcdfReadParam(final ImageReader reader) {
        super(reader);
        setSourceBands     (DEFAULT_BANDS);
        setDestinationBands(DEFAULT_BANDS);
    }

    /**
     * Returns the dimension to assign to bands for the specified variable. The default
     * implementation returns the last dimension corresponding to one of the types specified
     * to {@link #setBandDimensionTypes}.
     * <p>
     * <b>Example:</b> For a NetCDF variable having dimensions in the
     * (<var>t</var>,<var>z</var>,<var>y</var>,<var>x</var>) order (as in CF convention), if the
     * {@linkplain AxisType#Height height} and {@linkplain AxisType#Pressure pressure} types have
     * been {@linkplain #setBandDimensionTypes assigned} to bands, then this method will returns
     * the index of the <var>z</var> dimension, i.e. {@code 1}.
     *
     * @param variable The variable for which we want to determine the dimension to assign to bands.
     * @return The dimension assigned to bands, or {@code -1} if none.
     */
    public int getBandDimension(final VariableEnhanced variable) {
        if (bandDimensionTypes != null) {
            final List sys = variable.getCoordinateSystems();
            if (sys != null) {
                final int count = sys.size();
                for (int i=0; i<count; i++) {
                    final CoordinateSystem cs = (CoordinateSystem) sys.get(i);
                    final List axes = cs.getCoordinateAxes();
                    if (axes != null) {
                        for (int j=axes.size(); --j>=0;) { // Must be reverse order; see javadoc
                            final CoordinateAxis axis = (CoordinateAxis) axes.get(j);
                            if (axis != null && bandDimensionTypes.contains(axis.getAxisType())) {
                                return j;
                            }
                        }
                    }
                }
            }
        }
        return -1;
    }

    /**
     * Returns the dimension assigned to bands. This method returns the values given
     * to the last call to {@link #setBandDimensionTypes}, or {@code null} if none.
     */
    public AxisType[] getBandDimensionTypes() {
        if (bandDimensionTypes == null) {
            return null;
        }
        return (AxisType[]) bandDimensionTypes.toArray(new AxisType[bandDimensionTypes.size()]);
    }

    /**
     * Assigns the dimension of the specified types to bands. For example in a NetCDF variable
     * having (<var>t</var>,<var>y</var>,<var>x</var>) dimensions, it may be useful to treat
     * the <var>t</var> dimension as bands. After invoking this method with the
     * {@linkplain AxisType#Time time} value, users can select a time through the standard
     * {@link #setSourceBands} API.
     * <p>
     * More than one type may be specified if they should be considered as synonymous. For example
     * in order to assign the <var>z</var> dimension to bands, it may be necessary to specify both
     * the {@linkplain AxisType#Height height} and {@linkplain AxisType#Pressure pressure} types.
     *
     * @param type The types of dimension to assign to bands.
     * 
     * @todo Use vararg when we will be allowed to compile for J2SE 1.5.
     */
    public void setBandDimensionTypes(final AxisType[] types) {
        if (types != null && types.length != 0) {
            if (bandDimensionTypes == null) {
                bandDimensionTypes = new HashSet/*<AxisType>*/();
            } else {
                bandDimensionTypes.clear();
            }
            for (int i=0; i<types.length; i++) {
                bandDimensionTypes.add(types[i]);
            }
        } else {
            bandDimensionTypes = null;
        }
    }

    /**
     * Returns the indice to set at the dimension of the specified axis. This is relevant only
     * for <var>n</var>-dimensional data set where <var>n</var>&gt;2. This method returns the
     * last value set by {@link #setSliceIndice}.
     *
     * @param  dimension The axis type (typically {@linkplain AxisType#Height height} or
     *                   {@linkplain AxisType#Time time}).
     * @return The indice to set at the dimension of the specified axis (0 by default).
     */
    public int getSliceIndice(final AxisType axis) {
        if (sliceIndices != null) {
            final Integer indice = (Integer) sliceIndices.get(axis);
            if (indice != null) {
                return indice.intValue();
            }
        }
        return DEFAULT_INDICE;
    }

    /**
     * Sets the indice for the dimension of the specified axis. This is relevant only for
     * <var>n</var>-dimensional data set where <var>n</var>&gt;2. For example in 4-D data
     * set with (<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>) axis, those indices
     * may be used by image readers for <var>z</var> and <var>t</var> dimensions.
     * <p>
     * The default value is 0 for all cases. This means that for the above-cited 4-D data set,
     * only the image at the first time (<var>t</var>=0) and first altitude (<var>z</var>=0)
     * is selected.
     *
     * @param  dimension The axis type (typically {@linkplain AxisType#Height height} or
     *                   {@linkplain AxisType#Time time}).
     * @param indice The indice as a positive value.
     */
    public void setSliceIndice(final AxisType dimension, final int indice) {
        if (indice < 0) {
            throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
                    "indice", new Integer(indice)));
        }
        if (indice != DEFAULT_INDICE) {
            if (sliceIndices == null) {
                sliceIndices = new HashMap/*<AxisType,Integer>*/();
            }
            sliceIndices.put(dimension, new Integer(indice));
        } else if (sliceIndices != null) {
            sliceIndices.remove(dimension);
            if (sliceIndices.isEmpty()) {
                sliceIndices = null; // hasNonNullIndices() wants that.
            }
        }
    }

    /**
     * Returns {@code true} if this set of parameters contains a least one indice
     * defined to a value different than 0.
     */
    final boolean hasNonDefaultIndices() {
        return sliceIndices != null;
    }
}
