/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sis.storage.geotiff;

import java.io.IOException;
import java.text.ParseException;
import java.util.List;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.nio.charset.Charset;
import javax.measure.Unit;
import javax.measure.quantity.Length;
import org.opengis.metadata.citation.DateType;
import org.opengis.util.FactoryException;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.apache.sis.internal.geotiff.Resources;
import org.apache.sis.internal.storage.MetadataBuilder;
import org.apache.sis.internal.storage.AbstractGridResource;
import org.apache.sis.internal.storage.io.ChannelDataInput;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.math.Vector;
import org.apache.sis.measure.Units;


/**
 * An Image File Directory (FID) in a TIFF image.
 *
 * @author  Rémi Maréchal (Geomatys)
 * @author  Alexis Manin (Geomatys)
 * @author  Johann Sorel (Geomatys)
 * @author  Thi Phuong Hao Nguyen (VNSC)
 * @author  Martin Desruisseaux (Geomatys)
 * @version 1.0
 *
 * @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags.html">TIFF Tag Reference</a>
 *
 * @since 0.8
 * @module
 */
final class ImageFileDirectory extends AbstractGridResource {
    /**
     * Possible value for the {@link #tileTagFamily} field. That field tells whether image tiling
     * was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*} family.
     */
    private static final byte TILE = 1, STRIP = 2;

    /**
     * Possible value for {@link #sampleFormat} specifying how to interpret each data sample in a pixel.
     * Those values are not necessarily the same than the ones documented in {@link Tags#SampleFormat}.
     * Default value is {@link #UNSIGNED}.
     */
    private static final byte SIGNED = 1, UNSIGNED = 0, FLOAT = 3;

    /**
     * The GeoTIFF reader which contain this {@code ImageFileDirectory}.
     * Used for fetching information like the input channel and where to report warnings.
     */
    private final Reader reader;

    /**
     * The identifier as a sequence number in the namespace of the {@link GeoTiffStore}.
     * The first image has the sequence number "1".
     *
     * @see #getIdentifier()
     */
    private final GenericName identifier;

    /**
     * {@code true} if this {@code ImageFileDirectory} has not yet read all deferred entries.
     * When this flag is {@code true}, the {@code ImageFileDirectory} is not yet ready for use.
     */
    boolean hasDeferredEntries;

    /**
     * The size of the image described by this FID, or -1 if the information has not been found.
     * The image may be much bigger than the memory capacity, in which case the image shall be tiled.
     *
     * <p><b>Note:</b>
     * the {@link #imageHeight} attribute is named {@code ImageLength} in TIFF specification.</p>
     */
    private long imageWidth = -1, imageHeight = -1;

    /**
     * The size of each tile, or -1 if the information has not be found.
     * Tiles shall be small enough for fitting in memory, typically in a {@link java.awt.image.Raster} object.
     * The TIFF specification requires that tile width and height must be a multiple of 16, but the SIS reader
     * implementation works for any size. Tiles need not be square.
     *
     * <p>Assuming integer arithmetic, the number of tiles in an image can be computed as below
     * (these computed values are not TIFF fields):</p>
     *
     * {@preformat math
     *   tilesAcross   = (imageWidth  + tileWidth  - 1) / tileWidth
     *   tilesDown     = (imageHeight + tileHeight - 1) / tileHeight
     *   tilesPerImage = tilesAcross * tilesDown
     * }
     *
     * Note that {@link #imageWidth} can be less than {@code tileWidth} and/or {@link #imageHeight} can be less
     * than {@code tileHeight}. Such case means that the tiles are too large or that the tiled image is too small,
     * neither of which is recommended.
     *
     * <p><b>Note:</b>
     * the {@link #tileHeight} attribute is named {@code TileLength} in TIFF specification.</p>
     *
     * <div class="section">Strips considered as tiles</div>
     * The TIFF specification also defines a {@code RowsPerStrip} tag, which is equivalent to the
     * height of tiles having the same width than the image. While the TIFF specification handles
     * "tiles" and "strips" separately, Apache SIS handles strips as a special kind of tiles where
     * only {@code tileHeight} is specified and {@code tileWidth} defaults to {@link #imageWidth}.
     */
    private int tileWidth = -1, tileHeight = -1;

    /**
     * For each tile, the byte offset of that tile, as compressed and stored on disk.
     * The offset is specified with respect to the beginning of the TIFF file.
     * Each tile has a location independent of the locations of other tiles
     *
     * <p>Offsets are ordered left-to-right and top-to-bottom. if {@link #isPlanar} is {@code true}
     * (i.e. components are stored in separate “component planes”), then the offsets for the first
     * component plane are stored first, followed by all the offsets for the second component plane,
     * and so on.</p>
     *
     * <div class="section">Strips considered as tiles</div>
     * The TIFF specification also defines a {@code StripOffsets} tag, which contains the byte offset
     * of each strip. In Apache SIS implementation, strips are considered as a special kind of tiles
     * having a width equals to {@link #imageWidth}.
     */
    private Vector tileOffsets;

    /**
     * For each tile, the number of (compressed) bytes in that tile.
     * See {@link #tileOffsets} for a description of how the byte counts are ordered.
     *
     * <div class="section">Strips considered as tiles</div>
     * The TIFF specification also defines a {@code RowsPerStrip} tag, which is the number
     * of bytes in the strip after compression. In Apache SIS implementation, strips are
     * considered as a special kind of tiles having a width equals to {@link #imageWidth}.
     */
    private Vector tileByteCounts;

    /**
     * Whether the tiling was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*}
     * family of tags. Value can be {@link #TILE}, {@link #STRIP} or 0 if unspecified. This field is used
     * for error detection since Each TIFF file shall use exactly one family of tags.
     */
    private byte tileTagFamily;

    /**
     * If {@code true}, the components are stored in separate “component planes”.
     * The default is {@code false}, which stands for the "chunky" format
     * (for example RGB data stored as RGBRGBRGB).
     */
    private boolean isPlanar;

    /**
     * How to interpret each data sample in a pixel.
     * Possible values are {@link #SIGNED}, {@link #UNSIGNED} or {@link #FLOAT}.
     */
    private byte sampleFormat;

