/*
 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.awt.X11;

import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import sun.awt.X11GraphicsConfig;
import sun.awt.X11GraphicsDevice;
import sun.awt.X11GraphicsEnvironment;

import sun.java2d.pipe.Region;

/*
 * This class is a collection of utility methods that operate
 * with native windows.
 */
public final class XlibUtil
{
    /**
     * The constructor is made private to eliminate any
     * instances of this class
    */
    private XlibUtil()
    {
    }

    /**
     * Xinerama-aware version of XlibWrapper.RootWindow method.
     */
    public static long getRootWindow(int screenNumber)
    {
        XToolkit.awtLock();
        try
        {
            X11GraphicsEnvironment x11ge = (X11GraphicsEnvironment)
                GraphicsEnvironment.getLocalGraphicsEnvironment();
            if (x11ge.runningXinerama())
            {
                // all the Xinerama windows share the same root window
                return XlibWrapper.RootWindow(XToolkit.getDisplay(), 0);
            }
            else
            {
                return XlibWrapper.RootWindow(XToolkit.getDisplay(), screenNumber);
            }
        }
        finally
        {
            XToolkit.awtUnlock();
        }
    }

    /**
     * Checks if the given window is a root window for the given screen
     */
    static boolean isRoot(long rootCandidate, long screenNumber)
    {
        long root;

        XToolkit.awtLock();
        try
        {
            root = XlibWrapper.RootWindow(XToolkit.getDisplay(),
                                          screenNumber);
        }
        finally
        {
            XToolkit.awtUnlock();
        }

        return root == rootCandidate;
    }

    /**
     * Returns the bounds of the given window, in absolute coordinates
     */
    static Rectangle getWindowGeometry(long window, int scale)
    {
        XToolkit.awtLock();
        try
        {
            int res = XlibWrapper.XGetGeometry(XToolkit.getDisplay(),
                                               window,
                                               XlibWrapper.larg1, // root_return
                                               XlibWrapper.larg2, // x_return
                                               XlibWrapper.larg3, // y_return
                                               XlibWrapper.larg4, // width_return
                                               XlibWrapper.larg5, // height_return
                                               XlibWrapper.larg6, // border_width_return
                                               XlibWrapper.larg7); // depth_return
            if (res == 0)
            {
                return null;
            }

            int x = Native.getInt(XlibWrapper.larg2);
            int y = Native.getInt(XlibWrapper.larg3);
            long width = Native.getUInt(XlibWrapper.larg4);
            long height = Native.getUInt(XlibWrapper.larg5);

            return new Rectangle(scaleDown(x, scale), scaleDown(y, scale),
                                 scaleDown((int) width, scale),
                                 scaleDown((int) height, scale));
        }
        finally
        {
            XToolkit.awtUnlock();
        }
    }

    /**
     * Translates the given point from one window to another. Returns
     * null if the translation is failed
     */
    static Point translateCoordinates(long src, long dst, Point p, int scale)
    {
        Point translated = null;

        XToolkit.awtLock();
        try
        {
            XTranslateCoordinates xtc =
                new XTranslateCoordinates(src, dst, p.x * scale, p.y * scale);
            try
            {
                int status = xtc.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
                if ((status != 0) &&
                    ((XErrorHandlerUtil.saved_error == null) ||
                    (XErrorHandlerUtil.saved_error.get_error_code() == XConstants.Success)))
                {
                    translated = new Point(scaleDown(xtc.get_dest_x(), scale),
                                           scaleDown(xtc.get_dest_y(), scale));
                }
            }
            finally
            {
                xtc.dispose();
            }
        }
        finally
        {
            XToolkit.awtUnlock();
        }

        return translated;
    }

    /**
     * Translates the given rectangle from one window to another.
     * Returns null if the translation is failed
     */
    static Rectangle translateCoordinates(long src, long dst, Rectangle r,
                                          int scale)
    {
        Point translatedLoc = translateCoordinates(src, dst, r.getLocation(),
                                                   scale);

        if (translatedLoc == null)
        {
            return null;
        }
        else
        {
            return new Rectangle(translatedLoc, r.getSize());
        }
    }

    /**
     * Returns the parent for the given window
     */
    static long getParentWindow(long window)
    {
        XToolkit.awtLock();
        try
        {
            XBaseWindow bw = XToolkit.windowToXWindow(window);
            if (bw != null)
            {
                XBaseWindow pbw = bw.getParentWindow();
                if (pbw != null)
                {
                    return pbw.getWindow();
                }
            }

            XQueryTree qt = new XQueryTree(window);
            try
            {
                if (qt.execute() == 0)
                {
                    return 0;
                }
                else
                {
                    return qt.get_parent();
                }
            }
            finally
            {
                qt.dispose();
            }
        }
        finally
        {
            XToolkit.awtUnlock();
        }
    }

