/*
 * Copyright (c) 2003, 2010, 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.Point;

import java.awt.dnd.DnDConstants;

import java.awt.event.MouseEvent;

import java.io.IOException;

import java.util.logging.*;

import sun.misc.Unsafe;

/**
 * XDropTargetProtocol implementation for XDnD protocol.
 *
 * @since 1.5
 */
class XDnDDropTargetProtocol extends XDropTargetProtocol {
    private static final Logger logger =
        Logger.getLogger("sun.awt.X11.xembed.xdnd.XDnDDropTargetProtocol");

    private static final Unsafe unsafe = XlibWrapper.unsafe;

    private long sourceWindow = 0;
    private long sourceWindowMask = 0;
    private int sourceProtocolVersion = 0;
    private int sourceActions = DnDConstants.ACTION_NONE;
    private long[] sourceFormats = null;
    private boolean trackSourceActions = false;
    private int userAction = DnDConstants.ACTION_NONE;
    private int sourceX = 0;
    private int sourceY = 0;
    private XWindow targetXWindow = null;

    // XEmbed stuff.
    private long prevCtxt = 0;
    private boolean overXEmbedClient = false;

    protected XDnDDropTargetProtocol(XDropTargetProtocolListener listener) {
        super(listener);
    }

    /**
     * Creates an instance associated with the specified listener.
     *
     * @throws NullPointerException if listener is <code>null</code>.
     */
    static XDropTargetProtocol createInstance(XDropTargetProtocolListener listener) {
        return new XDnDDropTargetProtocol(listener);
    }

    public String getProtocolName() {
        return XDragAndDropProtocols.XDnD;
    }

    public void registerDropTarget(long window) {
        assert XToolkit.isAWTLockHeldByCurrentThread();

        long data = Native.allocateLongArray(1);

        try {
            Native.putLong(data, 0, XDnDConstants.XDND_PROTOCOL_VERSION);

            XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
            XDnDConstants.XA_XdndAware.setAtomData(window, XAtom.XA_ATOM, data, 1);
            XToolkit.RESTORE_XERROR_HANDLER();

            if (XToolkit.saved_error != null &&
                XToolkit.saved_error.get_error_code() != XlibWrapper.Success) {
                throw new XException("Cannot write XdndAware property");
            }
        } finally {
            unsafe.freeMemory(data);
            data = 0;
        }
    }

    public void unregisterDropTarget(long window) {
        assert XToolkit.isAWTLockHeldByCurrentThread();

        XDnDConstants.XA_XdndAware.DeleteProperty(window);
    }

