/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.jface.util;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Widget;

/**
 * <p>
 * A static class providing utility methods to all of JFace.
 * </p>
 *
 * @since 3.1
 */
public final class Util {

	/**
	 * An unmodifiable, empty, sorted set. This value is guaranteed to never
	 * change and never be <code>null</code>.
	 */
	public static final SortedSet<?> EMPTY_SORTED_SET = Collections
			.unmodifiableSortedSet(new TreeSet<>());

	/**
	 * A common zero-length string. It avoids needing write <code>NON-NLS</code>
	 * next to code fragments. It's also a bit clearer to read.
	 */
	public static final String ZERO_LENGTH_STRING = ""; //$NON-NLS-1$

	/**
	 * Verifies that the given object is an instance of the given class.
	 *
	 * @param object
	 *            The object to check; may be <code>null</code>.
	 * @param c
	 *            The class which the object should be; must not be
	 *            <code>null</code>.
	 */
	public static void assertInstance(final Object object, final Class<?> c) {
		assertInstance(object, c, false);
	}

	/**
	 * Verifies the given object is an instance of the given class. It is
	 * possible to specify whether the object is permitted to be
	 * <code>null</code>.
	 *
	 * @param object
	 *            The object to check; may be <code>null</code>.
	 * @param c
	 *            The class which the object should be; must not be
	 *            <code>null</code>.
	 * @param allowNull
	 *            Whether the object is allowed to be <code>null</code>.
	 */
	private static void assertInstance(final Object object,
			final Class<?> c, final boolean allowNull) {
		if (object == null && allowNull) {
			return;
		}

		if (object == null || c == null) {
			throw new NullPointerException();
		} else if (!c.isInstance(object)) {
			throw new IllegalArgumentException();
		}
	}

	/**
	 * Compares two boolean values. <code>false</code> is considered to be
	 * "less than" <code>true</code>.
	 *
	 * @param left
	 *            The left value to compare
	 * @param right
	 *            The right value to compare
	 * @return <code>-1</code> if the left is <code>false</code> and the
	 *         right is <code>true</code>. <code>1</code> if the opposite
	 *         is true. If they are equal, then it returns <code>0</code>.
	 */
	public static int compare(final boolean left, final boolean right) {
		return left == false ? (right == true ? -1 : 0) : 1;
	}

	/**
	 * Compares two integer values.
	 *
	 * @param left
	 *            The left value to compare
	 * @param right
	 *            The right value to compare
	 * @return <code>left - right</code>
	 */
	public static int compare(final int left, final int right) {
		return left - right;
	}

	/**
	 * Compares to comparable objects -- defending against <code>null</code>.
	 *
	 * @param left
	 *            The left object to compare; may be <code>null</code>.
	 * @param right
	 *            The right object to compare; may be <code>null</code>.
	 * @return The result of the comparison. <code>null</code> is considered
	 *         to be the least possible value.
	 */
	public static <T extends Comparable<? super T>> int compare(final T left,
			final T right) {
		if (left == null && right == null) {
			return 0;
		} else if (left == null) {
			return -1;
		} else if (right == null) {
			return 1;
		} else {
			return left.compareTo(right);
		}
	}

	/**
	 * Compares two arrays of comparable objects -- accounting for
	 * <code>null</code>.
	 *
	 * @param left
	 *            The left array to be compared; may be <code>null</code>.
	 * @param right
	 *            The right array to be compared; may be <code>null</code>.
	 * @return The result of the comparison. <code>null</code> is considered
	 *         to be the least possible value. A shorter array is considered
	 *         less than a longer array.
	 */
	public static <T extends Comparable<? super T>> int compare(final T[] left,
			final T[] right) {
		if (left == null && right == null) {
			return 0;
		} else if (left == null) {
			return -1;
		} else if (right == null) {
			return 1;
		} else {
			int l = left.length;
			int r = right.length;

			if (l != r) {
				return l - r;
			}

			for (int i = 0; i < l; i++) {
				int compareTo = compare(left[i], right[i]);

				if (compareTo != 0) {
					return compareTo;
				}
			}

			return 0;
		}
	}

