/*
 * 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.xmlgraphics.image.codec.tiff;

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

final class ImageInfo {

    // Default values
    private static final int DEFAULT_ROWS_PER_STRIP = 8;

    private final int numExtraSamples;
    private final ExtraSamplesType extraSampleType;
    private final ImageType imageType;
    private final int colormapSize;
    private final char[] colormap;
    private final int tileWidth;
    private final int tileHeight;
    private final int numTiles;
    private final long bytesPerRow;
    private final long bytesPerTile;

    private ImageInfo(ImageInfoBuilder builder) {
        this.numExtraSamples = builder.numExtraSamples;
        this.extraSampleType = builder.extraSampleType;
        this.imageType = builder.imageType;
        this.colormapSize = builder.colormapSize;
        this.colormap = copyColormap(builder.colormap);
        this.tileWidth = builder.tileWidth;
        this.tileHeight = builder.tileHeight;
        this.numTiles = builder.numTiles;
        this.bytesPerRow = builder.bytesPerRow;
        this.bytesPerTile = builder.bytesPerTile;
    }

    private static char[] copyColormap(char[] colorMap) {
        if (colorMap == null) {
            return null;
        }
        char[] copy = new char[colorMap.length];
        System.arraycopy(colorMap, 0, copy, 0, colorMap.length);
        return copy;
    }

    private static int getNumberOfExtraSamplesForColorSpace(ColorSpace colorSpace,
            ImageType imageType, int numBands) {
        if (imageType == ImageType.GENERIC) {
            return numBands - 1;
        } else if (numBands > 1) {
            return numBands - colorSpace.getNumComponents();
        } else {
            return 0;
        }
    }

    private static char[] createColormap(final int sizeOfColormap, byte[] r, byte[] g, byte[] b) {
        int redIndex = 0;
        int greenIndex = sizeOfColormap;
        int blueIndex = 2 * sizeOfColormap;
        char[] colormap = new char[sizeOfColormap * 3];
        for (int i = 0; i < sizeOfColormap; i++) {
            // beware of sign extended bytes
            colormap[redIndex++] = convertColorToColormapChar(0xff & r[i]);
            colormap[greenIndex++] = convertColorToColormapChar(0xff & g[i]);
            colormap[blueIndex++] = convertColorToColormapChar(0xff & b[i]);
        }
        return colormap;
    }

    private static char convertColorToColormapChar(int color) {
        return (char) (color << 8 | color);
    }

    int getNumberOfExtraSamples() {
        return numExtraSamples;
    }

    ExtraSamplesType getExtraSamplesType() {
        return extraSampleType;
    }

    ImageType getType() {
        return imageType;
    }

    int getColormapSize() {
        return colormapSize;
    }

    char[] getColormap() {
        return copyColormap(colormap);
    }

    int getTileWidth() {
        return tileWidth;
    }

    int getTileHeight() {
        return tileHeight;
    }

    int getNumTiles() {
        return numTiles;
    }

    long getBytesPerRow() {
        return bytesPerRow;
    }

    long getBytesPerTile() {
        return bytesPerTile;
    }

    static ImageInfo newInstance(RenderedImage im, int dataTypeSize, int numBands,
            ColorModel colorModel, TIFFEncodeParam params) {
        ImageInfoBuilder builder = new ImageInfoBuilder();
        if (colorModel instanceof IndexColorModel) { // Bilevel or palette
            IndexColorModel indexColorModel = (IndexColorModel) colorModel;
            int colormapSize = indexColorModel.getMapSize();
            byte[] r = new byte[colormapSize];
            indexColorModel.getReds(r);
            byte[] g = new byte[colormapSize];
            indexColorModel.getGreens(g);
            byte[] b = new byte[colormapSize];
            indexColorModel.getBlues(b);

            builder.imageType = ImageType.getTypeFromRGB(colormapSize, r, g, b, dataTypeSize,
                    numBands);
            if (builder.imageType == ImageType.PALETTE) {
                builder.colormap = createColormap(colormapSize, r, g, b);
                builder.colormapSize = colormapSize * 3;
            }
        } else if (colorModel == null) {
            if (dataTypeSize == 1 && numBands == 1) { // bilevel
                builder.imageType = ImageType.BILEVEL_BLACK_IS_ZERO;
            } else {
                builder.imageType = ImageType.GENERIC;
                builder.numExtraSamples = numBands > 1 ? numBands - 1 : 0;
            }
        } else {
            ColorSpace colorSpace = colorModel.getColorSpace();
            builder.imageType = ImageType.getTypeFromColorSpace(colorSpace, params);
            builder.numExtraSamples = getNumberOfExtraSamplesForColorSpace(colorSpace,
                    builder.imageType, numBands);
            builder.extraSampleType = ExtraSamplesType.getValue(colorModel,
                    builder.numExtraSamples);
        }

        // Initialize tile dimensions.
        final int width = im.getWidth();
        final int height = im.getHeight();
        if (params.getWriteTiled()) {
            builder.tileWidth = params.getTileWidth() > 0 ? params.getTileWidth() : width;
            builder.tileHeight = params.getTileHeight() > 0 ? params.getTileHeight() : height;
            // NB: Parentheses are used in this statement for correct rounding.
            builder.numTiles = ((width + builder.tileWidth - 1) / builder.tileWidth)
                    * ((height + builder.tileHeight - 1) / builder.tileHeight);
        } else {
            builder.tileWidth = width;
            builder.tileHeight = params.getTileHeight() > 0 ? params.getTileHeight()
                    : DEFAULT_ROWS_PER_STRIP;
            builder.numTiles = (int) Math.ceil(height / (double) builder.tileHeight);
        }
        builder.setBytesPerRow(dataTypeSize, numBands)
        .setBytesPerTile();
        return builder.build();
    }

    private static final class ImageInfoBuilder {
        private ImageType imageType = ImageType.UNSUPPORTED;
        private int numExtraSamples;
        private char[] colormap;
        private int colormapSize;
        private ExtraSamplesType extraSampleType = ExtraSamplesType.UNSPECIFIED;
        private int tileWidth;
        private int tileHeight;
        private int numTiles;
        private long bytesPerRow;
        private long bytesPerTile;

        private ImageInfoBuilder setBytesPerRow(int dataTypeSize, int numBands) {
            bytesPerRow = (long) Math.ceil((dataTypeSize / 8.0) * tileWidth * numBands);
            return this;
        }

        private ImageInfoBuilder setBytesPerTile() {
            bytesPerTile = bytesPerRow * tileHeight;
            return this;
        }

        private ImageInfo build() {
            return new ImageInfo(this);
        }
    }
}