/* Copyright (C) 2005-2011 Fabio Riccardi */

package com.lightcrafts.ui.toolkit;

import com.lightcrafts.mediax.jai.BorderExtender;
import com.lightcrafts.mediax.jai.Interpolation;
import com.lightcrafts.mediax.jai.JAI;
import com.lightcrafts.mediax.jai.RenderedOp;
import com.lightcrafts.mediax.jai.operator.AffineDescriptor;
import com.lightcrafts.mediax.jai.operator.BandMergeDescriptor;
import com.lightcrafts.mediax.jai.operator.BandSelectDescriptor;
import com.lightcrafts.mediax.jai.operator.InvertDescriptor;
import com.lightcrafts.ui.LightZoneSkin;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.net.URL;

/**
 * Construct icons from resources in a systematic way where scaling can be
 * enforced and errors can be handled.
 */
public class IconFactory {

    public static int StandardSize = 18;

    /**
     * The resource assumption is that icons are accessible through the
     * IconFactory's classloader, namespaced by the class of a corresonding
     * object (like a button that uses the icon) with an extra "resources"
     * name at the end.
     */
    public static Icon createIcon(Class clazz, String name) {
        return createIcon(clazz, name, StandardSize);
    }

    public static Icon createIcon(Class clazz, String name, int size) {
        try {
            URL url = clazz.getResource("resources/" + name);
            BufferedImage image = ImageIO.read(url);
            image = getScaledImage(image, size);
            return new ImageIcon(image);
        }
        catch (Throwable t) {   // IOException, null URL
            t.printStackTrace();
            throw new RuntimeException(
                "Couldn't read icon resource \"" + name + "\" " +
                "for class " + clazz.getName()
            );
        }
    }

    public static Icon createInvertedIcon(Class clazz, String name) {
        return createInvertedIcon(clazz, name, StandardSize);
    }

    public static Icon createInvertedIcon(Class clazz, String name, int size) {
        Icon icon = createIcon(clazz, name, size);
        if (icon != null) {
            icon = invertIcon(icon);
        }
        return icon;
    }

    public static Icon invertIcon(Icon icon) {
        if (icon == null)
            return null;
        
        int width = icon.getIconWidth();
        int height = icon.getIconHeight();
        BufferedImage image =
            new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        Graphics g = image.createGraphics();
        icon.paintIcon(null, g, 0, 0);
        image = invertImageWithAlpha(image);
        return new ImageIcon(image);
    }

    // Invert the color channels of the given BufferedImage but not the
    // alpha channel.  This works by assuming that band 3 is the alpha,
    // which appears to be true for BufferedImages read from resources by
    // ImageIO.read() and constructed above in invertIcon().
    private static BufferedImage invertImageWithAlpha(BufferedImage image) {
        RenderedOp alpha =
            BandSelectDescriptor.create(image, new int[] { 3 }, null);
        RenderedOp colors =
            BandSelectDescriptor.create(image, new int[] { 0, 1, 2 }, null);
        colors = InvertDescriptor.create(colors, null);
        RenderedOp op = BandMergeDescriptor.create(colors, alpha, null);
        return op.getAsBufferedImage();
    }
    
    public static BufferedImage getScaledImage(BufferedImage image, int limit) {
        int maxSize = Math.max(image.getWidth(), image.getHeight());
        if (maxSize > limit) {
            if (true) {
                double scale = limit / (float) maxSize;
                AffineTransform transform =
                        AffineTransform.getScaleInstance(scale, scale);
                RenderingHints extenderHint = new RenderingHints(
                        JAI.KEY_BORDER_EXTENDER,
                        BorderExtender.createInstance(BorderExtender.BORDER_COPY)
                );
                Interpolation interp = Interpolation.getInstance(
                        Interpolation.INTERP_BILINEAR
                );
                image = AffineDescriptor.create(
                        image, transform, interp, null, extenderHint
                ).getAsBufferedImage();
            } else {
                BufferedImage rescaled = new BufferedImage(limit, limit, BufferedImage.TYPE_4BYTE_ABGR);
                Graphics2D g = (Graphics2D) rescaled.getGraphics();
                double scale = limit / (float) maxSize;
                AffineTransform transform =
                        AffineTransform.getScaleInstance(scale, scale);
                // g.setTransform(transform);
                g.drawRenderedImage(image, transform);
                g.dispose();
                image = rescaled;
            }
        }
        return image;
    }
}