	/**
	 * Compares two lists -- account for <code>null</code>. The lists must
	 * contain comparable objects.
	 *
	 * @param left
	 *            The left list to compare; may be <code>null</code>. This
	 *            list must only contain instances of <code>Comparable</code>.
	 * @param right
	 *            The right list to compare; may be <code>null</code>. This
	 *            list must only contain instances of <code>Comparable</code>.
	 * @return The result of the comparison. <code>null</code> is considered
	 *         to be the least possible value. A shorter list is considered less
	 *         than a longer list.
	 */
	public static <T extends Comparable<? super T>> int compare(final List<T> left, final List<T> right) {
		if (left == null && right == null) {
			return 0;
		} else if (left == null) {
			return -1;
		} else if (right == null) {
			return 1;
		} else {
			int l = left.size();
			int r = right.size();

			if (l != r) {
				return l - r;
			}

			for (int i = 0; i < l; i++) {
				int compareTo = compare(left.get(i), right.get(i));

				if (compareTo != 0) {
					return compareTo;
				}
			}

			return 0;
		}
	}

	/**
	 * Tests whether the first array ends with the second array.
	 *
	 * @param left
	 *            The array to check (larger); may be <code>null</code>.
	 * @param right
	 *            The array that should be a subsequence (smaller); may be
	 *            <code>null</code>.
	 * @param equals
	 *            Whether the two array are allowed to be equal.
	 * @return <code>true</code> if the second array is a subsequence of the
	 *         array list, and they share end elements.
	 */
	public static boolean endsWith(final Object[] left,
			final Object[] right, final boolean equals) {
		if (left == null || right == null) {
			return false;
		}

		int l = left.length;
		int r = right.length;

		if (r > l || !equals && r == l) {
			return false;
		}

		for (int i = 0; i < r; i++) {
			if (!Objects.equals(left[l - i - 1], right[r - i - 1])) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Checks whether the two objects are <code>null</code> -- allowing for
	 * <code>null</code>.
	 *
	 * @param left
	 *            The left object to compare; may be <code>null</code>.
	 * @param right
	 *            The right object to compare; may be <code>null</code>.
	 * @return <code>true</code> if the two objects are equivalent;
	 *         <code>false</code> otherwise.
	 * @deprecated Use {@link Objects#equals(Object, Object)}
	 */
	@Deprecated
	public static boolean equals(final Object left, final Object right) {
		return Objects.equals(left, right);
	}

	/**
	 * Tests whether two arrays of objects are equal to each other. The arrays must
	 * not be <code>null</code>, but their elements may be <code>null</code>.
	 *
	 * @param leftArray
	 *            The left array to compare; may be <code>null</code>, and may be
	 *            empty and may contain <code>null</code> elements.
	 * @param rightArray
	 *            The right array to compare; may be <code>null</code>, and may be
	 *            empty and may contain <code>null</code> elements.
	 * @return <code>true</code> if the arrays are equal length and the elements at
	 *         the same position are equal; <code>false</code> otherwise.
	 * @deprecated Use {@link Arrays#equals(Object[], Object[])}
	 */
	@Deprecated
	public static boolean equals(final Object[] leftArray,
			final Object[] rightArray) {
		return Arrays.equals(leftArray, rightArray);
	}

	/**
	 * Provides a hash code based on the given integer value.
	 *
	 * @param i The integer value
	 * @return <code>i</code>
	 * @deprecated return directly value, or use {@link Integer#hashCode(int)}
	 */
	@Deprecated
	public static int hashCode(final int i) {
		return i;
	}

	/**
	 * Provides a hash code for the object -- defending against <code>null</code>.
	 *
	 * @param object The object for which a hash code is required.
	 * @return <code>object.hashCode</code> or <code>0</code> if <code>object</code>
	 *         if <code>null</code>.
	 * @deprecated use {@link Objects#hashCode(Object)}
	 */
	@Deprecated
	public static int hashCode(final Object object) {
		return object != null ? object.hashCode() : 0;
	}

	/**
	 * Computes the hash code for an array of objects, but with defense against
	 * <code>null</code>.
	 *
	 * @param objects The array of objects for which a hash code is needed; may be
	 *                <code>null</code>.
	 * @return The hash code for <code>objects</code>; or <code>0</code> if
	 *         <code>objects</code> is <code>null</code>.
	 * @deprecated use {@link Arrays#hashCode(Object[])}
	 */
	@Deprecated
	public static int hashCode(final Object[] objects) {
		if (objects == null) {
			return 0;
		}

		int hashCode = 89;
		for (final Object object : objects) {
			if (object != null) {
				hashCode = hashCode * 31 + object.hashCode();
			}
		}

		return hashCode;
	}

	/**
	 * Checks whether the second array is a subsequence of the first array, and
	 * that they share common starting elements.
	 *
	 * @param left
	 *            The first array to compare (large); may be <code>null</code>.
	 * @param right
	 *            The second array to compare (small); may be <code>null</code>.
	 * @param equals
	 *            Whether it is allowed for the two arrays to be equivalent.
	 * @return <code>true</code> if the first arrays starts with the second
	 *         list; <code>false</code> otherwise.
	 */
	public static boolean startsWith(final Object[] left,
			final Object[] right, final boolean equals) {
		if (left == null || right == null) {
			return false;
		}

		int l = left.length;
		int r = right.length;

		if (r > l || !equals && r == l) {
			return false;
		}

		for (int i = 0; i < r; i++) {
			if (!Objects.equals(left[i], right[i])) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Converts an array into a string representation that is suitable for
	 * debugging.
	 *
	 * @param array
	 *            The array to convert; may be <code>null</code>.
	 * @return The string representation of the array; never <code>null</code>.
	 */
	public static String toString(final Object[] array) {
		if (array == null) {
			return "null"; //$NON-NLS-1$
		}

		final StringBuilder buffer = new StringBuilder();
		buffer.append('[');

		final int length = array.length;
		for (int i = 0; i < length; i++) {
			if (i != 0) {
				buffer.append(',');
			}
			final Object object = array[i];
			final String element = String.valueOf(object);
			buffer.append(element);
		}
		buffer.append(']');

		return buffer.toString();
	}

	/**
	 * Provides a translation of a particular key from the resource bundle.
	 *
	 * @param resourceBundle
	 *            The key to look up in the resource bundle; should not be
	 *            <code>null</code>.
	 * @param key
	 *            The key to look up in the resource bundle; should not be
	 *            <code>null</code>.
	 * @param defaultString
	 *            The value to return if the resource cannot be found; may be
	 *            <code>null</code>.
	 * @return The value of the translated resource at <code>key</code>. If
	 *         the key cannot be found, then it is simply the
	 *         <code>defaultString</code>.
	 */
	public static String translateString(
			final ResourceBundle resourceBundle, final String key,
			final String defaultString) {
		if (resourceBundle != null && key != null) {
			try {
				final String translatedString = resourceBundle.getString(key);

				if (translatedString != null) {
					return translatedString;
				}
			} catch (MissingResourceException eMissingResource) {
				// Such is life. We'll return the key
			}
		}

		return defaultString;
	}

	/**
	 * Foundation replacement for <code>String#replaceAll(String,
	 * String)</code>, but <strong>without support for regular
	 * expressions</strong>.
	 *
	 * @param src the original string
	 * @param find the string to find
	 * @param replacement the replacement string
	 * @return the new string, with all occurrences of <code>find</code>
	 *         replaced by <code>replacement</code> (not using regular
	 *         expressions)
	 * @since 3.4
	 */
	public static String replaceAll(String src, String find, String replacement) {
		final int len = src.length();
		final int findLen = find.length();

		int idx = src.indexOf(find);
		if (idx < 0) {
			return src;
		}

		StringBuilder buf = new StringBuilder();
		int beginIndex = 0;
		while (idx != -1 && idx < len) {
			buf.append(src.substring(beginIndex, idx));
			buf.append(replacement);

			beginIndex = idx + findLen;
			if (beginIndex < len) {
				idx = src.indexOf(find, beginIndex);
			} else {
				idx = -1;
			}
		}
		if (beginIndex<len) {
			buf.append(src.substring(beginIndex, (idx==-1?len:idx)));
		}
		return buf.toString();
	}

	//
	// Methods for working with the windowing system
	//

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	public static final String WS_WIN32 = "win32";//$NON-NLS-1$

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	@Deprecated
	public static final String WS_MOTIF = "motif";//$NON-NLS-1$

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	public static final String WS_GTK = "gtk";//$NON-NLS-1$

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	@Deprecated
	public static final String WS_PHOTON = "photon";//$NON-NLS-1$

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	@Deprecated
	public static final String WS_CARBON = "carbon";//$NON-NLS-1$

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	public static final String WS_COCOA = "cocoa";//$NON-NLS-1$

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	public static final String WS_WPF = "wpf";//$NON-NLS-1$

	/**
	 * Windowing system constant.
	 * @since 3.5
	 */
	public static final String WS_UNKNOWN = "unknown";//$NON-NLS-1$

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for windows platforms
	 * @since 3.5
	 */
	public static boolean isWindows() {
		final String ws = SWT.getPlatform();
		return WS_WIN32.equals(ws) || WS_WPF.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for mac platforms
	 * @since 3.5
	 */
	public static boolean isMac() {
		final String ws = SWT.getPlatform();
		return WS_CARBON.equals(ws) || WS_COCOA.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for linux platform
	 * @since 3.5
	 */
	public static boolean isLinux() {
		final String ws = SWT.getPlatform();
		return WS_GTK.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for gtk platforms
	 * @since 3.5
	 */
	public static boolean isGtk() {
		final String ws = SWT.getPlatform();
		return WS_GTK.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for motif platforms
	 * @since 3.5
	 */
	@Deprecated
	public static boolean isMotif() {
		final String ws = SWT.getPlatform();
		return WS_MOTIF.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for photon platforms
	 * @since 3.5
	 */
	@Deprecated
	public static boolean isPhoton() {
		final String ws = SWT.getPlatform();
		return WS_PHOTON.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for carbon platforms
	 * @since 3.5
	 */
	@Deprecated
	public static boolean isCarbon() {
		final String ws = SWT.getPlatform();
		return WS_CARBON.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for the cocoa platform.
	 * @since 3.5
	 */
	public static boolean isCocoa() {
		final String ws = SWT.getPlatform();
		return WS_COCOA.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for WPF
	 * @since 3.5
	 */
	public static boolean isWpf() {
		final String ws = SWT.getPlatform();
		return WS_WPF.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return <code>true</code> for win32
	 * @since 3.5
	 */
	public static boolean isWin32() {
		final String ws = SWT.getPlatform();
		return WS_WIN32.equals(ws);
	}

	/**
	 * Common WS query helper method.
	 * @return the SWT windowing platform string.
	 * @see SWT#getPlatform()
	 * @since 3.5
	 */
	public static String getWS() {
		return SWT.getPlatform();
	}

	/**
	 * This class should never be constructed.
	 */
	private Util() {
		// Not allowed.
	}

	/**
	 * Returns the monitor whose client area contains the given point. If no monitor
	 * contains the point, returns the monitor that is closest to the point. If this
	 * is ever made public, it should be moved into a separate utility class.
	 *
	 * @param toSearch point to find (display coordinates)
	 * @param toFind   point to find (display coordinates)
	 * @return the monitor closest to the given point
	 * @since 3.15
	 */
	public static Monitor getClosestMonitor(Display toSearch, Point toFind) {
		int closest = Integer.MAX_VALUE;

		Monitor[] monitors = toSearch.getMonitors();
		Monitor result = monitors[0];

		for (Monitor current : monitors) {
			Rectangle clientArea = current.getClientArea();

			if (clientArea.contains(toFind)) {
				return current;
			}

			int distance = Geometry.distanceSquared(Geometry.centerPoint(clientArea), toFind);
			if (distance < closest) {
				closest = distance;
				result = current;
			}
		}

		return result;
	}

	/**
	 * Helper method to check if a widget is not null and not disposed
	 *
	 * @param widget to be checked
	 * @return true if widget is not disposed or null, false otherwise
	 * @since 3.22
	 */
	public static boolean isValid(Widget widget) {
		return widget != null && !widget.isDisposed();
	}
}