    /**
     * Whether the bit order should be reversed. This boolean value is determined from the {@code FillOrder} TIFF tag.
     *
     * <ul>
     *   <li>Value 1 (mapped to {@code false}) means that pixels with lower column values are stored in the
     *       higher-order bits of the byte. This is the default value.</li>
     *   <li>Value 2 (mapped to {@code true}) means that pixels with lower column values are stored in the
     *       lower-order bits of the byte. In practice, this order is very uncommon and is not recommended.</li>
     * </ul>
     *
     * Value 1 is mapped to {@code false} and 2 is mapped to {@code true}.
     */
    private boolean reverseBitsOrder;

    /**
     * Number of bits per component.
     * The TIFF specification allows a different number of bits per component for each component corresponding to a pixel.
     * For example, RGB color data could use a different number of bits per component for each of the three color planes.
     * However, current Apache SIS implementation requires that all components have the same {@code BitsPerSample} value.
     */
    private short bitsPerSample;

    /**
     * The number of components per pixel.
     * The {@code samplesPerPixel} value is usually 1 for bilevel, grayscale and palette-color images,
     * and 3 for RGB images. If this value is higher, then the {@code ExtraSamples} TIFF tag should
     * give an indication of the meaning of the additional channels.
     */
    private short samplesPerPixel;

    /**
     * Specifies that each pixel has {@code extraSamples.size()} extra components whose interpretation is defined
     * by one of the values listed below. When this field is used, the {@link #samplesPerPixel} field has a value
     * greater than what the {@link #photometricInterpretation} field suggests. For example, full-color RGB data
     * normally has {@link #samplesPerPixel} = 3. If {@code samplesPerPixel} is greater than 3, then this
     * {@code extraSamples} field describes the meaning of the extra samples. If {@code samplesPerPixel} is,
     * say, 5 then this {@code extraSamples} field will contain 2 values, one for each extra sample.
     *
     * <p>Extra components that are present must be stored as the last components in each pixel.
     * For example, if {@code samplesPerPixel} is 4 and there is 1 extra component, then it is
     * located in the last component location in each pixel.</p>
     *
     * <p>ExtraSamples is typically used to include non-color information, such as opacity, in an image.
     * The possible values for each item are:</p>
     *
     * <ul>
     *   <li>0 = Unspecified data.</li>
     *   <li>1 = Associated alpha data (with pre-multiplied color).</li>
     *   <li>2 = Unassociated alpha data.</li>
     * </ul>
     *
     * Associated alpha is generally interpreted as true transparency information. Indeed, the original color
     * values are lost in the case of complete transparency, and rounded in the case of partial transparency.
     * Also, associated alpha is only logically possible as the single extra channel.
     * Unassociated alpha channels, on the other hand, can be used to encode a number of independent masks.
     * The original color data is preserved without rounding. Any number of unassociated alpha channels can
     * accompany an image.
     *
     * <p>If an extra sample is used to encode information that has little or nothing to do with alpha,
     * then {@code extraSample} = 0 ({@code EXTRASAMPLE_UNSPECIFIED}) is recommended.</p>
     */
    private Vector extraSamples;

    /**
     * The color space of the image data, or -1 if unspecified.
     *
     * <table>
     *   <caption>Color space codes</caption>
     *   <tr><th>Value</th> <th>Label</th>        <th>Description</th></tr>
     *   <tr><td>0</td> <td>WhiteIsZero</td>      <td>For bilevel and grayscale images. 0 is imaged as white.</td></tr>
     *   <tr><td>1</td> <td>BlackIsZero</td>      <td>For bilevel and grayscale images. 0 is imaged as black.</td></tr>
     *   <tr><td>2</td> <td>RGB</td>              <td>RGB value of (0,0,0) represents black, and (255,255,255) represents white.</td></tr>
     *   <tr><td>3</td> <td>PaletteColor</td>     <td>The value of the component is used as an index into the RGB values of the {@link #colorMap}.</td></tr>
     *   <tr><td>4</td> <td>TransparencyMask</td> <td>Defines an irregularly shaped region of another image in the same TIFF file.</td></tr>
     * </table>
     */
    private byte photometricInterpretation = -1;

    /**
     * A color map for palette color images ({@link #photometricInterpretation} = 3).
     * This vector defines a Red-Green-Blue color map (often called a lookup table) for palette-color images.
     * In a palette-color image, a pixel value is used to index into an RGB lookup table. For example, a
     * palette-color pixel having a value of 0 would be displayed according to the 0th Red, Green, Blue triplet.
     *
     * <p>In a TIFF ColorMap, all the Red values come first, followed by all Green values, then all Blue values.
     * The number of values for each color is 1 {@literal <<} {@link #bitsPerSample}. Therefore, the {@code ColorMap}
     * vector for an 8-bit palette-color image would have 3 * 256 values. 0 represents the minimum intensity and 65535
     * represents the maximum intensity. Black is represented by 0,0,0 and white by 65535, 65535, 65535.</p>
     *
     * <p>{@code ColorMap} must be included in all palette-color images.
     * In Specification Supplement 1, support was added for color maps containing other then RGB values.
     * This scheme includes the {@code Indexed} tag, with value 1, and a {@link #photometricInterpretation}
     * different from {@code PaletteColor}.</p>
     */
    private Vector colorMap;

    /**
     * The size of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file.
     * This field should be present only if {@code Threshholding} tag is 2 (an ordered dither or halftone
     * technique has been applied to the image data). Special values:
     *
     * <ul>
     *   <li>-1 means that {@code Threshholding} is 1 or unspecified.</li>
     *   <li>-2 means that {@code Threshholding} is 2 but the matrix size has not yet been specified.</li>
     *   <li>-3 means that {@code Threshholding} is 3 (randomized process such as error diffusion).</li>
     * </ul>
     */
    private short cellWidth = -1, cellHeight = -1;

    /**
     * The minimum or maximum sample value found in the image, with one value per band.
     * May be a vector of length 1 if the same single value applies to all bands.
     */
    private Vector minValues, maxValues;

    /**
     * {@code true} if {@link #minValues} and {@link #maxValues} have been explicitly specified
     * in the TIFF file, or {@code false} if they have been inferred from {@link #bitsPerSample}.
     */
    private boolean isMinSpecified, isMaxSpecified;

    /**
     * The number of pixels per {@link #resolutionUnit} in the {@link #imageWidth} and the {@link #imageHeight}
     * directions, or {@link Double#NaN} is unspecified. Since ISO 19115 does not have separated resolution fields
     * for image width and height, Apache SIS stores only the maximal value.
     */
    private double resolution = Double.NaN;

