/*
 * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.imageio.plugins.tiff;

import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageReadParam;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
import javax.imageio.plugins.tiff.TIFFField;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;

public class TIFFJPEGDecompressor extends TIFFDecompressor {
    // Start of Image
    protected static final int SOI = 0xD8;

    // End of Image
    protected static final int EOI = 0xD9;

    protected ImageReader JPEGReader = null;
    protected ImageReadParam JPEGParam;

    protected boolean hasJPEGTables = false;
    protected byte[] tables = null;

    private byte[] data = new byte[0];

    public TIFFJPEGDecompressor() {}

    @Override
    public void beginDecoding() {
        // Initialize the JPEG reader if needed.
        if(this.JPEGReader == null) {
            // Get all JPEG readers.
            Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("jpeg");

            if(!iter.hasNext()) {
                throw new IllegalStateException("No JPEG readers found!");
            }

            // Initialize reader to the first one.
            this.JPEGReader = iter.next();

            // The JDK built-in ImageReader will self-dispose.
            // So a Disposer is only needed here if it is an unknown reader.
            // This is not common, so likely this will rarely be needed.
            if (!(this.JPEGReader instanceof com.sun.imageio.plugins.jpeg.JPEGImageReader)) {
                Disposer.addRecord(this, new ImageReaderDisposerRecord(this.JPEGReader));
            }

            this.JPEGParam = JPEGReader.getDefaultReadParam();
        }

        // Get the JPEGTables field.
        TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata;
        TIFFField f =
            tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);

        if (f != null) {
            this.hasJPEGTables = true;
            this.tables = f.getAsBytes();
        } else {
            this.hasJPEGTables = false;
        }
    }

    @Override
    public void decodeRaw(byte[] b,
                          int dstOffset,
                          int bitsPerPixel,
                          int scanlineStride) throws IOException {
        // Seek to the data position for this segment.
        stream.seek(offset);

        // Set the stream variable depending on presence of JPEGTables.
        ImageInputStream is;
        if(this.hasJPEGTables) {
            // The current strip or tile is an abbreviated JPEG stream.

            // Reallocate memory if there is not enough already.
            int dataLength = tables.length + byteCount;
            if(data.length < dataLength) {
                data = new byte[dataLength];
            }

            // Copy the tables ignoring any EOI and subsequent bytes.
            int dataOffset = tables.length;
            for(int i = tables.length - 2; i > 0; i--) {
                if((tables[i] & 0xff) == 0xff &&
                   (tables[i+1] & 0xff) == EOI) {
                    dataOffset = i;
                    break;
                }
            }
            System.arraycopy(tables, 0, data, 0, dataOffset);

            // Check for SOI and skip it if present.
            byte byte1 = (byte)stream.read();
            byte byte2 = (byte)stream.read();
            if(!((byte1 & 0xff) == 0xff && (byte2 & 0xff) == SOI)) {
                data[dataOffset++] = byte1;
                data[dataOffset++] = byte2;
            }

            // Read remaining data.
            stream.readFully(data, dataOffset, byteCount - 2);

            // Create ImageInputStream.
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            is = new MemoryCacheImageInputStream(bais);
        } else {
            // The current strip or tile is a complete JPEG stream.
            is = stream;
        }

        // Set the stream on the reader.
        JPEGReader.setInput(is, false, true);

        // Set the destination to the raw image ignoring the parameters.
        JPEGParam.setDestination(rawImage);

        // Read the strip or tile.
        JPEGReader.read(0, JPEGParam);
    }

    private static class ImageReaderDisposerRecord implements DisposerRecord {
        private final ImageReader reader;

        public ImageReaderDisposerRecord(ImageReader reader) {
            this.reader = reader;
        }

        @Override
        public void dispose() {
            reader.dispose();
        }
    }
}
