/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.util;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.image.MemoryImageSource;

import java.util.Arrays;

import javax.swing.ImageIcon;

/** Some static utility methods.
 * @since 2.0
 */
public final class JaxoUtils {
    /** The system-dependent line separator. */
    public static final String LINE_SEPARATOR;

    private static final Dimension DEFAULT_IMAGE_SIZE = new Dimension(40, 15);

    static {
        LINE_SEPARATOR = System.getProperty("line.separator");
    }

    /**
     * Empty private constructor to prevent the class from being
     * explicitly instantiated.
     */
    private JaxoUtils() {
        // empty on purpose
    }

    /**
     * Compares the two floats within a certain accuracy.
     *
     * @param a First float to compare.
     * @param b Second float to compare.
     * @param epsilon the desired accuracy. Has to be non-negative.
     * @return True if the floats are equal to within the given accuracy.
     */
    public static boolean equal(final float a, final float b, final float epsilon) {
        float eps = epsilon;

        if (zero(eps)) {
            eps = 0.00001f;
        } else if (eps < 0.f) {
            throw new IllegalArgumentException("Negative accuracy!");
        }

        boolean equal = false;

        if (Math.abs(a) < eps) {
            equal = (Math.abs(b) < eps);
        } else {
            equal = (Math.abs((a - b) / a) < eps);
        }

        return equal;
    }

    /** Compares the two floats.
     * @param a First float to compare.
     * @param b Second float to compare.
     * @return True if the floats are equal to within 0.1%.
     */
    public static boolean equal(final float a, final float b) {
        return equal(a, b, 0.001f);
    }

    /** Checks if the given double is zero.
     * @param a The double to check.
     * @return True if the double is smaller in magnitude than 0.001d.
     */
    public static boolean zero(final double a) {
        return (Math.abs(a) < 0.001d);
    }

    /**
     * Draw the part (x,y,width,height) of the image 'm' on 'g', at the same position (x,y).
     * @param m The image.
     * @param x The x coordinate.
     * @param y The y coordinate.
     * @param width The width.
     * @param height The height.
     * @param g The graphics context.
     */
    public static void drawImageArea(final Image m, final int x, final int y, final int width,
        final int height, final Graphics g) {
        drawImageArea(m, x, y, width, height, g, x, y);
    }

    /**
     * Draw the part (x,y,width,height) of the image 'm' on 'g' at (gx,gy).
     * @param m The image.
     * @param x The x coordinate.
     * @param y The y coordinate.
     * @param width The width.
     * @param height The height.
     * @param g The graphics context.
     * @param gx The x coordinate of the new origin.
     * @param gy The y coordinate of the new origin.
     */
    public static void drawImageArea(final Image m, final int x, final int y, final int width,
        final int height, final Graphics g, final int gx, final int gy) {
        final int x2 = x + width;
        final int y2 = y + height;
        final int gx2 = gx + width;
        final int gy2 = gy + height;

        g.drawImage(m, gx, gy, gx2, gy2, x, y, x2, y2, null);

    }

    /**
     * ImageIcon from /resources/icons/ icon resource.
     * @param resourceName The name of the resource.
     * @return An ImageIcon.
     */
    public static ImageIcon newImageIcon(final String resourceName) {
        return new ImageIcon(Thread.currentThread().getContextClassLoader().getResource("resources/icons/"
                + resourceName));
    }

    /**
     * Image from /resources/icons/ icon resource.
     * @param resourceName The name of the resource file.
     * @return An Image.
     */
    public static Image newImage(final String resourceName) {
        return Toolkit.getDefaultToolkit().getImage(Thread.currentThread().getContextClassLoader()
                                                                   .getResource("resources/icons/"
                + resourceName));
    }

    /**
     * Returns an image with a box of 40x15 pixels size,
     * of the given color.
     * @param iconColor The color of theimage.
     * @return An Image.
     */
    public static Image getChooserImage(final Color iconColor) {
        return getChooserImage(iconColor, DEFAULT_IMAGE_SIZE);
    }

