/*******************************************************************************
 * Copyright (c) 2004, 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.ui.internal.dnd;

import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;

/**
 * Contains static methods for manipulating SWT controls
 *
 * @since 3.0
 */
public class SwtUtil {

    private SwtUtil() {

    }

    /**
     * Returns true if the given control is null or has been disposed
     *
     * @param toTest the control to test
     * @return false if it is safe to invoke methods on the given control
     */
    public static boolean isDisposed(Control toTest) {
        return toTest == null || toTest.isDisposed();
    }

    /**
     * Returns the control that is covering the given control, or null if none.
     *
     * @param toTest control to test
     * @return a control that obscures the test control or null if none
     */
    public static Control controlThatCovers(Control toTest) {
        return controlThatCovers(toTest, DragUtil.getDisplayBounds(toTest));
    }

    private static Control controlThatCovers(Control toTest, Rectangle testRegion) {
        Composite parent = toTest.getParent();

        if (parent == null || toTest instanceof Shell) {
            return null;
        }

		for (Control element : parent.getChildren()) {
            Control control = element;

            if (control == toTest) {
                break;
            }

            if (!control.isVisible()) {
                continue;
            }

            Rectangle nextBounds = DragUtil.getDisplayBounds(control);

            if (nextBounds.intersects(testRegion)) {
                return control;
            }
        }

        return controlThatCovers(parent, testRegion);
    }

    /**
	 * Determines if one control is a child of another.
	 *
	 * @param potentialParent
	 * @param childToTest
	 * @return <code>true</code> if the second argument is a child of the first
	 *         or the same object, <code>false</code> otherwise
	 */
    public static boolean isChild(Control potentialParent, Control childToTest) {
        if (childToTest == null) {
            return false;
        }

        if (childToTest == potentialParent) {
            return true;
        }

        return isChild(potentialParent, childToTest.getParent());
    }

    public static boolean isFocusAncestor(Control potentialParent) {
		if (potentialParent == null)
			return false;
        Control focusControl = Display.getCurrent().getFocusControl();
        if (focusControl == null) {
            return false;
        }
        return isChild(potentialParent, focusControl);
    }

    /**
	 * Finds and returns the most specific SWT control at the given location.
	 * (Note: this does a DFS on the SWT widget hierarchy, which is slow).
	 *
	 * @param displayToSearch
	 * @param locationToFind
	 * @return the most specific SWT control at the given location
	 */
    public static Control findControl(Display displayToSearch,
            Point locationToFind) {
        Shell[] shells = displayToSearch.getShells();

        return findControl(shells, locationToFind);
    }

    /**
     * Searches the given list of controls for a control containing the given point.
     * If the array contains any composites, those composites will be recursively
     * searched to find the most specific child that contains the point.
     *
     * @param toSearch an array of composites
     * @param locationToFind a point (in display coordinates)
     * @return the most specific Control that overlaps the given point, or null if none
     */
    public static Control findControl(Control[] toSearch, Point locationToFind) {
        for (int idx = toSearch.length - 1; idx >= 0; idx--) {
            Control next = toSearch[idx];

            if (!next.isDisposed() && next.isVisible()) {

                Rectangle bounds = DragUtil.getDisplayBounds(next);

                if (bounds.contains(locationToFind)) {
                    if (next instanceof Composite) {
                        Control result = findControl((Composite) next,
                                locationToFind);

                        if (result != null) {
                            return result;
                        }
                    }

                    return next;
                }
            }
        }

        return null;
    }

    public static Control[] getAncestors(Control theControl) {
        return getAncestors(theControl, 1);
    }

    private static Control[] getAncestors(Control theControl, int children) {
        Control[] result;

        if (theControl.getParent() == null) {
            result = new Control[children];
        } else {
            result = getAncestors(theControl.getParent(), children + 1);
        }

        result[result.length - children] = theControl;

        return result;
    }

    public static Control findCommonAncestor(Control control1, Control control2) {
        Control[] control1Ancestors = getAncestors(control1);
        Control[] control2Ancestors = getAncestors(control2);

        Control mostSpecific = null;

        for (int idx = 0; idx < Math.min(control1Ancestors.length, control2Ancestors.length); idx++) {
            Control control1Ancestor = control1Ancestors[idx];
            if (control1Ancestor == control2Ancestors[idx]) {
                mostSpecific = control1Ancestor;
            } else {
                break;
            }
        }

        return mostSpecific;
    }

    /**
	 * Finds the control at the given location.
	 *
	 * @param toSearch
	 * @param locationToFind
	 *            location (in display coordinates)
	 * @return the control at the given location
	 */
    public static Control findControl(Composite toSearch, Point locationToFind) {
        Control[] children = toSearch.getChildren();

        return findControl(children, locationToFind);
    }

    /**
	 *
	 * Returns true iff the given rectangle is located in the client area of any
	 * monitor.
	 *
	 * @param display
	 *            the display
	 * @param someRectangle
	 *            a rectangle in display coordinates (not null)
	 * @return true iff the given point can be seen on any monitor
	 */
    public static boolean intersectsAnyMonitor(Display display,
            Rectangle someRectangle) {
		for (Monitor monitor : display.getMonitors()) {
			if (monitor.getClientArea().intersects(someRectangle)) {
                return true;
            }
        }

        return false;
    }

}