    public void registerEmbedderDropSite(long embedder) {
        assert XToolkit.isAWTLockHeldByCurrentThread();

        boolean overriden = false;
        int version = 0;
        long proxy = 0;
        long newProxy = XDropTargetRegistry.getDnDProxyWindow();
        int status = 0;

        WindowPropertyGetter wpg1 =
            new WindowPropertyGetter(embedder, XDnDConstants.XA_XdndAware, 0, 1,
                                     false, XlibWrapper.AnyPropertyType);

        try {
            status = wpg1.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

            if (status == XlibWrapper.Success &&
                wpg1.getData() != 0 && wpg1.getActualType() == XAtom.XA_ATOM) {

                overriden = true;
                version = (int)Native.getLong(wpg1.getData());
            }
        } finally {
            wpg1.dispose();
        }

        /* XdndProxy is not supported for prior to XDnD version 4 */
        if (overriden && version >= 4) {
            WindowPropertyGetter wpg2 =
                new WindowPropertyGetter(embedder, XDnDConstants.XA_XdndProxy,
                                         0, 1, false, XAtom.XA_WINDOW);

            try {
                status = wpg2.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                if (status == XlibWrapper.Success &&
                    wpg2.getData() != 0 &&
                    wpg2.getActualType() == XAtom.XA_WINDOW) {

                    proxy = Native.getLong(wpg2.getData());
                }
            } finally {
                wpg2.dispose();
            }

            if (proxy != 0) {
                WindowPropertyGetter wpg3 =
                    new WindowPropertyGetter(proxy, XDnDConstants.XA_XdndProxy,
                                             0, 1, false, XAtom.XA_WINDOW);

                try {
                    status = wpg3.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                    if (status != XlibWrapper.Success ||
                        wpg3.getData() == 0 ||
                        wpg3.getActualType() != XAtom.XA_WINDOW ||
                        Native.getLong(wpg3.getData()) != proxy) {

                        proxy = 0;
                    } else {
                        WindowPropertyGetter wpg4 =
                            new WindowPropertyGetter(proxy,
                                                     XDnDConstants.XA_XdndAware,
                                                     0, 1, false,
                                                     XlibWrapper.AnyPropertyType);

                        try {
                            status = wpg4.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                            if (status != XlibWrapper.Success ||
                                wpg4.getData() == 0 ||
                                wpg4.getActualType() != XAtom.XA_ATOM) {

                                proxy = 0;
                            }
                        } finally {
                            wpg4.dispose();
                        }
                    }
                } finally {
                    wpg3.dispose();
                }
            }
        }

        if (proxy == newProxy) {
            // Embedder already registered.
            return;
        }

        long data = Native.allocateLongArray(1);

        try {
            Native.putLong(data, 0, XDnDConstants.XDND_PROTOCOL_VERSION);

            /* The proxy window must have the XdndAware set, as XDnD protocol
               prescribes to check the proxy window for XdndAware. */
            XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
            XDnDConstants.XA_XdndAware.setAtomData(newProxy, XAtom.XA_ATOM,
                                                   data, 1);
            XToolkit.RESTORE_XERROR_HANDLER();

            if (XToolkit.saved_error != null &&
                XToolkit.saved_error.get_error_code() !=
                XlibWrapper.Success) {
                throw new XException("Cannot write XdndAware property");
            }

            Native.putLong(data, 0, newProxy);

            /* The proxy window must have the XdndProxy set to point to itself.*/
            XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
            XDnDConstants.XA_XdndProxy.setAtomData(newProxy, XAtom.XA_WINDOW,
                                                   data, 1);
            XToolkit.RESTORE_XERROR_HANDLER();

            if (XToolkit.saved_error != null &&
                XToolkit.saved_error.get_error_code() !=
                XlibWrapper.Success) {
                throw new XException("Cannot write XdndProxy property");
            }

            Native.putLong(data, 0, XDnDConstants.XDND_PROTOCOL_VERSION);

            XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
            XDnDConstants.XA_XdndAware.setAtomData(embedder, XAtom.XA_ATOM,
                                                   data, 1);
            XToolkit.RESTORE_XERROR_HANDLER();

            if (XToolkit.saved_error != null &&
                XToolkit.saved_error.get_error_code() !=
                XlibWrapper.Success) {
                throw new XException("Cannot write XdndAware property");
            }

            Native.putLong(data, 0, newProxy);

            XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
            XDnDConstants.XA_XdndProxy.setAtomData(embedder, XAtom.XA_WINDOW,
                                                   data, 1);
            XToolkit.RESTORE_XERROR_HANDLER();

            if (XToolkit.saved_error != null &&
                XToolkit.saved_error.get_error_code() !=
                XlibWrapper.Success) {
                throw new XException("Cannot write XdndProxy property");
            }
        } finally {
            unsafe.freeMemory(data);
            data = 0;
        }

        putEmbedderRegistryEntry(embedder, overriden, version, proxy);
    }

    public void unregisterEmbedderDropSite(long embedder) {
        assert XToolkit.isAWTLockHeldByCurrentThread();

        EmbedderRegistryEntry entry = getEmbedderRegistryEntry(embedder);

        if (entry == null) {
            return;
        }

        if (entry.isOverriden()) {
            long data = Native.allocateLongArray(1);

            try {
                Native.putLong(data, 0, entry.getVersion());

                XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
                XDnDConstants.XA_XdndAware.setAtomData(embedder, XAtom.XA_ATOM,
                                                       data, 1);
                XToolkit.RESTORE_XERROR_HANDLER();

                if (XToolkit.saved_error != null &&
                    XToolkit.saved_error.get_error_code() !=
                    XlibWrapper.Success) {
                    throw new XException("Cannot write XdndAware property");
                }

                Native.putLong(data, 0, (int)entry.getProxy());

                XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
                XDnDConstants.XA_XdndProxy.setAtomData(embedder, XAtom.XA_WINDOW,
                                                       data, 1);
                XToolkit.RESTORE_XERROR_HANDLER();

                if (XToolkit.saved_error != null &&
                    XToolkit.saved_error.get_error_code() !=
                    XlibWrapper.Success) {
                    throw new XException("Cannot write XdndProxy property");
                }
            } finally {
                unsafe.freeMemory(data);
                data = 0;
            }
        } else {
            XDnDConstants.XA_XdndAware.DeleteProperty(embedder);
            XDnDConstants.XA_XdndProxy.DeleteProperty(embedder);
        }
    }