    /**
     * Returns an image with a box of the given pixels size,
     * of the given color.
     * @param iconColor The color of theimage.
     * @param size The size ofthe image.
     * @return  An Image.
     */
    public static Image getChooserImage(final Color iconColor, final Dimension size) {
        final int[] pixels = new int[size.width * size.height];

        Arrays.fill(pixels, iconColor.getRGB());

        final MemoryImageSource source =
            new MemoryImageSource(size.width, size.height, pixels, 0,
                size.width);

        return Toolkit.getDefaultToolkit().createImage(source);
    }

    /**
     * Returns an image icon with a box of 40x15 pixels size,
     * of the given color. Used for the color button in the edit panels.
     * @param iconColor The color of theimage.
     * @return  An ImageIcon.
     */
    public static ImageIcon getChooserImageIcon(final Color iconColor) {
        return new ImageIcon(getChooserImage(iconColor));
    }

    /**
     * Returns an image icon with a box of the given pixels size,
     * of the given color.
     * @param iconColor The color of theimage.
     * @param size The size ofthe image.
     * @return An ImageIcon.
     */
    public static ImageIcon getChooserImageIcon(final Color iconColor, final Dimension size) {
        return new ImageIcon(getChooserImage(iconColor, size));
    }

    /** Check for button 1 mouse events.
     * @param e The MouseEvent.
     * @return True if e corresponds to a button 1 mouse event.
     */
    public static boolean isButton1(final MouseEvent e) {
        return ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0);
    }

    /** Check for button 2 mouse events.
     * @param e The MouseEvent.
     * @return True if e corresponds to a button 2 mouse event.
     */
    public static boolean isButton2(final MouseEvent e) {
        return ((e.getModifiers() & InputEvent.BUTTON2_MASK) != 0);
    }

    /** Check for button 3 mouse events.
     * @param e The MouseEvent.
     * @return True if e corresponds to a button 3 mouse event.
     */
    public static boolean isButton3(final MouseEvent e) {
        return ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0);
    }

    /** Check for double click mouse events.
     * @param e The MouseEvent.
     * @return True if e corresponds to a double click mouse event.
     */
    public static boolean isDoubleClick(final MouseEvent e) {
        return (e.getClickCount() == 2);
    }

    /** This is intended to be equivalent to <code>Arrays.toString(a)</code>
     * but also compatible with JDK 1.4.
     * This concatenates the results of calling String.valueOf() on each element
     * of the array, so this won't work well for multi-dimensional arrays.
     * @param array An array.
     * @return A string.
     */
    public static String toString(final Object[] array) {
        if (array == null) {
            return "null";
        }

        final int max = array.length - 1;
        final StringBuffer sb = new StringBuffer("[");
        for (int j = 0; j <= max; j += 1) {
            sb.append(String.valueOf(array[j]));
            if (j < max) {
                sb.append(','); // Arrays.toString() appends ", "
            }
        }
        sb.append(']');

        return sb.toString();
    }

    /**
     * Check if two arrays are equal.
     * Two arrays are equal if they contain the same elements in the same order,
     * or if they are both null.
     *
     * @param expected one array to be tested for equality.
     * @param actual the other array to be tested for equality.
     *
     * @return True if the two arrays are equal.
     *
     * @see java.util.Arrays#equals(java.lang.Object[],java.lang.Object[])
     */
    public static boolean equals(final Object[] expected, final Object[] actual) {
        return Arrays.equals(expected, actual);
    }

    /**
     * Check if two arrays are equivalent.
     * Two arrays are equivalent if they contain the same number of elements,
     * and every element of one array is also contained in the other array.
     * In other words, two arrays are equivalent if they contain the same
     * elements irrespective of order.
     *
     * <p>
     * This implementation uses
     * {@link java.util.Arrays#sort(java.lang.Object[])} to bring the elements
     * of each array into natural order, before they are checked for equality.
     * Therefore, the elements of each array must implement the
     * Comparable interface and they must be mutually comparable.
     * </p>
     *
     * @param expected one array to be tested for equivalence.
     * @param actual the other array to be tested for equivalence.
     *
     * @return True if the two arrays contain the same elements.
     *
     * @see java.util.Arrays#equals(java.lang.Object[],java.lang.Object[])
     * @see java.util.Arrays#sort(java.lang.Object[])
     */
    public static boolean equivalent(final Object[] expected, final Object[] actual) {
        Arrays.sort(expected);
        Arrays.sort(actual);

        return equals(expected, actual);
    }
}
