/*
 * Copyright (c) 2014, 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.javafx.iio.bmp;

import com.sun.javafx.iio.ImageFrame;
import com.sun.javafx.iio.ImageLoader;
import com.sun.javafx.iio.ImageLoaderFactory;
import com.sun.javafx.iio.ImageTestHelper;
import static com.sun.javafx.iio.bmp.BMPImageLoader.checkDisjointMasks;
import static com.sun.javafx.iio.bmp.BMPImageLoader.isPow2Minus1;
import com.sun.prism.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.imageio.stream.MemoryCacheImageInputStream;
import static org.junit.Assert.*;
import org.junit.Test;

public class BMPImageLoaderTest {
    // if true, the test will write BMP files generated by JDK to the current directory
    static final boolean writeFiles = false;
    static final int testWidth = 509, testHeight = 157;

    int getByte(int dword, int shift) {
        return (dword >> shift) & 0xff;
    }

    boolean compareByte(int p1, int p2, int shift, int tolerance) {
        return Math.abs(getByte(p1, shift) - getByte(p2, shift)) <= tolerance;
    }

    boolean compareRGB(int p1, int p2, int tolerance) {
        return compareByte(p1, p2, 24, tolerance) &&
               compareByte(p1, p2, 16, tolerance) &&
               compareByte(p1, p2, 8,  tolerance);
    }

    void compare(Image img, BufferedImage bImg) {
        assertNotNull(img);
        assertNotNull(bImg);
        int w = bImg.getWidth(), h = bImg.getHeight();
        assertEquals("Unmatched width", w, img.getWidth());
        assertEquals("Unmatched height", h, img.getHeight());

        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                int p1 = bImg.getRGB(x, y);
                int p2 = img.getArgb(x, y);
                if (!compareRGB(p1, p2, 1)) {
                    throw new org.junit.ComparisonFailure(
                        "pixel " + x + ", " + y + " does not match",
                        String.format("0x%08X", p1), String.format("0x%08X", p2)
                    );
                }
            }
        }
    }

    Image loadImage(InputStream stream) throws IOException {
        ImageLoaderFactory loaderFactory = BMPImageLoaderFactory.getInstance();
        ImageLoader loader = loaderFactory.createImageLoader(stream);
        assertNotNull(loader);

        ImageFrame frame = loader.load(0, 0, 0, true, true);
        return Image.convertImageFrame(frame);
    }

    BufferedImage create4BitImage() {
        int[] cmap = new int[16];
        int i = 0;
        for (int r = 0; r < 2; r++) {
            for (int g = 0; g < 2; g++) {
                for (int b = 0; b < 2; b++) {
                    cmap[i++] = 0xff << 24 | r * 255 << 16 | g * 255 << 8 | b * 255;
                    if ((r | g | b) == 0) {
                        cmap[i++] = 0xffc0c0c0;
                    } else {
                        cmap[i++] = 0xff << 24 | r * 128 << 16 | g * 128 << 8 | b * 128;
                    }
                }
            }
        }
        IndexColorModel cm = new IndexColorModel(4, 16, cmap, 0, false, -1, DataBuffer.TYPE_BYTE);
        return new BufferedImage(testWidth, testHeight, BufferedImage.TYPE_BYTE_BINARY, cm);
    }

    BufferedImage createImage(int type) {
        return new BufferedImage(testWidth, testHeight, type);
    }

    void writeBMPFile(BufferedImage bImg, String fileName, String compression) {
        try {
            ImageTestHelper.writeImage(bImg, fileName, "bmp", compression);
        } catch (IOException e) {
            System.out.println("writeBMPFile " + fileName + " failed: " + e);
        }
    }

    Image getImage(BufferedImage bImg, String compression) throws IOException {
        ByteArrayInputStream stream =
                ImageTestHelper.writeImageToStream(bImg, "bmp", compression);
        return loadImage(stream);
    }

    void testImageType(int type, String fileName, String compression) throws IOException {
        BufferedImage bImg = createImage(type);
        testImage(bImg, fileName, compression);
    }

    void testImageType(int type, String fileName) throws IOException {
        BufferedImage bImg = createImage(type);
        testImage(bImg, fileName, null);
    }

    void testImage(BufferedImage bImg, String fileName, String compression) throws IOException {
        //ImageTestHelper.drawImageHue(bImg);
        //ImageTestHelper.drawImageAll(bImg);
        ImageTestHelper.drawImageRandom(bImg);
        if (writeFiles) {
            writeBMPFile(bImg, fileName, compression);
        }
        Image image = getImage(bImg, compression);
        compare(image, bImg);
    }

    @Test
    public void testRT32213() throws IOException  {
        final int[] bytes = {
            0x42, 0x4d, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x28, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0x00,
            0x00, 0x00
        };

        ByteArrayInputStream stream = ImageTestHelper.constructStreamFromInts(bytes);
        Image image = loadImage(stream);
        stream.reset();
        BufferedImage bImg = ImageIO.read(new MemoryCacheImageInputStream(stream));
        compare(image, bImg);
    }

    @Test
    public void testRT15619() throws IOException {
        InputStream stream = ImageTestHelper.createTestImageStream("bmp");
        InputStream testStream = ImageTestHelper.createStutteringInputStream(stream);
        loadImage(testStream);
    }

    @Test
    public void test1Bit() throws IOException {
        testImageType(BufferedImage.TYPE_BYTE_BINARY, "out1bit.bmp");
    }

    @Test
    public void test4Bit() throws IOException {
        testImage(create4BitImage(), "out4bit.bmp", null);
    }

    //@Test
    public void test4BitRLE() throws IOException {
        testImage(create4BitImage(), "out4bitRLE.bmp", "BI_RLE4");
    }

    @Test
    public void test8Bit() throws IOException {
        testImageType(BufferedImage.TYPE_BYTE_INDEXED, "out8bit.bmp");
    }

    @Test
    public void test8BitRLE() throws IOException {
        testImageType(BufferedImage.TYPE_BYTE_INDEXED, "out8bitRLE.bmp", "BI_RLE8");
    }

    @Test
    public void test16Bit() throws IOException {
        testImageType(BufferedImage.TYPE_USHORT_555_RGB, "out16bit.bmp");
    }

    @Test
    public void test24Bit() throws IOException {
        testImageType(BufferedImage.TYPE_INT_RGB, "out24bit.bmp");
    }

    @Test
    public void testBitfields() throws IOException {
        testImageType(BufferedImage.TYPE_USHORT_555_RGB, "out16bit555.bmp", "BI_BITFIELDS");
        testImageType(BufferedImage.TYPE_USHORT_565_RGB, "out16bit565.bmp", "BI_BITFIELDS");
    }

    @Test
    public void testMasks() {
        assertTrue(checkDisjointMasks(1, 2, 4));
        assertTrue(checkDisjointMasks(0x00F, 0x0F0, 0xF00));
        assertFalse(checkDisjointMasks(1, 2, 5));
        assertFalse(checkDisjointMasks(2, 1, 6));

        assertTrue(isPow2Minus1(1));
        assertTrue(isPow2Minus1(3));
        assertTrue(isPow2Minus1(7));
        assertFalse(isPow2Minus1(2));
        assertFalse(isPow2Minus1(11));
    }
}
