/*
 * Copyright 2020 Laszlo Balazs-Csiki and Contributors
 *
 * This file is part of Pixelitor. Pixelitor is free software: you
 * can redistribute it and/or modify it under the terms of the GNU
 * General Public License, version 3 as published by the Free
 * Software Foundation.
 *
 * Pixelitor 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Pixelitor. If not, see <http://www.gnu.org/licenses/>.
 */

package pixelitor.io;

import pixelitor.utils.*;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import static javax.imageio.ImageWriteParam.*;

/**
 * Utility class with static methods related to writing JPEG images
 */
public final class JpegOutput {
    private JpegOutput() {
    }

    public static void write(BufferedImage image, File file, JpegInfo config) throws IOException {
        ImageOutputStream ios = ImageIO.createImageOutputStream(file);
        if (ios == null) {
            TrackedIO.throwNoIOSErrorFor(file);
        }
        var tracker = new StatusBarProgressTracker("Writing " + file.getName(), 100);
        writeJPGtoStream(image, ios, config, tracker);
    }

    public static ImageWithSize writeJPGtoPreviewImage(BufferedImage image, JpegInfo config, ProgressTracker pt) {
        var bos = new ByteArrayOutputStream(32768);
        BufferedImage previewImage = null;
        byte[] bytes = null;
        try {
            // writes the JPEG format with the given settings to memory...
            // approximately 70% of the total time is spent here
            ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
            var pt1 = new SubtaskProgressTracker(0.7, pt);
            writeJPGtoStream(image, ios, config, pt1);

            // ...then reads it back into an image
            // approximately 30% of the total time is spent here
            bytes = bos.toByteArray();
            var in = new ByteArrayInputStream(bytes);
            var pt2 = new SubtaskProgressTracker(0.3, pt);
            try (ImageInputStream iis = ImageIO.createImageInputStream(in)) {
                previewImage = TrackedIO.readFromIIS(iis, pt2);
            }

            pt.finished();
        } catch (IOException e) {
            Messages.showException(e);
        }

        int sizeInBytes = bytes.length;

        return new ImageWithSize(previewImage, sizeInBytes);
    }

    private static void writeJPGtoStream(BufferedImage image,
                                         ImageInputStream ios,
                                         JpegInfo config,
                                         ProgressTracker tracker) throws IOException {
        Iterator<ImageWriter> jpgWriters = ImageIO.getImageWritersByFormatName("jpg");
        if (!jpgWriters.hasNext()) {
            throw new IllegalStateException("No JPG writers found");
        }
        ImageWriter writer = jpgWriters.next();

        ImageWriteParam imageWriteParam = writer.getDefaultWriteParam();

        if (config.isProgressive()) {
            imageWriteParam.setProgressiveMode(MODE_DEFAULT);
        } else {
            imageWriteParam.setProgressiveMode(MODE_DISABLED);
        }

        imageWriteParam.setCompressionMode(MODE_EXPLICIT);
        imageWriteParam.setCompressionQuality(config.getQuality());

        IIOImage iioImage = new IIOImage(image, null, null);

        writer.setOutput(ios);
        if (tracker != null) {
            writer.addIIOWriteProgressListener(new TrackerWriteProgressListener(tracker));
        }
        writer.write(null, iioImage, imageWriteParam);

        ios.flush();
        ios.close();
    }

    static class ImageWithSize {
        final BufferedImage image;
        final int size;

        private ImageWithSize(BufferedImage image, int size) {
            this.image = image;
            this.size = size;
        }

        public BufferedImage getImage() {
            return image;
        }

        public int getSize() {
            return size;
        }
    }
}