    /**
     * The unit of measurement for the {@linkplain #resolution} value, or {@code null} if none.
     * A null value is used for images that may have a non-square aspect ratio, but no meaningful
     * absolute dimensions. Default value for TIFF files is inch.
     */
    private Unit<Length> resolutionUnit = Units.INCH;

    /**
     * The compression method, or {@code null} if unknown. If the compression method is unknown
     * or unsupported we can not read the image, but we still can read the metadata.
     */
    private Compression compression;

    /**
     * A helper class for building Coordinate Reference System and complete related metadata.
     * Contains the following information:
     *
     * <ul>
     *   <li>{@link GridGeometryBuilder#keyDirectory}</li>
     *   <li>{@link GridGeometryBuilder#numericParameters}</li>
     *   <li>{@link GridGeometryBuilder#asciiParameters}</li>
     *   <li>{@link GridGeometryBuilder#modelTiePoints}</li>
     * </ul>
     *
     * @see #getGridGeometry()
     */
    private GridGeometryBuilder referencing;

    /**
     * The sample dimensions, or {@code null} if not yet created.
     *
     * @see #getSampleDimensions()
     */
    private List<SampleDimension> sampleDimensions;

    /**
     * Returns {@link #referencing}, created when first needed. We delay its creation since
     * this object is not needed for ordinary TIFF files (i.e. without the GeoTIFF extension).
     */
    private GridGeometryBuilder referencing() {
        if (referencing == null) {
            referencing = new GridGeometryBuilder(reader);
        }
        return referencing;
    }

    /**
     * Creates a new image file directory.
     *
     * @param reader  information about the input stream to read, the metadata and the character encoding.
     * @param index   the image index as a sequence number starting with 0 for the first image.
     */
    ImageFileDirectory(final Reader reader, final int index) {
        super(reader.owner.listeners());
        this.reader = reader;
        identifier = reader.nameFactory.createLocalName(reader.owner.identifier, String.valueOf(index + 1));
    }

    /**
     * Shortcut for a frequently requested information.
     */
    private ChannelDataInput input() {
        return reader.input;
    }

    /**
     * Shortcut for a frequently requested information.
     */
    private String filename() {
        return input().filename;
    }

    /**
     * Shortcut for a frequently requested information.
     */
    private Charset encoding() {
        return reader.owner.encoding;
    }

    /**
     * Returns the identifier as a sequence number in the namespace of the {@link GeoTiffStore}.
     * The first image has the sequence number "1".
     *
     * @see #getMetadata()
     */
    @Override
    public Optional<GenericName> getIdentifier() {
        return Optional.of(identifier);
    }