    /*
     * Gets and stores in the registry the embedder's XDnD drop site info
     * from the embedded.
     */
    public void registerEmbeddedDropSite(long embedded) {
        assert XToolkit.isAWTLockHeldByCurrentThread();

        boolean overriden = false;
        int version = 0;
        long proxy = 0;
        long newProxy = XDropTargetRegistry.getDnDProxyWindow();
        int status = 0;

        WindowPropertyGetter wpg1 =
            new WindowPropertyGetter(embedded, XDnDConstants.XA_XdndAware, 0, 1,
                                     false, XlibWrapper.AnyPropertyType);

        try {
            status = wpg1.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

            if (status == XlibWrapper.Success &&
                wpg1.getData() != 0 && wpg1.getActualType() == XAtom.XA_ATOM) {

                overriden = true;
                version = (int)Native.getLong(wpg1.getData());
            }
        } finally {
            wpg1.dispose();
        }

        /* XdndProxy is not supported for prior to XDnD version 4 */
        if (overriden && version >= 4) {
            WindowPropertyGetter wpg2 =
                new WindowPropertyGetter(embedded, XDnDConstants.XA_XdndProxy,
                                         0, 1, false, XAtom.XA_WINDOW);

            try {
                status = wpg2.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                if (status == XlibWrapper.Success &&
                    wpg2.getData() != 0 &&
                    wpg2.getActualType() == XAtom.XA_WINDOW) {

                    proxy = Native.getLong(wpg2.getData());
                }
            } finally {
                wpg2.dispose();
            }

            if (proxy != 0) {
                WindowPropertyGetter wpg3 =
                    new WindowPropertyGetter(proxy, XDnDConstants.XA_XdndProxy,
                                             0, 1, false, XAtom.XA_WINDOW);

                try {
                    status = wpg3.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                    if (status != XlibWrapper.Success ||
                        wpg3.getData() == 0 ||
                        wpg3.getActualType() != XAtom.XA_WINDOW ||
                        Native.getLong(wpg3.getData()) != proxy) {

                        proxy = 0;
                    } else {
                        WindowPropertyGetter wpg4 =
                            new WindowPropertyGetter(proxy,
                                                     XDnDConstants.XA_XdndAware,
                                                     0, 1, false,
                                                     XlibWrapper.AnyPropertyType);

                        try {
                            status = wpg4.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                            if (status != XlibWrapper.Success ||
                                wpg4.getData() == 0 ||
                                wpg4.getActualType() != XAtom.XA_ATOM) {

                                proxy = 0;
                            }
                        } finally {
                            wpg4.dispose();
                        }
                    }
                } finally {
                    wpg3.dispose();
                }
            }
        }

        putEmbedderRegistryEntry(embedded, overriden, version, proxy);
    }

    public boolean isProtocolSupported(long window) {
        assert XToolkit.isAWTLockHeldByCurrentThread();

        WindowPropertyGetter wpg1 =
            new WindowPropertyGetter(window, XDnDConstants.XA_XdndAware, 0, 1,
                                     false, XlibWrapper.AnyPropertyType);

        try {
            int status = wpg1.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

            if (status == XlibWrapper.Success &&
                wpg1.getData() != 0 && wpg1.getActualType() == XAtom.XA_ATOM) {

                return true;
            } else {
                return false;
            }
        } finally {
            wpg1.dispose();
        }
    }

    private boolean processXdndEnter(XClientMessageEvent xclient) {
        long source_win = 0;
        long source_win_mask = 0;
        int protocol_version = 0;
        int actions = DnDConstants.ACTION_NONE;
        boolean track = true;
        long[] formats = null;

        if (getSourceWindow() != 0) {
            return false;
        }

        if (!(XToolkit.windowToXWindow(xclient.get_window()) instanceof XWindow)
            && getEmbedderRegistryEntry(xclient.get_window()) == null) {
            return false;
        }

        if (xclient.get_message_type() != XDnDConstants.XA_XdndEnter.getAtom()){
            return false;
        }

        protocol_version =
            (int)((xclient.get_data(1) & XDnDConstants.XDND_PROTOCOL_MASK) >>
                  XDnDConstants.XDND_PROTOCOL_SHIFT);

        /* XDnD compliance only requires supporting version 3 and up. */
        if (protocol_version < XDnDConstants.XDND_MIN_PROTOCOL_VERSION) {
            return false;
        }

        /* Ignore the source if the protocol version is higher than we support. */
        if (protocol_version > XDnDConstants.XDND_PROTOCOL_VERSION) {
            return false;
        }

        source_win = xclient.get_data(0);

        /* Extract the list of supported actions. */
        if (protocol_version < 2) {
            /* Prior to XDnD version 2 only COPY action was supported. */
            actions = DnDConstants.ACTION_COPY;
        } else {
            WindowPropertyGetter wpg =
                new WindowPropertyGetter(source_win,
                                         XDnDConstants.XA_XdndActionList,
                                         0, 0xFFFF, false,
                                         XAtom.XA_ATOM);
            try {
                wpg.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                if (wpg.getActualType() == XAtom.XA_ATOM &&
                    wpg.getActualFormat() == 32) {
                    long data = wpg.getData();

                    for (int i = 0; i < wpg.getNumberOfItems(); i++) {
                        actions |=
                            XDnDConstants.getJavaActionForXDnDAction(Native.getLong(data, i));
                    }
                } else {
                    /*
                     * According to XDnD protocol, XdndActionList is optional.
                     * If XdndActionList is not set we try to guess which actions are
                     * supported.
                     */
                    actions = DnDConstants.ACTION_COPY;
                    track = true;
                }
            } finally {
                wpg.dispose();
            }
        }

        /* Extract the available data types. */
        if ((xclient.get_data(1) & XDnDConstants.XDND_DATA_TYPES_BIT) != 0) {
            WindowPropertyGetter wpg =
                new WindowPropertyGetter(source_win,
                                         XDnDConstants.XA_XdndTypeList,
                                         0, 0xFFFF, false,
                                         XAtom.XA_ATOM);
            try {
                wpg.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                if (wpg.getActualType() == XAtom.XA_ATOM &&
                    wpg.getActualFormat() == 32) {
                    formats = Native.toLongs(wpg.getData(),
                                             wpg.getNumberOfItems());
                } else {
                    formats = new long[0];
                }
            } finally {
                wpg.dispose();
            }
        } else {
            int countFormats = 0;
            long[] formats3 = new long[3];

            for (int i = 0; i < 3; i++) {
                long j;
                if ((j = xclient.get_data(2 + i)) != XlibWrapper.None) {
                    formats3[countFormats++] = j;
                }
            }

            formats = new long[countFormats];

            System.arraycopy(formats3, 0, formats, 0, countFormats);
        }

        assert XToolkit.isAWTLockHeldByCurrentThread();

        /*
         * Select for StructureNotifyMask to receive DestroyNotify in case of source
         * crash.
         */
        XWindowAttributes wattr = new XWindowAttributes();
        try {
            XToolkit.WITH_XERROR_HANDLER(XErrorHandler.IgnoreBadWindowHandler.getInstance());
            int status = XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(),
                                                          source_win, wattr.pData);

            XToolkit.RESTORE_XERROR_HANDLER();

            if (status == 0 ||
                (XToolkit.saved_error != null &&
                 XToolkit.saved_error.get_error_code() != XlibWrapper.Success)) {
                throw new XException("XGetWindowAttributes failed");
            }

            source_win_mask = wattr.get_your_event_mask();
        } finally {
            wattr.dispose();
        }