    /**
     * Returns all the children for the given window
     */
    static Set<Long> getChildWindows(long window)
    {
        XToolkit.awtLock();
        try
        {
            XBaseWindow bw = XToolkit.windowToXWindow(window);
            if (bw != null)
            {
                return bw.getChildren();
            }

            XQueryTree xqt = new XQueryTree(window);
            try
            {
                int status = xqt.execute();
                if (status == 0)
                {
                    return Collections.emptySet();
                }

                long children = xqt.get_children();

                if (children == 0)
                {
                    return Collections.emptySet();
                }

                int childrenCount = xqt.get_nchildren();

                Set<Long> childrenSet = new HashSet<Long>(childrenCount);
                for (int i = 0; i < childrenCount; i++)
                {
                    childrenSet.add(Native.getWindow(children, i));
                }

                return childrenSet;
            }
            finally
            {
                xqt.dispose();
            }
        }
        finally
        {
            XToolkit.awtUnlock();
        }
    }

    /**
     * Checks if the given window is a Java window and is an
     * instance of XWindowPeer
     */
    static boolean isXAWTToplevelWindow(long window)
    {
        return XToolkit.windowToXWindow(window) instanceof XWindowPeer;
    }

    /**
     * NOTICE: Right now returns only decorated top-levels (not Window)
     */
    static boolean isToplevelWindow(long window)
    {
        if (XToolkit.windowToXWindow(window) instanceof XDecoratedPeer)
        {
            return true;
        }

        XToolkit.awtLock();
        try
        {
            WindowPropertyGetter wpg =
                new WindowPropertyGetter(window, XWM.XA_WM_STATE, 0, 1, false,
                                         XWM.XA_WM_STATE);
            try
            {
                wpg.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
                if (wpg.getActualType() == XWM.XA_WM_STATE.getAtom())
                {
                    return true;
                }
            }
            finally
            {
                wpg.dispose();
            }

            return false;
        }
        finally
        {
            XToolkit.awtUnlock();
        }
    }

    /**
     * The same as isToplevelWindow(window), but doesn't treat
     * XEmbeddedFramePeer as toplevel.
     */
    static boolean isTrueToplevelWindow(long window)
    {
        if (XToolkit.windowToXWindow(window) instanceof XEmbeddedFramePeer)
        {
            return false;
        }

        return isToplevelWindow(window);
    }

    static int getWindowMapState(long window)
    {
        XToolkit.awtLock();
        XWindowAttributes wattr = new XWindowAttributes();
        try
        {
            XErrorHandlerUtil.WITH_XERROR_HANDLER(XErrorHandler.IgnoreBadWindowHandler.getInstance());
            int status = XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(),
                                                          window, wattr.pData);
            XErrorHandlerUtil.RESTORE_XERROR_HANDLER();
            if ((status != 0) &&
                ((XErrorHandlerUtil.saved_error == null) ||
                (XErrorHandlerUtil.saved_error.get_error_code() == XConstants.Success)))
            {
                return wattr.get_map_state();
            }
        }
        finally
        {
            wattr.dispose();
            XToolkit.awtUnlock();
        }

        return XConstants.IsUnmapped;
    }

    /**
     * XSHAPE extension support.
     */

    // The variable is declared static as the XSHAPE extension cannot
    // be disabled at run-time, and thus is available all the time
    // once the check is passed.
    static Boolean isShapingSupported = null;

    /**
     *  Returns whether the XSHAPE extension available
     *  @since 1.7
     */
    static synchronized boolean isShapingSupported() {

        if (isShapingSupported == null) {
            XToolkit.awtLock();
            try {
                isShapingSupported =
                    XlibWrapper.XShapeQueryExtension(
                            XToolkit.getDisplay(),
                            XlibWrapper.larg1,
                            XlibWrapper.larg2);
            } finally {
                XToolkit.awtUnlock();
            }
        }

        return isShapingSupported.booleanValue();
    }

    static int getButtonMask(int button) {
        // Button indices start with 1. The first bit in the button mask is the 8th.
        // The state mask does not support button indices > 5, so we need to
        // cut there.
        if (button <= 0 || button > XConstants.MAX_BUTTONS) {
            return 0;
        } else {
            return 1 << (7 + button);
        }
    }

    static int scaleDown(int x, int scale) {
        return Region.clipRound(x / (double)scale);
    }
}