    /**
     * Adds the value read from the current position in the given stream for the entry identified
     * by the given GeoTIFF tag. This method may store the value either in a field of this class,
     * or directly in the {@link MetadataBuilder}. However in the later case, this method should
     * not write anything under the {@code "metadata/contentInfo"} node.
     *
     * @param  tag    the GeoTIFF tag to decode.
     * @param  type   the GeoTIFF type of the value to read.
     * @param  count  the number of values to read.
     * @return {@code null} on success, or the unrecognized value otherwise.
     * @throws IOException if an error occurred while reading the stream.
     * @throws ParseException if the value need to be parsed as date and the parsing failed.
     * @throws NumberFormatException if the value need to be parsed as number and the parsing failed.
     * @throws ArithmeticException if the value can not be represented in the expected Java type.
     * @throws IllegalArgumentException if a value which was expected to be a singleton is not.
     * @throws UnsupportedOperationException if the given type is {@link Type#UNDEFINED}.
     * @throws DataStoreException if a logical error is found or an unsupported TIFF feature is used.
     */
    Object addEntry(final short tag, final Type type, final long count)
            throws IOException, ParseException, DataStoreException
    {
        switch (tag) {

            ////////////////////////////////////////////////////////////////////////////////////////////////
            ////                                                                                        ////
            ////    Essential information for being able to read the image at least as grayscale.       ////
            ////    In Java2D, following information are needed for building the SampleModel.           ////
            ////                                                                                        ////
            ////////////////////////////////////////////////////////////////////////////////////////////////

            /*
             * How the components of each pixel are stored.
             * 1 = Chunky format. The component values for each pixel are stored contiguously (for example RGBRGBRGB).
             * 2 = Planar format. For example one plane of Red components, one plane of Green and one plane if Blue.
             */
            case Tags.PlanarConfiguration: {
                final int value = type.readInt(input(), count);
                switch (value) {
                    case 1:  isPlanar = false; break;
                    case 2:  isPlanar = true;  break;
                    default: return value;                  // Cause a warning to be reported by the caller.
                }
                break;
            }
            /*
             * The number of columns in the image, i.e., the number of pixels per row.
             */
            case Tags.ImageWidth: {
                imageWidth = type.readUnsignedLong(input(), count);
                break;
            }
            /*
             * The number of rows of pixels in the image.
             */
            case Tags.ImageLength: {
                imageHeight = type.readUnsignedLong(input(), count);
                break;
            }
            /*
             * The tile width in pixels. This is the number of columns in each tile.
             */
            case Tags.TileWidth: {
                setTileTagFamily(TILE);
                tileWidth = type.readInt(input(), count);
                break;
            }
            /*
             * The tile length (height) in pixels. This is the number of rows in each tile.
             */
            case Tags.TileLength: {
                setTileTagFamily(TILE);
                tileHeight = type.readInt(input(), count);
                break;
            }
            /*
             * The number of rows per strip. This is considered by SIS as a special kind of tiles.
             * From this point of view, TileLength = RowPerStrip and TileWidth = ImageWidth.
             */
            case Tags.RowsPerStrip: {
                setTileTagFamily(STRIP);
                tileHeight = type.readInt(input(), count);
                break;
            }
            /*
             * The tile length (height) in pixels. This is the number of rows in each tile.
             */
            case Tags.TileOffsets: {
                setTileTagFamily(TILE);
                tileOffsets = type.readVector(input(), count);
                break;
            }
            /*
             * For each strip, the byte offset of that strip relative to the beginning of the TIFF file.
             * In Apache SIS implementation, strips are considered as a special kind of tiles.
             */
            case Tags.StripOffsets: {
                setTileTagFamily(STRIP);
                tileOffsets = type.readVector(input(), count);
                break;
            }
            /*
             * The tile width in pixels. This is the number of columns in each tile.
             */
            case Tags.TileByteCounts: {
                setTileTagFamily(TILE);
                tileByteCounts = type.readVector(input(), count);
                break;
            }
            /*
             * For each strip, the number of bytes in the strip after compression.
             * In Apache SIS implementation, strips are considered as a special kind of tiles.
             */
            case Tags.StripByteCounts: {
                setTileTagFamily(STRIP);
                tileByteCounts = type.readVector(input(), count);
                break;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////
            ////                                                                                        ////
            ////    Information that defines how the sample values are organized (their layout).        ////
            ////    In Java2D, following information are needed for building the SampleModel.           ////
            ////                                                                                        ////
            ////////////////////////////////////////////////////////////////////////////////////////////////

            /*
             * Compression scheme used on the image data.
             */
            case Tags.Compression: {
                final long value = type.readLong(input(), count);
                compression = Compression.valueOf(value);
                if (compression == null) {
                    return value;                           // Cause a warning to be reported by the caller.
                }
                break;
            }
            /*
             * The logical order of bits within a byte. If this value is 2, then
             * bits order shall be reversed in every bytes before decompression.
             */
            case Tags.FillOrder: {
                final int value = type.readInt(input(), count);
                switch (value) {
                    case 1: reverseBitsOrder = false; break;
                    case 2: reverseBitsOrder = true;  break;
                    default: return value;                  // Cause a warning to be reported by the caller.
                }
                break;
            }
            /*
             * How to interpret each data sample in a pixel. The size of data samples is still
             * specified by the BitsPerSample field.
             */
            case Tags.SampleFormat: {
                final int value = type.readInt(input(), count);
                switch (value) {
                    default: return value;                          // Warning to be reported by the caller.
                    case 1: sampleFormat = UNSIGNED; break;         // Unsigned integer data (default).
                    case 2: sampleFormat = SIGNED;   break;         // Two’s complement signed integer data.
                    case 3: sampleFormat = FLOAT;    break;         // IEEE floating point data.
                    case 4: warning(Level.WARNING, Resources.Keys.UndefinedDataFormat_1, filename()); break;
                }
                break;
            }
            /*
             * Number of bits per component. The array length should be the number of components in a
             * pixel (e.g. 3 for RGB values). Typically, all components have the same number of bits.
             * But the TIFF specification allows different values.
             */
            case Tags.BitsPerSample: {
                final Vector values = type.readVector(input(), count);
                /*
                 * The current implementation requires that all 'bitsPerSample' elements have the same value.
                 * This restriction may be revisited in future Apache SIS versions.
                 * Note: 'count' is never zero when this method is invoked, so we do not need to check bounds.
                 */
                bitsPerSample = values.shortValue(0);
                final int length = values.size();
                for (int i = 1; i < length; i++) {
                    if (values.shortValue(i) != bitsPerSample) {
                        throw new DataStoreContentException(reader.resources().getString(
                                Resources.Keys.ConstantValueRequired_3, "BitsPerSample", filename(), values));
                    }
                }
                break;
            }
            /*
             * The number of components per pixel. Usually 1 for bilevel, grayscale, and palette-color images,
             * and 3 for RGB images. Default value is 1.
             */
            case Tags.SamplesPerPixel: {
                samplesPerPixel = type.readShort(input(), count);
                break;
            }
            /*
             * Specifies that each pixel has N extra components. When this field is used, the SamplesPerPixel field
             * has a value greater than the PhotometricInterpretation field suggests. For example, a full-color RGB
             * image normally has SamplesPerPixel=3. If SamplesPerPixel is greater than 3, then the ExtraSamples field
             * describes the meaning of the extra samples. It may be an alpha channel, but not necessarily.
             */
            case Tags.ExtraSamples: {
                extraSamples = type.readVector(input(), count);
                break;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////
            ////                                                                                        ////
            ////    Information related to the color palette or the meaning of sample values.           ////
            ////    In Java2D, following information are needed for building the ColorModel.            ////
            ////                                                                                        ////
            ////////////////////////////////////////////////////////////////////////////////////////////////

            /*
             * The color space of the image data.
             * 0 = WhiteIsZero. For bilevel and grayscale images: 0 is imaged as white.
             * 1 = BlackIsZero. For bilevel and grayscale images: 0 is imaged as black.
             * 2 = RGB. RGB value of (0,0,0) represents black, and (65535,65535,65535) represents white.
             * 3 = Palette color. The value of the component is used as an index into the RGB values of the ColorMap.
             * 4 = Transparency Mask the defines an irregularly shaped region of another image in the same TIFF file.
             */
            case Tags.PhotometricInterpretation: {
                final short value = type.readShort(input(), count);
                if (value < 0 || value > Byte.MAX_VALUE) return value;
                photometricInterpretation = (byte) value;
                break;
            }
            /*
             * The lookup table for palette-color images. This is represented by IndexColorModel in Java2D.
             * Color space is RGB if PhotometricInterpretation is "PaletteColor", or another color space otherwise.
             * In the RGB case, all the Red values come first, followed by all Green values, then all Blue values.
             * The number of values for each color is (1 << BitsPerSample) where 0 represents the minimum intensity
             * (black is 0,0,0) and 65535 represents the maximum intensity.
             */
            case Tags.ColorMap: {
                colorMap = type.readVector(input(), count);
                break;
            }
            /*
             * The minimum component value used. MinSampleValue is a single value that apply to all bands
             * while SMinSampleValue lists separated values for each band. Default is 0.
             */
            case Tags.MinSampleValue:
            case Tags.SMinSampleValue: {
                minValues = extremum(minValues, type.readVector(input(), count), false);
                isMinSpecified = true;
                break;
            }
            /*
             * The maximum component value used. Default is {@code (1 << BitsPerSample) - 1}.
             * This field is for statistical purposes and should not to be used to affect the
             * visual appearance of an image, unless a map styling is applied.
             */
            case Tags.MaxSampleValue:
            case Tags.SMaxSampleValue: {
                maxValues = extremum(maxValues, type.readVector(input(), count), true);
                isMaxSpecified = true;
                break;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////
            ////                                                                                        ////
            ////    Information useful for defining the image role in a multi-images context.           ////
            ////                                                                                        ////
            ////////////////////////////////////////////////////////////////////////////////////////////////

            /*
             * A general indication of the kind of data contained in this subfile, mainly useful when there
             * are multiple subfiles in a single TIFF file. This field is made up of a set of 32 flag bits.
             *
             * Bit 0 is 1 if the image is a reduced-resolution version of another image in this TIFF file.
             * Bit 1 is 1 if the image is a single page of a multi-page image (see PageNumber).
             * Bit 2 is 1 if the image defines a transparency mask for another image in this TIFF file (see PhotometricInterpretation).
             * Bit 4 indicates MRC imaging model as described in ITU-T recommendation T.44 [T.44] (See ImageLayer tag) - RFC 2301.
             */
            case Tags.NewSubfileType: {
                // TODO
                break;
            }
            /*
             * Old version (now deprecated) of above NewSubfileType.
             * 1 = full-resolution image data
             * 2 = reduced-resolution image data
             * 3 = a single page of a multi-page image (see PageNumber).
             */
            case Tags.SubfileType: {
                // TODO
                break;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////
            ////                                                                                        ////
            ////    Information related to the Coordinate Reference System and the bounding box.        ////
            ////                                                                                        ////
            ////////////////////////////////////////////////////////////////////////////////////////////////

            /*
             * References the "GeoKeys" needed for building the Coordinate Reference System.
             * An array of unsigned SHORT values, which are primarily grouped into blocks of 4.
             * The first 4 values are special, and contain GeoKey directory header information.
             */
            case Tags.GeoKeyDirectory: {
                referencing().keyDirectory = type.readVector(input(), count);
                break;
            }
            /*
             * Stores all of the 'double' valued GeoKeys, referenced by the GeoKeyDirectory.
             */
            case Tags.GeoDoubleParams: {
                referencing().numericParameters = type.readVector(input(), count);
                break;
            }
            /*
             * Stores all the characters referenced by the GeoKeyDirectory. Should contains exactly one string
             * which will be splitted by CRSBuilder, but we allow an arbitrary amount as a paranoiac check.
             * Note that TIFF files use 0 as the end delimiter in strings (C/C++ convention).
             */
            case Tags.GeoAsciiParams: {
                final String[] values = type.readString(input(), count, encoding());
                switch (values.length) {
                    case 0:  break;
                    case 1:  referencing().asciiParameters = values[0]; break;
                    default: referencing().asciiParameters = String.join("\u0000", values).concat("\u0000"); break;
                }
                break;
            }
            /*
             * The orientation of the image with respect to the rows and columns.
             * This is an integer numeroted from 1 to 7 inclusive (see TIFF specification for meaning).
             */
            case Tags.Orientation: {
                // TODO
                break;
            }
            /*
             * The "grid to CRS" conversion as a 4×4 matrix in row-major fashion. The third matrix row and
             * the third matrix column may contain only zero values; this block does not reduce the number
             * of dimensions from 3 to 2.
             */
            case Tags.ModelTransformation: {
                final Vector m = type.readVector(input(), count);
                final int n;
                switch (m.size()) {
                    case  6:                    // Assume 2D model with implicit [0 0 1] last row.
                    case  9: n = 3; break;      // Assume 2D model with full 3×3 matrix.
                    case 12:                    // Assume 3D model with implicit [0 0 0 1] last row.
                    case 16: n = 4; break;      // 3D model with full 4×4 matrix, as required by GeoTIFF spec.
                    default: return m;
                }
                referencing().setGridToCRS(m, n);
                break;
            }
            /*
             * The "grid to CRS" conversion with only the scale factor specified. This block sets the
             * translation column to NaN, meaning that it will need to be computed from the tie point.
             */
            case Tags.ModelPixelScaleTag: {
                final Vector m = type.readVector(input(), count);
                final int size = m.size();
                if (size < 2 || size > 3) {     // Length should be exactly 3, but we make this reader tolerant.
                    return m;
                }
                referencing().setScaleFactors(m);
                break;
            }
            /*
             * The mapping from pixel coordinates to CRS coordinates as a sequence of (I,J,K, X,Y,Z) records.
             */
            case Tags.ModelTiePoints: {
                referencing().modelTiePoints = type.readVector(input(), count);
                break;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////
            ////                                                                                        ////
            ////    Metadata for discovery purposes, conditions of use, etc.                            ////
            ////    Those metadata are not "critical" information for reading the image.                ////
            ////    Should not write anything under 'metadata/contentInfo' node.                        ////
            ////                                                                                        ////
            ////////////////////////////////////////////////////////////////////////////////////////////////

            /*
             * A string that describes the subject of the image.
             * For example, a user may wish to attach a comment such as "1988 company picnic" to an image.
             */
            case Tags.ImageDescription: {
                for (final String value : type.readString(input(), count, encoding())) {
                    reader.metadata.addTitle(value);
                }
                break;
            }
            /*
             * Person who created the image. Some older TIFF files used this tag for storing
             * Copyright information, but Apache SIS does not support this legacy practice.
             */
            case Tags.Artist: {
                for (final String value : type.readString(input(), count, encoding())) {
                    reader.metadata.addAuthor(value);
                }
                break;
            }
            /*
             * Copyright notice of the person or organization that claims the copyright to the image.
             * Example: “Copyright, John Smith, 1992. All rights reserved.”
             */
            case Tags.Copyright: {
                for (final String value : type.readString(input(), count, encoding())) {
                    reader.metadata.parseLegalNotice(value);
                }
                break;
            }
            /*
             * Date and time of image creation. The format is: "YYYY:MM:DD HH:MM:SS" with 24-hour clock.
             */
            case Tags.DateTime: {
                for (final String value : type.readString(input(), count, encoding())) {
                    reader.metadata.addCitationDate(reader.getDateFormat().parse(value),
                            DateType.CREATION, MetadataBuilder.Scope.RESOURCE);
                }
                break;
            }
            /*
             * The computer and/or operating system in use at the time of image creation.
             */
            case Tags.HostComputer: {
                for (final String value : type.readString(input(), count, encoding())) {
                    reader.metadata.addHostComputer(value);
                }
                break;
            }
            /*
             * Name and version number of the software package(s) used to create the image.
             */
            case Tags.Software: {
                for (final String value : type.readString(input(), count, encoding())) {
                    reader.metadata.addSoftwareReference(value);
                }
                break;
            }
            /*
             * Manufacturer of the scanner, video digitizer, or other type of equipment used to generate the image.
             * Synthetic images should not include this field.
             */
            case Tags.Make: {
                // TODO: is Instrument.citation.citedResponsibleParty.party.name an appropriate place?
                // what would be the citation title? A copy of Tags.Model?
                break;
            }
            /*
             * The model name or number of the scanner, video digitizer, or other type of equipment used to
             * generate the image.
             */
            case Tags.Model: {
                for (final String value : type.readString(input(), count, encoding())) {
                    reader.metadata.addInstrument(null, value);
                }
                break;
            }
            /*
             * The number of pixels per ResolutionUnit in the ImageWidth or ImageHeight direction.
             */
            case Tags.XResolution:
            case Tags.YResolution: {
                final double r = type.readDouble(input(), count);
                if (Double.isNaN(resolution) || r > resolution) {
                    resolution = r;
                }
                break;
            }
            /*
             * The unit of measurement for XResolution and YResolution.
             *
             *   1 = None. Used for images that may have a non-square aspect ratio.
             *   2 = Inch (default).
             *   3 = Centimeter.
             */
            case Tags.ResolutionUnit: {
                final short unit = type.readShort(input(), count);
                switch (unit) {
                    case 1:  resolutionUnit = null;             break;
                    case 2:  resolutionUnit = Units.INCH;       break;
                    case 3:  resolutionUnit = Units.CENTIMETRE; break;
                    default: return unit;                   // Cause a warning to be reported by the caller.
                }
                break;
            }
            /*
             * For black and white TIFF files that represent shades of gray, the technique used to convert
             * from gray to black and white pixels. The default value is 1 (nothing done on the image).
             *
             *   1 = No dithering or halftoning has been applied to the image data.
             *   2 = An ordered dither or halftone technique has been applied to the image data.
             *   3 = A randomized process such as error diffusion has been applied to the image data.
             */
            case Tags.Threshholding: {
                final short value = type.readShort(input(), count);
                switch (value) {
                    case 1:  break;
                    case 2:  if (cellWidth >= 0 || cellHeight >= 0) return null; else break;
                    case 3:  break;
                    default: return value;                  // Cause a warning to be reported by the caller.
                }
                cellWidth = cellHeight = (short) -value;
                break;
            }
            /*
             * The width and height of the dithering or halftoning matrix used to create
             * a dithered or halftoned bilevel file. Meaningful only if Threshholding = 2.
             */
            case Tags.CellWidth: {
                cellWidth = type.readShort(input(), count);
                break;
            }
            case Tags.CellLength: {
                cellHeight = type.readShort(input(), count);
                break;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////
            ////                                                                                        ////
            ////    Defined by TIFF specification but currently ignored.                                ////
            ////                                                                                        ////
            ////////////////////////////////////////////////////////////////////////////////////////////////

            /*
             * For each string of contiguous unused bytes in a TIFF file, the number of bytes and the byte offset
             * in the string. Those tags are deprecated and do not need to be supported.
             */
            case Tags.FreeByteCounts:
            case Tags.FreeOffsets:
            /*
             * For grayscale data, the optical density of each possible pixel value, plus the precision of that
             * information. This is ignored by most TIFF readers.
             */
            case Tags.GrayResponseCurve:
            case Tags.GrayResponseUnit: {
                warning(Level.FINE, Resources.Keys.IgnoredTag_1, Tags.name(tag));
                break;
            }
        }
        return null;
    }

    /**
     * Sets the {@link #tileTagFamily} field to the given value if it does not conflict with previous value.
     *
     * @param  family  either {@link #TILE} or {@link #STRIP}.
     * @throws DataStoreContentException if {@link #tileTagFamily} is already set to another value.
     */
    private void setTileTagFamily(final byte family) throws DataStoreContentException {
        if (tileTagFamily != family && tileTagFamily != 0) {
            throw new DataStoreContentException(reader.resources().getString(
                    Resources.Keys.InconsistentTileStrip_1, filename()));
        }
        tileTagFamily = family;
    }

    /**
     * Computes the minimal or maximal values of the given vector. Those vectors do not need to have the same length.
     * One of those two vector will be modified in-place.
     *
     * @param  a    the first vector, or {@code null} if none.
     * @param  b    the new vector to combine with the existing one. Can not be null.
     * @param  max  {@code true} for computing the maximal values, or {@code false} for the minimal value.
     */
    private static Vector extremum(Vector a, Vector b, final boolean max) {
        if (a != null) {
            int s = b.size();
            int i = a.size();
            if (i > s) {                            // If a vector is longer than b, swap a and b.
                i = s;
                final Vector t = a; a = b; b = t;
            }
            while (--i >= 0) {                      // At this point, 'b' shall be the longest vector.
                final double va = a.doubleValue(i);
                final double vb = b.doubleValue(i);
                if (Double.isNaN(vb) || (max ? va > vb : va < vb)) {
                    b.set(i, va);
                }
            }
        }
        return b;
    }

    /**
     * Multiplies the given value by the number of bytes in one pixel,
     * or return -1 if the result is not an integer.
     *
     * @throws ArithmeticException if the result overflows.
     */
    private long pixelToByteCount(long value) {
        value = Math.multiplyExact(value, samplesPerPixel * (int) bitsPerSample);
        return (value % Byte.SIZE == 0) ? value / Byte.SIZE : -1;
    }

    /**
     * Computes the tile width or height from the other size,
     * or returns a negative number if the size can not be computed.
     *
     * @param  knownSize  the tile width or height.
     * @return the tile width if the known size was height, or the tile height if the known size was width,
     *         or a negative number if the width or height can not be computed.
     * @throws ArithmeticException if the result overflows.
     */
    private int computeTileSize(final int knownSize) {
        final int n = tileByteCounts.size();
        if (n != 0) {
            final long count = tileByteCounts.longValue(0);
            int i = 0;
            do if (++i == n) {
                // At this point, we verified that all vector values are equal.
                final long length = pixelToByteCount(knownSize);
                if (count % length != 0) break;
                return Math.toIntExact(count / length);
            } while (tileByteCounts.longValue(i) == n);
        }
        return -1;
    }

    /**
     * Verifies that the mandatory tags are present and consistent with each others.
     * If a mandatory tag is absent, then there is a choice:
     *
     * <ul>
     *   <li>If the tag can be inferred from other tag values, performs that computation and logs a warning.</li>
     *   <li>Otherwise throws an exception.</li>
     * </ul>
     *
     * This method opportunistically computes default value of optional fields
     * when those values can be computed from other (usually mandatory) fields.
     *
     * @throws DataStoreContentException if a mandatory tag is missing and can not be inferred.
     */
    final void validateMandatoryTags() throws DataStoreContentException {
        if (imageWidth  < 0) throw missingTag(Tags.ImageWidth);
        if (imageHeight < 0) throw missingTag(Tags.ImageLength);
        final short offsetsTag, byteCountsTag;
        switch (tileTagFamily) {
            case STRIP: {
                if (tileWidth  < 0) tileWidth  = Math.toIntExact(imageWidth);
                if (tileHeight < 0) tileHeight = Math.toIntExact(imageHeight);
                offsetsTag    = Tags.StripOffsets;
                byteCountsTag = Tags.StripByteCounts;
                break;
            }
            case TILE:  {
                offsetsTag    = Tags.TileOffsets;
                byteCountsTag = Tags.TileByteCounts;
                break;
            }
            default: {
                throw new DataStoreContentException(reader.resources().getString(
                        Resources.Keys.InconsistentTileStrip_1, filename()));
            }
        }
        if (tileOffsets == null) {
            throw missingTag(offsetsTag);
        }
        if (samplesPerPixel == 0) {
            samplesPerPixel = 1;
            missingTag(Tags.SamplesPerPixel, 1, false);
        }
        if (bitsPerSample == 0) {
            bitsPerSample = 1;
            missingTag(Tags.BitsPerSample, 1, false);
        }
        if (colorMap != null) {
            ensureSameLength(Tags.ColorMap, Tags.BitsPerSample, colorMap.size(),  3 * (1 << bitsPerSample));
        }
        if (sampleFormat != FLOAT) {
            long minValue, maxValue;
            if (sampleFormat == UNSIGNED) {
                minValue =  0L;
                maxValue = -1L;                 // All bits set to 1.
            } else {
                minValue = Long.MIN_VALUE;
                maxValue = Long.MAX_VALUE;
            }
            final int shift = Long.SIZE - bitsPerSample;
            if (shift >= 0 && shift < Long.SIZE) {
                minValue >>>= shift;
                maxValue >>>= shift;
                if (minValue < maxValue) {      // Exclude the unsigned long case since we can not represent it.
                    minValues = extremum(minValues, Vector.createSequence(minValue, 0, samplesPerPixel), false);
                    maxValues = extremum(maxValues, Vector.createSequence(maxValue, 0, samplesPerPixel), true);
                }
            }
        }
        /*
         * All of tile width, height and length information should be provided. But if only one of them is missing,
         * we can compute it provided that the file does not use any compression method. If there is a compression,
         * then we set a bit for preventing the 'switch' block to perform a calculation but we let the code performs
         * the other checks in order to get an exception to be thrown with a good message.
         */
        int missing = !isPlanar && compression.equals(Compression.NONE) ? 0 : 0b1000;
        if (tileWidth      < 0)     missing |= 0b0001;
        if (tileHeight     < 0)     missing |= 0b0010;
        if (tileByteCounts == null) missing |= 0b0100;
        switch (missing) {
            case 0:
            case 0b1000: {          // Every thing is ok.
                break;
            }
            case 0b0001: {          // Compute missing tile width.
                tileWidth = computeTileSize(tileHeight);
                missingTag(Tags.TileWidth, tileWidth, true);
                break;
            }
            case 0b0010: {          // Compute missing tile height.
                tileHeight = computeTileSize(tileWidth);
                missingTag(Tags.TileLength, tileHeight, true);
                break;
            }
            case 0b0100: {          // Compute missing tile byte count.
                final long tileByteCount = pixelToByteCount(Math.multiplyExact(tileWidth, tileHeight));
                final long[] tileByteCountArray = new long[tileOffsets.size()];
                Arrays.fill(tileByteCountArray, tileByteCount);
                tileByteCounts = Vector.create(tileByteCountArray, true);
                missingTag(byteCountsTag, tileByteCount, true);
                break;
            }
            default: {
                final short tag;
                switch (Integer.lowestOneBit(missing)) {
                    case 0b0001: tag = Tags.TileWidth;  break;
                    case 0b0010: tag = Tags.TileLength; break;
                    default:     tag = byteCountsTag;   break;
                }
                throw missingTag(tag);
            }
        }
        /*
         * Report an error if the tile offset and tile byte count vectors do not have the same length.
         * Then ensure that the number of tiles is equal to the expected number. The formula below is the
         * one documented in the TIFF specification and reproduced in tileWidth & tileHeight fields javadoc.
         */
        ensureSameLength(offsetsTag, byteCountsTag, tileOffsets.size(), tileByteCounts.size());
        long expectedCount = Math.multiplyExact(
                Math.addExact(imageWidth,  tileWidth  - 1) / tileWidth,
                Math.addExact(imageHeight, tileHeight - 1) / tileHeight);
        if (isPlanar) {
            expectedCount = Math.multiplyExact(expectedCount, samplesPerPixel);
        }
        final int actualCount = Math.min(tileOffsets.size(), tileByteCounts.size());
        if (actualCount != expectedCount) {
            throw new DataStoreContentException(reader.resources().getString(Resources.Keys.UnexpectedTileCount_3,
                    filename(), expectedCount, actualCount));
        }
        /*
         * If a "grid to CRS" conversion has been specified with only the scale factor, we need to compute
         * the translation terms now.
         */
        if (referencing != null && !referencing.validateMandatoryTags()) {
            throw missingTag(Tags.ModelTiePoints);
        }
    }

    /**
     * Completes the metadata with the information stored in the field of this IFD.
     * This method is invoked only if the user requested the ISO 19115 metadata.
     * This method creates a new {@code "metadata/contentInfo"} node for this image.
     * Information not under the {@code "metadata/contentInfo"} node will be merged
     * with the current content of the given {@code MetadataBuilder}.
     *
     * @param   metadata  where to write metadata information. Caller should have already invoked
     *                    {@link MetadataBuilder#setFormat(String)} before {@code completeMetadata(…)} calls.
     */
    final void completeMetadata(final MetadataBuilder metadata, final Locale locale)
            throws DataStoreContentException, FactoryException
    {
        metadata.newCoverage(false);
        if (compression != null) {
            metadata.addCompression(compression.name().toLowerCase(locale));
        }
        for (int band = 0; band < samplesPerPixel;) {
            metadata.newSampleDimension();
            metadata.setBitPerSample(bitsPerSample);
            if (isMinSpecified) metadata.addMinimumSampleValue(minValues.doubleValue(Math.min(band, minValues.size()-1)));
            if (isMaxSpecified) metadata.addMaximumSampleValue(maxValues.doubleValue(Math.min(band, maxValues.size()-1)));
            metadata.setBandIdentifier(++band);
        }
        /*
         * Add the resolution into the metadata. Our current ISO 19115 implementation restricts
         * the resolution unit to metres, but it may be relaxed in a future SIS version.
         */
        if (!Double.isNaN(resolution) && resolutionUnit != null) {
            metadata.addResolution(resolutionUnit.getConverterTo(Units.METRE).convert(resolution));
        }
        /*
         * Cell size is relevant only if the Threshholding TIFF tag value is 2. By convention in
         * this implementation class, other Threshholding values are stored as negative cell sizes:
         *
         *   -1 means that Threshholding is 1 or unspecified.
         *   -2 means that Threshholding is 2 but the matrix size has not yet been specified.
         *   -3 means that Threshholding is 3 (randomized process such as error diffusion).
         */
        switch (Math.min(cellWidth, cellHeight)) {
            case -1: {
                // Nothing to report.
                break;
            }
            case -3: {
                metadata.addProcessDescription(Resources.formatInternational(Resources.Keys.RandomizedProcessApplied));
                break;
            }
            default: {
                metadata.addProcessDescription(Resources.formatInternational(
                            Resources.Keys.DitheringOrHalftoningApplied_2,
                            (cellWidth  >= 0) ? cellWidth  : '?',
                            (cellHeight >= 0) ? cellHeight : '?'));
                break;
            }
        }
        /*
         * Add Coordinate Reference System built from GeoTIFF tags.  Note that the CRS may not exist,
         * in which case the CRS builder returns null. This is safe since all MetadataBuilder methods
         * ignore null values (a design choice because this pattern come very often).
         */
        if (referencing != null) {
            getGridGeometry();                  // For calculation of gridGeometry if not already done.
            referencing.completeMetadata(metadata);
        }
    }

    @Override
    protected void createMetadata(final MetadataBuilder metadata) throws DataStoreException {
        super.createMetadata(metadata);
        /*
         * TODO:
         *   - Modify ImageFileDirectory.completeMetadata(…) with the addition of a boolean telling that
         *     that we invoke this method for a single image instead than the whole image. Use that flag
         *     for skipping MetadataBuilder calls writing in metadata/identificationInfo/resourceFormat.
         *   - Invoke ImageFileDirectory.completeMetadata(…) if not already done and cache in a field.
         *   - Add a metadata utility method taking two Metadata in argument, search for properties that
         *     are equal and replace them by the same instance.
         *   - Invoke that method from here if GeoTiffStore already has a metadata, or conversely from
         *     GeoTiffStore if ImageResource already has a metadata.
         */
    }

    /**
     * Returns an object containing the image size, the CRS and the conversion from pixel indices to CRS coordinates.
     */
    @Override
    public GridGeometry getGridGeometry() throws DataStoreContentException {
        if (referencing != null) {
            GridGeometry gridGeometry = referencing.gridGeometry;
            if (gridGeometry == null) try {
                gridGeometry = referencing.build(imageWidth, imageHeight);
            } catch (FactoryException e) {
                throw new DataStoreContentException(reader.resources().getString(Resources.Keys.CanNotComputeGridGeometry_1, filename()), e);
            }
            return gridGeometry;
        } else {
            return new GridGeometry(new GridExtent(imageWidth, imageHeight), null);
        }
    }

    /**
     * Returns the ranges of sample values together with the conversion from samples to real values.
     */
    @Override
    @SuppressWarnings("ReturnOfCollectionOrArrayField")
    public List<SampleDimension> getSampleDimensions() throws DataStoreContentException {
        if (sampleDimensions == null) {
            final SampleDimension[] dimensions = new SampleDimension[samplesPerPixel];
            final SampleDimension.Builder builder = new SampleDimension.Builder();
            final InternationalString name = Vocabulary.formatInternational(Vocabulary.Keys.Value);
            for (int band = 0; band < samplesPerPixel;) {
                builder.addQualitative(name, minValues.get(Math.min(band, minValues.size()-1)),
                                             maxValues.get(Math.min(band, maxValues.size()-1)));
                dimensions[band] = builder.setName(++band).build();
                builder.clear();
            }
            sampleDimensions = UnmodifiableArrayList.wrap(dimensions);
        }
        return sampleDimensions;        // Safe because unmodifiable.
    }

    /**
     * Loads a subset of the grid coverage represented by this resource.
     *
     * @param  domain  desired grid extent and resolution, or {@code null} for reading the whole domain.
     * @param  range   0-based index of sample dimensions to read, or an empty sequence for reading all ranges.
     * @return the grid coverage for the specified domain and range.
     * @throws DataStoreException if an error occurred while reading the grid coverage data.
     */
    @Override
    public GridCoverage read(final GridGeometry domain, final int... range) throws DataStoreException {
        throw new DataStoreException("Not yet implemented.");   // TODO
    }

    /**
     * Reports a warning with a message created from the given resource keys and parameters.
     *
     * @param  level       the logging level for the message to log.
     * @param  key         the {@code Resources} key of the message to format.
     * @param  parameters  the parameters to put in the message.
     */
    private void warning(final Level level, final short key, final Object... parameters) {
        final LogRecord r = reader.resources().getLogRecord(level, key, parameters);
        reader.owner.warning(r);
    }

    /**
     * Verifies that the given tags have the same length and reports a warning if they do not.
     */
    private void ensureSameLength(final short tag1, final short tag2, final int length1, final int length2) {
        if (length1 != length2) {
            warning(Level.WARNING, Resources.Keys.MismatchedLength_4, Tags.name(tag1), Tags.name(tag2), length1, length2);
        }
    }

    /**
     * Reports a warning for a missing TIFF tag for which a default value can be computed.
     *
     * @param  missing   the numerical value of the missing tag.
     * @param  value     the default value or the computed value.
     * @param  computed  whether the default value has been computed.
     */
    private void missingTag(final short missing, final long value, final boolean computed) {
        warning(computed ? Level.WARNING : Level.FINE,
                computed ? Resources.Keys.ComputedValueForAttribute_2 : Resources.Keys.DefaultValueForAttribute_2,
                Tags.name(missing), value);
    }

    /**
     * Builds an exception for a missing TIFF tag for which no default value can be computed.
     *
     * @param  missing  the numerical value of the missing tag.
     */
    private DataStoreContentException missingTag(final short missing) {
        return new DataStoreContentException(reader.resources().getString(
                Resources.Keys.MissingValue_2, filename(), Tags.name(missing)));
    }
}