        XToolkit.WITH_XERROR_HANDLER(XErrorHandler.IgnoreBadWindowHandler.getInstance());
        XlibWrapper.XSelectInput(XToolkit.getDisplay(), source_win,
                                 source_win_mask |
                                 XlibWrapper.StructureNotifyMask);

        XToolkit.RESTORE_XERROR_HANDLER();

        if (XToolkit.saved_error != null &&
            XToolkit.saved_error.get_error_code() != XlibWrapper.Success) {
            throw new XException("XSelectInput failed");
        }

        sourceWindow = source_win;
        sourceWindowMask = source_win_mask;
        sourceProtocolVersion = protocol_version;
        sourceActions = actions;
        sourceFormats = formats;
        trackSourceActions = track;

        return true;
    }

    private boolean processXdndPosition(XClientMessageEvent xclient) {
        long time_stamp = (int)XlibWrapper.CurrentTime;
        long xdnd_action = 0;
        int java_action = DnDConstants.ACTION_NONE;
        int x = 0;
        int y = 0;

        /* Ignore XDnD messages from all other windows. */
        if (sourceWindow != xclient.get_data(0)) {
            return false;
        }

        XWindow xwindow = null;
        {
            XBaseWindow xbasewindow = XToolkit.windowToXWindow(xclient.get_window());
            if (xbasewindow instanceof XWindow) {
                xwindow = (XWindow)xbasewindow;
            }
        }

        x = (int)(xclient.get_data(2) >> 16);
        y = (int)(xclient.get_data(2) & 0xFFFF);

        if (xwindow == null) {
            long receiver =
                XDropTargetRegistry.getRegistry().getEmbeddedDropSite(
                    xclient.get_window(), x, y);

            if (receiver != 0) {
                XBaseWindow xbasewindow = XToolkit.windowToXWindow(receiver);
                if (xbasewindow instanceof XWindow) {
                    xwindow = (XWindow)xbasewindow;
                }
            }
        }

        if (xwindow != null) {
            /* Translate mouse position from root coordinates
               to the target window coordinates. */
            Point p = xwindow.toLocal(x, y);
            x = p.x;
            y = p.y;
        }

        /* Time stamp - new in XDnD version 1. */
        if (sourceProtocolVersion > 0) {
            time_stamp = xclient.get_data(3);
        }

        /* User action - new in XDnD version 2. */
        if (sourceProtocolVersion > 1) {
            xdnd_action = xclient.get_data(4);
        } else {
            /* The default action is XdndActionCopy */
            xdnd_action = XDnDConstants.XA_XdndActionCopy.getAtom();
        }

        java_action = XDnDConstants.getJavaActionForXDnDAction(xdnd_action);

        if (trackSourceActions) {
            sourceActions |= java_action;
        }

        if (xwindow == null) {
            if (targetXWindow != null) {
                notifyProtocolListener(targetXWindow, x, y,
                                       DnDConstants.ACTION_NONE, xclient,
                                       MouseEvent.MOUSE_EXITED);
            }
        } else {
            int java_event_id = 0;

            if (targetXWindow == null) {
                java_event_id = MouseEvent.MOUSE_ENTERED;
            } else {
                java_event_id = MouseEvent.MOUSE_DRAGGED;
            }

            notifyProtocolListener(xwindow, x, y, java_action, xclient,
                                   java_event_id);
        }

        userAction = java_action;
        sourceX = x;
        sourceY = y;
        targetXWindow = xwindow;

        return true;
    }

    private boolean processXdndLeave(XClientMessageEvent xclient) {
        /* Ignore XDnD messages from all other windows. */
        if (sourceWindow != xclient.get_data(0)) {
            return false;
        }

        cleanup();

        return true;
    }

    private boolean processXdndDrop(XClientMessageEvent xclient) {
        /* Ignore XDnD messages from all other windows. */
        if (sourceWindow != xclient.get_data(0)) {
            return false;
        }

        if (targetXWindow != null) {
            notifyProtocolListener(targetXWindow, sourceX, sourceY, userAction,
                                   xclient, MouseEvent.MOUSE_RELEASED);
        }

        return true;
    }

    public int getMessageType(XClientMessageEvent xclient) {
        long messageType = xclient.get_message_type();

        if (messageType == XDnDConstants.XA_XdndEnter.getAtom()) {
            return ENTER_MESSAGE;
        } else if (messageType == XDnDConstants.XA_XdndPosition.getAtom()) {
            return MOTION_MESSAGE;
        } else if (messageType == XDnDConstants.XA_XdndLeave.getAtom()) {
            return LEAVE_MESSAGE;
        } else if (messageType == XDnDConstants.XA_XdndDrop.getAtom()) {
            return DROP_MESSAGE;
        } else {
            return UNKNOWN_MESSAGE;
        }
    }

    protected boolean processClientMessageImpl(XClientMessageEvent xclient) {
        long messageType = xclient.get_message_type();

        if (messageType == XDnDConstants.XA_XdndEnter.getAtom()) {
            return processXdndEnter(xclient);
        } else if (messageType == XDnDConstants.XA_XdndPosition.getAtom()) {
            return processXdndPosition(xclient);
        } else if (messageType == XDnDConstants.XA_XdndLeave.getAtom()) {
            return processXdndLeave(xclient);
        } else if (messageType == XDnDConstants.XA_XdndDrop.getAtom()) {
            return processXdndDrop(xclient);
        } else {
            return false;
        }
    }

    protected void sendEnterMessageToToplevel(long toplevel,
                                              XClientMessageEvent xclient) {
        /* flags */
        long data1 = sourceProtocolVersion << XDnDConstants.XDND_PROTOCOL_SHIFT;
        if (sourceFormats != null && sourceFormats.length > 3) {
            data1 |= XDnDConstants.XDND_DATA_TYPES_BIT;
        }
        long data2 = sourceFormats.length > 0 ? sourceFormats[0] : 0;
        long data3 = sourceFormats.length > 1 ? sourceFormats[1] : 0;
        long data4 = sourceFormats.length > 2 ? sourceFormats[2] : 0;
        sendEnterMessageToToplevelImpl(toplevel, xclient.get_data(0),
                                       data1, data2, data3, data4);

    }

    private void sendEnterMessageToToplevelImpl(long toplevel,
                                                long sourceWindow,
                                                long data1, long data2,
                                                long data3, long data4) {
        XClientMessageEvent enter = new XClientMessageEvent();
        try {
            enter.set_type((int)XlibWrapper.ClientMessage);
            enter.set_window(toplevel);
            enter.set_format(32);
            enter.set_message_type(XDnDConstants.XA_XdndEnter.getAtom());
            /* XID of the source window */
            enter.set_data(0, sourceWindow);
            enter.set_data(1, data1);
            enter.set_data(2, data2);
            enter.set_data(3, data3);
            enter.set_data(4, data4);

            forwardClientMessageToToplevel(toplevel, enter);
        } finally {
            enter.dispose();
        }
    }

    protected void sendLeaveMessageToToplevel(long toplevel,
                                              XClientMessageEvent xclient) {
        sendLeaveMessageToToplevelImpl(toplevel, xclient.get_data(0));
    }

    protected void sendLeaveMessageToToplevelImpl(long toplevel,
                                                  long sourceWindow) {
        XClientMessageEvent leave = new XClientMessageEvent();
        try {
            leave.set_type((int)XlibWrapper.ClientMessage);
            leave.set_window(toplevel);
            leave.set_format(32);
            leave.set_message_type(XDnDConstants.XA_XdndLeave.getAtom());
            /* XID of the source window */
            leave.set_data(0, sourceWindow);
            /* flags */
            leave.set_data(1, 0);

            forwardClientMessageToToplevel(toplevel, leave);
        } finally {
            leave.dispose();
        }
    }

    public boolean sendResponse(long ctxt, int eventID, int action) {
        XClientMessageEvent xclient = new XClientMessageEvent(ctxt);

        if (xclient.get_message_type() !=
            XDnDConstants.XA_XdndPosition.getAtom()) {

            return false;
        }

        if (eventID == MouseEvent.MOUSE_EXITED) {
            action = DnDConstants.ACTION_NONE;
        }

        XClientMessageEvent msg = new XClientMessageEvent();
        try {
            msg.set_type((int)XlibWrapper.ClientMessage);
            msg.set_window(xclient.get_data(0));
            msg.set_format(32);
            msg.set_message_type(XDnDConstants.XA_XdndStatus.getAtom());
            /* target window */
            msg.set_data(0, xclient.get_window());
            /* flags */
            long flags = 0;
            if (action != DnDConstants.ACTION_NONE) {
                flags |= XDnDConstants.XDND_ACCEPT_DROP_FLAG;
            }
            msg.set_data(1, flags);
            /* specify an empty rectangle */
            msg.set_data(2, 0); /* x, y */
            msg.set_data(3, 0); /* w, h */
            /* action accepted by the target */
            msg.set_data(4, XDnDConstants.getXDnDActionForJavaAction(action));

            XToolkit.awtLock();
            try {
                XlibWrapper.XSendEvent(XToolkit.getDisplay(),
                                       xclient.get_data(0),
                                       false, XlibWrapper.NoEventMask,
                                       msg.pData);
            } finally {
                XToolkit.awtUnlock();
            }
        } finally {
            msg.dispose();
        }

        return true;
    }

    public Object getData(long ctxt, long format)
      throws IllegalArgumentException, IOException {
        XClientMessageEvent xclient = new XClientMessageEvent(ctxt);
        long message_type = xclient.get_message_type();
        long time_stamp = XlibWrapper.CurrentTime;

        // NOTE: we assume that the source supports at least version 1, so we
        // can use the time stamp
        if (message_type == XDnDConstants.XA_XdndPosition.getAtom()) {
            // X server time is an unsigned 32-bit number!
            time_stamp = xclient.get_data(3) & 0xFFFFFFFFL;
        } else if (message_type == XDnDConstants.XA_XdndDrop.getAtom()) {
            // X server time is an unsigned 32-bit number!
            time_stamp = xclient.get_data(2) & 0xFFFFFFFFL;
        } else {
            throw new IllegalArgumentException();
        }

        return XDnDConstants.XDnDSelection.getData(format, time_stamp);
    }

    public boolean sendDropDone(long ctxt, boolean success, int dropAction) {
        XClientMessageEvent xclient = new XClientMessageEvent(ctxt);

        if (xclient.get_message_type() !=
            XDnDConstants.XA_XdndDrop.getAtom()) {
            return false;
        }

        /*
         * The XDnD protocol recommends that the target requests the special
         * target DELETE in case if the drop action is XdndActionMove.
         */
        if (dropAction == DnDConstants.ACTION_MOVE && success) {

            long time_stamp = xclient.get_data(2);
            long xdndSelectionAtom =
                XDnDConstants.XDnDSelection.getSelectionAtom().getAtom();

            XToolkit.awtLock();
            try {
                XlibWrapper.XConvertSelection(XToolkit.getDisplay(),
                                              xdndSelectionAtom,
                                              XAtom.get("DELETE").getAtom(),
                                              XAtom.get("XAWT_SELECTION").getAtom(),
                                              XWindow.getXAWTRootWindow().getWindow(),
                                              time_stamp);
            } finally {
                XToolkit.awtUnlock();
            }
        }

        XClientMessageEvent msg = new XClientMessageEvent();
        try {
            msg.set_type((int)XlibWrapper.ClientMessage);
            msg.set_window(xclient.get_data(0));
            msg.set_format(32);
            msg.set_message_type(XDnDConstants.XA_XdndFinished.getAtom());
            msg.set_data(0, xclient.get_window()); /* target window */
            msg.set_data(1, 0); /* flags */
            /* specify an empty rectangle */
            msg.set_data(2, 0);
            if (sourceProtocolVersion >= 5) {
                if (success) {
                    msg.set_data(1, XDnDConstants.XDND_ACCEPT_DROP_FLAG);
                }
                /* action performed by the target */
                msg.set_data(2, XDnDConstants.getXDnDActionForJavaAction(dropAction));
            }
            msg.set_data(3, 0);
            msg.set_data(4, 0);

            XToolkit.awtLock();
            try {
                XlibWrapper.XSendEvent(XToolkit.getDisplay(),
                                       xclient.get_data(0),
                                       false, XlibWrapper.NoEventMask,
                                       msg.pData);
            } finally {
                XToolkit.awtUnlock();
            }
        } finally {
            msg.dispose();
        }

        /*
         * Flush the buffer to guarantee that the drop completion event is sent
         * to the source before the method returns.
         */
        XToolkit.awtLock();
        try {
            XlibWrapper.XFlush(XToolkit.getDisplay());
        } finally {
            XToolkit.awtUnlock();
        }

        /* Trick to prevent cleanup() from posting dragExit */
        targetXWindow = null;

        /* Cannot do cleanup before the drop finishes as we may need
           source protocol version to send drop finished message. */
        cleanup();
        return true;
    }

    public final long getSourceWindow() {
        return sourceWindow;
    }

    /**
     * Reset the state of the object.
     */
    public void cleanup() {
        // Clear the reference to this protocol.
        XDropTargetEventProcessor.reset();

        if (targetXWindow != null) {
            notifyProtocolListener(targetXWindow, 0, 0,
                                   DnDConstants.ACTION_NONE, null,
                                   MouseEvent.MOUSE_EXITED);
        }

        if (sourceWindow != 0) {
            XToolkit.awtLock();
            try {
                XToolkit.WITH_XERROR_HANDLER(XErrorHandler.IgnoreBadWindowHandler.getInstance());
                XlibWrapper.XSelectInput(XToolkit.getDisplay(), sourceWindow,
                                         sourceWindowMask);
                XToolkit.RESTORE_XERROR_HANDLER();
            } finally {
                XToolkit.awtUnlock();
            }
        }

        sourceWindow = 0;
        sourceWindowMask = 0;
        sourceProtocolVersion = 0;
        sourceActions = DnDConstants.ACTION_NONE;
        sourceFormats = null;
        trackSourceActions = false;
        userAction = DnDConstants.ACTION_NONE;
        sourceX = 0;
        sourceY = 0;
        targetXWindow = null;
    }

    public boolean isDragOverComponent() {
        return targetXWindow != null;
    }

    public void adjustEventForForwarding(XClientMessageEvent xclient,
                                         EmbedderRegistryEntry entry) {
        /* Adjust the event to match the XDnD protocol version. */
        int version = entry.getVersion();
        if (xclient.get_message_type() == XDnDConstants.XA_XdndEnter.getAtom()) {
            int min_version = sourceProtocolVersion < version ?
                sourceProtocolVersion : version;
            long data1 = min_version << XDnDConstants.XDND_PROTOCOL_SHIFT;
            if (sourceFormats != null && sourceFormats.length > 3) {
                data1 |= XDnDConstants.XDND_DATA_TYPES_BIT;
            }
            if (logger.isLoggable(Level.FINEST)) {
                logger.finest("         "
                              + " entryVersion=" + version
                              + " sourceProtocolVersion=" +
                              sourceProtocolVersion
                              + " sourceFormats.length=" +
                              (sourceFormats != null ? sourceFormats.length : 0));
            }
            xclient.set_data(1, data1);
        }
    }

    private void notifyProtocolListener(XWindow xwindow, int x, int y,
                                        int dropAction,
                                        XClientMessageEvent xclient,
                                        int eventID) {
        long nativeCtxt = 0;

        // Make a copy of the passed XClientMessageEvent structure, since
        // the original structure can be freed before this
        // SunDropTargetEvent is dispatched.
        if (xclient != null) {
            int size = new XClientMessageEvent(nativeCtxt).getSize();

            nativeCtxt = unsafe.allocateMemory(size + 4 * Native.getLongSize());

            unsafe.copyMemory(xclient.pData, nativeCtxt, size);

            long data1 = sourceProtocolVersion << XDnDConstants.XDND_PROTOCOL_SHIFT;
            if (sourceFormats != null && sourceFormats.length > 3) {
                data1 |= XDnDConstants.XDND_DATA_TYPES_BIT;
            }
            // Append information from the latest XdndEnter event.
            Native.putLong(nativeCtxt + size, data1);
            Native.putLong(nativeCtxt + size + Native.getLongSize(),
                           sourceFormats.length > 0 ? sourceFormats[0] : 0);
            Native.putLong(nativeCtxt + size + 2 * Native.getLongSize(),
                           sourceFormats.length > 1 ? sourceFormats[1] : 0);
            Native.putLong(nativeCtxt + size + 3 * Native.getLongSize(),
                           sourceFormats.length > 2 ? sourceFormats[2] : 0);
        }

        getProtocolListener().handleDropTargetNotification(xwindow, x, y,
                                                           dropAction,
                                                           sourceActions,
                                                           sourceFormats,
                                                           nativeCtxt,
                                                           eventID);
    }

    /*
     * The methods/fields defined below are executed/accessed only on
     * the toolkit thread.
     * The methods/fields defined below are executed/accessed only on the event
     * dispatch thread.
     */

    public boolean forwardEventToEmbedded(long embedded, long ctxt,
                                          int eventID) {
        if (logger.isLoggable(Level.FINEST)) {
            logger.finest("        ctxt=" + ctxt +
                          " type=" + (ctxt != 0 ?
                                      getMessageType(new
                                          XClientMessageEvent(ctxt)) : 0) +
                          " prevCtxt=" + prevCtxt +
                          " prevType=" + (prevCtxt != 0 ?
                                      getMessageType(new
                                          XClientMessageEvent(prevCtxt)) : 0));
        }
        if ((ctxt == 0 ||
             getMessageType(new XClientMessageEvent(ctxt)) == UNKNOWN_MESSAGE) &&
            (prevCtxt == 0 ||
             getMessageType(new XClientMessageEvent(prevCtxt)) == UNKNOWN_MESSAGE)) {
            return false;
        }

        // The size of XClientMessageEvent structure.
        int size = XClientMessageEvent.getSize();

        if (ctxt != 0) {
            XClientMessageEvent xclient = new XClientMessageEvent(ctxt);
            if (!overXEmbedClient) {
                long data1 = Native.getLong(ctxt + size);
                long data2 = Native.getLong(ctxt + size + Native.getLongSize());
                long data3 = Native.getLong(ctxt + size + 2 * Native.getLongSize());
                long data4 = Native.getLong(ctxt + size + 3 * Native.getLongSize());

                if (logger.isLoggable(Level.FINEST)) {
                    logger.finest("         1 "
                                  + " embedded=" + embedded
                                  + " source=" + xclient.get_data(0)
                                  + " data1=" + data1
                                  + " data2=" + data2
                                  + " data3=" + data3
                                  + " data4=" + data4);
                }

                // Copy XdndTypeList from source to proxy.
                if ((data1 & XDnDConstants.XDND_DATA_TYPES_BIT) != 0) {
                    WindowPropertyGetter wpg =
                        new WindowPropertyGetter(xclient.get_data(0),
                                                 XDnDConstants.XA_XdndTypeList,
                                                 0, 0xFFFF, false,
                                                 XAtom.XA_ATOM);
                    try {
                        wpg.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());

                        if (wpg.getActualType() == XAtom.XA_ATOM &&
                            wpg.getActualFormat() == 32) {

                            XToolkit.awtLock();
                            try {
                                XToolkit.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
                                XDnDConstants.XA_XdndTypeList.setAtomData(xclient.get_window(),
                                                                          XAtom.XA_ATOM,
                                                                          wpg.getData(),
                                                                          wpg.getNumberOfItems());
                                XToolkit.RESTORE_XERROR_HANDLER();

                                if (XToolkit.saved_error != null &&
                                    XToolkit.saved_error.get_error_code() != XlibWrapper.Success) {
                                    if (logger.isLoggable(Level.WARNING)) {
                                        logger.warning("Cannot set XdndTypeList on the proxy window");
                                    }
                                }
                            } finally {
                                XToolkit.awtUnlock();
                            }
                        } else {
                            if (logger.isLoggable(Level.WARNING)) {
                                logger.warning("Cannot read XdndTypeList from the source window");
                            }
                        }
                    } finally {
                        wpg.dispose();
                    }
                }
                XDragSourceContextPeer.setProxyModeSourceWindow(xclient.get_data(0));

                sendEnterMessageToToplevelImpl(embedded, xclient.get_window(),
                                               data1, data2, data3, data4);
                overXEmbedClient = true;
            }

            if (logger.isLoggable(Level.FINEST)) {
                logger.finest("         2 "
                              + " embedded=" + embedded
                              + " xclient=" + xclient);
            }

            /* Make a copy of the original event, since we are going to modify the
               event while it still can be referenced from other Java events. */
            {
                XClientMessageEvent copy = new XClientMessageEvent();
                unsafe.copyMemory(xclient.pData, copy.pData, copy.getSize());

                copy.set_data(0, xclient.get_window());

                forwardClientMessageToToplevel(embedded, copy);
            }
        }

        if (eventID == MouseEvent.MOUSE_EXITED) {
            if (overXEmbedClient) {
                if (ctxt != 0 || prevCtxt != 0) {
                    // Last chance to send XdndLeave to the XEmbed client.
                    XClientMessageEvent xclient = ctxt != 0 ?
                        new XClientMessageEvent(ctxt) :
                        new XClientMessageEvent(prevCtxt);
                    sendLeaveMessageToToplevelImpl(embedded, xclient.get_window());
                }
                overXEmbedClient = false;
                // We have to clear the proxy mode source window here,
                // when the drag exits the XEmbedCanvasPeer.
                // NOTE: at this point the XEmbed client still might have some
                // drag notifications to process and it will send responses to
                // us. With the proxy mode source window cleared we won't be
                // able to forward these responses to the actual source. This is
                // not a problem if the drag operation was initiated in this
                // JVM. However, if it was initiated in another processes the
                // responses will be lost. We bear with it for now, as it seems
                // there is no other reliable point to clear.
                XDragSourceContextPeer.setProxyModeSourceWindow(0);
            }
        }

        if (eventID == MouseEvent.MOUSE_RELEASED) {
            overXEmbedClient = false;
            cleanup();
        }

        if (prevCtxt != 0) {
            unsafe.freeMemory(prevCtxt);
            prevCtxt = 0;
        }

        if (ctxt != 0 && overXEmbedClient) {
            prevCtxt = unsafe.allocateMemory(size + 4 * Native.getLongSize());

            unsafe.copyMemory(ctxt, prevCtxt, size + 4 * Native.getLongSize());
        }

        return true;
    }

    public boolean isXEmbedSupported() {
        return true;
    }
}
