/*
 * Copyright (C) 2013 University of Dundee & Open Microscopy Environment.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.openmicroscopy.shoola.keywords;

import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.util.NoSuchElementException;

import javax.swing.JPanel;

import org.robotframework.abbot.finder.BasicFinder;
import org.robotframework.abbot.finder.ComponentNotFoundException;
import org.robotframework.abbot.finder.Matcher;
import org.robotframework.abbot.finder.MultipleComponentsFoundException;

import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;

/**
 * Robot Framework SwingLibrary keyword library offering methods for checking thumbnails.
 * @author m.t.b.carroll@dundee.ac.uk
 * @since 4.4.9
 */
public class ThumbnailCheckLibrary
{
    /** Allow Robot Framework to instantiate this library only once. */
    public static final String ROBOT_LIBRARY_SCOPE = "GLOBAL";

    /**
     * An iterator over the integer pixel values of a rendered image,
     * first increasing <em>x</em>, then <em>y</em> when <em>x</em> wraps back to 0.
     * This is written so as to be scalable over arbitrary image sizes
     * and to not cause heap allocations during the iteration.
     * @author m.t.b.carroll@dundee.ac.uk
     * @since 4.4.9
     */
    private static class IteratorIntPixel {
        final Raster raster;
        final int width;
        final int height;
        final int[] pixel = new int[1];
        int x = 0;
        int y = 0;

        /**
         * Create a new pixel iterator for the given image.
         * The image is assumed to be of a type that packs data for each pixel into an <code>int</code>.
         * @param image the image over whose pixels to iterate
         */
        IteratorIntPixel(RenderedImage image) {
            this.raster = image.getData();
            this.width = image.getWidth();
            this.height = image.getHeight();
        }

        /**
         * @return if any pixels remain to be read with {@link #next()}
         */
        boolean hasNext() {
            return y < height;
        }

        /**
         * @return the next pixel
         * @throws NoSuchElementException if no more pixels remain
         */
        int next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            raster.getDataElements(x, y, pixel);
            if (++x == width) {
                x = 0;
                ++y;
            }
            return pixel[0];
        }
    }

    /**
     * Find the thumbnail <code>Component</code> in the AWT hierarchy.
     * @param panelType if the thumbnail should be the whole <code>"image node"</code> or just its <code>"thumbnail"</code> canvas
     * @param imageFilename the name of the image whose thumbnail is to be rasterized
     * @return the AWT <code>Component</code> for the thumbnail
     * @throws MultipleComponentsFoundException if multiple thumbnails are for the given image name
     * @throws ComponentNotFoundException if no thumbnails are for the given image name
     */
    private static Component componentFinder(final String panelType, final String imageFilename)
            throws ComponentNotFoundException, MultipleComponentsFoundException {
        return new BasicFinder().find(new Matcher() {
            private final String soughtName = panelType + " for " + imageFilename;
            public boolean matches(Component component) {
                return component instanceof JPanel && this.soughtName.equals(component.getName());
            }});
    }

    /**
     * Convert the thumbnail for the image of the given filename into rasterized pixel data.
     * Each pixel is represented by an <code>int</code>.
     * @param panelType if the thumbnail should be the whole <code>"image node"</code> or just its <code>"thumbnail"</code> canvas
     * @param imageFilename the name of the image whose thumbnail is to be rasterized
     * @return the image on the thumbnail
     * @throws MultipleComponentsFoundException if multiple thumbnails are for the given image name
     * @throws ComponentNotFoundException if no thumbnails are for the given image name
     */
    private static RenderedImage captureImage(final String panelType, final String imageFilename)
            throws ComponentNotFoundException, MultipleComponentsFoundException {
        final JPanel thumbnail = (JPanel) componentFinder(panelType, imageFilename);
        final int width = thumbnail.getWidth();
        final int height = thumbnail.getHeight();
        final BufferedImage image = new BufferedImage(width, height, StaticFieldLibrary.IMAGE_TYPE);
        final Graphics2D graphics = image.createGraphics();
        if (graphics == null) {
            throw new RuntimeException("thumbnail is not displayable");
        }
        thumbnail.paint(graphics);
        graphics.dispose();
        return image;
    }

    /**
     * <table>
     *   <td>Get Thumbnail Border Color</td>
     *   <td>name of image whose thumbnail is queried</td>
     * </table>
     * @param imageFilename the name of the image
     * @return the color of the thumbnail's corner pixel
     * @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
     * @throws ComponentNotFoundException if no thumbnails exist for the given name
     */
    public String getThumbnailBorderColor(String imageFilename)
            throws ComponentNotFoundException, MultipleComponentsFoundException {
        final RenderedImage image = captureImage("image node", imageFilename);
        final IteratorIntPixel pixels = new IteratorIntPixel(image);
        if (!pixels.hasNext()) {
            throw new RuntimeException("image node has no pixels");
        }
        return Integer.toHexString(pixels.next());
    }

    /**
     * <table>
     *   <td>Is Thumbnail Monochromatic</td>
     *   <td>name of image whose thumbnail is queried</td>
     * </table>
     * @param imageFilename the name of the image
     * @return if the image's thumbnail canvas is solidly one color
     * @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
     * @throws ComponentNotFoundException if no thumbnails exist for the given name
     */
    public boolean isThumbnailMonochromatic(String imageFilename)
            throws ComponentNotFoundException, MultipleComponentsFoundException {
        final RenderedImage image = captureImage("thumbnail", imageFilename);
        final IteratorIntPixel pixels = new IteratorIntPixel(image);
        if (!pixels.hasNext()) {
            throw new RuntimeException("thumbnail image has no pixels");
        }
        final int oneColor = pixels.next();
        while (pixels.hasNext()) {
            if (pixels.next() != oneColor) {
                return false;
            }
        }
        return true;
    }

    /**
     * <table>
     *   <td>Get Thumbnail Hash</td>
     *   <td>name of image whose thumbnail is queried</td>
     * </table>
     * @param imageFilename the name of the image
     * @return the hash of the thumbnail canvas image
     * @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
     * @throws ComponentNotFoundException if no thumbnails exist for the given name
     */
    public String getThumbnailHash(String imageFilename)
            throws ComponentNotFoundException, MultipleComponentsFoundException {
        final RenderedImage image = captureImage("thumbnail", imageFilename);
        final IteratorIntPixel pixels = new IteratorIntPixel(image);
        final Hasher hasher = Hashing.goodFastHash(128).newHasher();
        while (pixels.hasNext()) {
            hasher.putInt(pixels.next());
        }
        return hasher.hash().toString();
    }

    /**
     * <table>
     *   <td>Get Name Of Thumbnail For Image</td>
     *   <td>name of image whose thumbnail is queried</td>
     * </table>
     * @param imageFilename the name of the image
     * @return the return value of the corresponding <code>ThumbnailCanvas.getName()</code>
     * @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
     * @throws ComponentNotFoundException if no thumbnails exist for the given name
     */
    public String getNameOfThumbnailForImage(final String imageFilename)
            throws ComponentNotFoundException, MultipleComponentsFoundException {
        return componentFinder("thumbnail", imageFilename).getName();
    }
}
