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

import java.util.HashMap;

import sun.util.logging.PlatformLogger;

/**
 * An abstract class for drop protocols on X11 systems.
 * Contains protocol-independent drop target code.
 *
 * @since 1.5
 */
abstract class XDropTargetProtocol {
    private static final PlatformLogger logger =
        PlatformLogger.getLogger("sun.awt.X11.xembed.xdnd.XDropTargetProtocol");

    private final XDropTargetProtocolListener listener;

    public static final int EMBEDDER_ALREADY_REGISTERED = 0;

    public static final int UNKNOWN_MESSAGE = 0;
    public static final int ENTER_MESSAGE   = 1;
    public static final int MOTION_MESSAGE  = 2;
    public static final int LEAVE_MESSAGE   = 3;
    public static final int DROP_MESSAGE    = 4;

    protected XDropTargetProtocol(XDropTargetProtocolListener listener) {
        if (listener == null) {
            throw new NullPointerException("Null XDropTargetProtocolListener");
        }
        this.listener = listener;
    }

    protected final XDropTargetProtocolListener getProtocolListener() {
        return listener;
    }

    /**
     * Returns the protocol name. The protocol name cannot be null.
     */
    public abstract String getProtocolName();

    /* The caller must hold AWT_LOCK. */
    public abstract void registerDropTarget(long window);

    /* The caller must hold AWT_LOCK. */
    public abstract void unregisterDropTarget(long window);

    /* The caller must hold AWT_LOCK. */
    public abstract void registerEmbedderDropSite(long window);

    /* The caller must hold AWT_LOCK. */
    public abstract void unregisterEmbedderDropSite(long window);

    /* The caller must hold AWT_LOCK. */
    public abstract void registerEmbeddedDropSite(long embedded);

    /* The caller must hold AWT_LOCK. */
    public final void unregisterEmbeddedDropSite(long embedded) {
        removeEmbedderRegistryEntry(embedded);
    }


    /* The caller must hold AWT_LOCK. */
    public abstract boolean isProtocolSupported(long window);

    public abstract int getMessageType(XClientMessageEvent xclient);

    /* The caller must hold AWT_LOCK. */
    public final boolean processClientMessage(XClientMessageEvent xclient) {
        int type = getMessageType(xclient);
        boolean processed = processClientMessageImpl(xclient);

        postProcessClientMessage(xclient, processed, type);

        return processed;
    }

    /* The caller must hold AWT_LOCK. */
    protected abstract boolean processClientMessageImpl(XClientMessageEvent xclient);

    /*
     * Forwards a drag notification to the embedding toplevel modifying the event
     * to match the protocol version supported by the toplevel.
     * The caller must hold AWT_LOCK.
     * Returns True if the event is sent, False otherwise.
     */
    protected final boolean forwardClientMessageToToplevel(long toplevel,
                                                           XClientMessageEvent xclient) {
        EmbedderRegistryEntry entry = getEmbedderRegistryEntry(toplevel);

        if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
            logger.finest("        entry={0}", entry);
        }
        // Window not registered as an embedder for this protocol.
        if (entry == null) {
            return false;
        }

        if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
            logger.finest("        entry.isOverriden()={0}", entry.isOverriden());
        }
        // Window didn't have an associated drop site, so there is no need
        // to forward the message.
        if (!entry.isOverriden()) {
            return false;
        }

        adjustEventForForwarding(xclient, entry);

        long proxy = entry.getProxy();

        if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
            logger.finest("        proxy={0} toplevel={1}", proxy, toplevel);
        }
        if (proxy == 0) {
            proxy = toplevel;
        }

        xclient.set_window(toplevel);

        XToolkit.awtLock();
        try {
            XlibWrapper.XSendEvent(XToolkit.getDisplay(), proxy, false,
                                   XConstants.NoEventMask, xclient.pData);
        } finally {
            XToolkit.awtUnlock();
        }

        return true;
    }


    /* True iff the previous notification was MotionEvent and it was
       forwarded to the browser. */
    private boolean motionPassedAlong = false;

    protected abstract void sendEnterMessageToToplevel(long toplevel,
                                                       XClientMessageEvent xclient);

    protected abstract void sendLeaveMessageToToplevel(long toplevel,
                                                       XClientMessageEvent xclient);

    private void postProcessClientMessage(XClientMessageEvent xclient,
                                          boolean processed,
                                          int type) {
        long toplevel = xclient.get_window();

        if (getEmbedderRegistryEntry(toplevel) != null) {
            /*
             * This code forwards drag notifications to the browser according to the
             * following rules:
             *  - the messages that we failed to process are always forwarded to the
             *    browser;
             *  - MotionEvents and DropEvents are forwarded if and only if the drag
             *    is not over a plugin window;
             *  - XDnD: EnterEvents and LeaveEvents are never forwarded, instead, we
             *    send synthesized EnterEvents or LeaveEvents when the drag
             *    respectively exits or enters plugin windows;
             *  - Motif DnD: EnterEvents and LeaveEvents are always forwarded.
             * Synthetic EnterEvents and LeaveEvents are needed, because the XDnD drop
             * site implemented Netscape 6.2 has a nice feature: when it receives
             * the first XdndPosition it continuously sends XdndStatus messages to
             * the source (every 100ms) until the drag terminates or leaves the drop
             * site. When the mouse is dragged over plugin window embedded in the
             * browser frame, these XdndStatus messages are mixed with the XdndStatus
             * messages sent from the plugin.
             * For Motif DnD, synthetic events cause Motif warnings being displayed,
             * so these events are always forwarded. However, Motif DnD drop site in
             * Netscape 6.2 is implemented in the same way, so there could be similar
             * problems if the drag source choose Motif DnD for communication.
             */
            if (!processed) {
                forwardClientMessageToToplevel(toplevel, xclient);
            } else {
                boolean motifProtocol =
                    xclient.get_message_type() ==
                    MotifDnDConstants.XA_MOTIF_DRAG_AND_DROP_MESSAGE.getAtom();

                switch (type) {
                case XDropTargetProtocol.MOTION_MESSAGE:
                    if (!isDragOverComponent()) {
                        if (!motionPassedAlong && !motifProtocol) {
                            sendEnterMessageToToplevel(toplevel, xclient);
                        }
                        forwardClientMessageToToplevel(toplevel, xclient);
                        motionPassedAlong = true;
                    } else {
                        if (motionPassedAlong && !motifProtocol) {
                            sendLeaveMessageToToplevel(toplevel, xclient);
                        }
                        motionPassedAlong = false;
                    }
                    break;
                case XDropTargetProtocol.DROP_MESSAGE:
                    if (!isDragOverComponent()) {
                        forwardClientMessageToToplevel(toplevel, xclient);
                    }
                    motionPassedAlong = false;
                    break;
                case XDropTargetProtocol.ENTER_MESSAGE:
                case XDropTargetProtocol.LEAVE_MESSAGE:
                    if (motifProtocol) {
                        forwardClientMessageToToplevel(toplevel, xclient);
                    }
                    motionPassedAlong = false;
                    break;
                }
            }
        }
    }

    public abstract boolean sendResponse(long ctxt, int eventID, int action);

    /*
     * Retrieves the data from the drag source in the specified format.
     *
     * @param ctxt a pointer to the XClientMessageEvent structure for this
     *             protocol's drop message.
     * @param format the format in which the data should be retrieved.
     *
     * @throws IllegalArgumentException if ctxt doesn't point to the
     *         XClientMessageEvent structure for this protocol's drop message.
     * @throws IOException if data retrieval failed.
     */
    public abstract Object getData(long ctxt, long format)
      throws IllegalArgumentException, IOException;

    public abstract boolean sendDropDone(long ctxt, boolean success,
                                         int dropAction);

    public abstract long getSourceWindow();

    public abstract void cleanup();

    public abstract boolean isDragOverComponent();

    public void adjustEventForForwarding(XClientMessageEvent xclient,
        EmbedderRegistryEntry entry) {}

    public abstract boolean forwardEventToEmbedded(long embedded, long ctxt,
                                                   int eventID);

    /*
     * Returns true if the XEmbed protocol prescribes that an XEmbed server must
     * support this DnD protocol for drop sites associated with XEmbed clients.
     */
    public abstract boolean isXEmbedSupported();

    protected static final class EmbedderRegistryEntry {
        private final boolean overridden;
        private final int version;
        private final long proxy;
        EmbedderRegistryEntry(boolean overridden, int version, long proxy) {
            this.overridden = overridden;
            this.version = version;
            this.proxy = proxy;
        }
        public boolean isOverriden() {
            return overridden;
        }
        public int getVersion() {
            return version;
        }
        public long getProxy() {
            return proxy;
        }
    }

    /* Access to HashMap is synchronized on this XDropTargetProtocol instance. */
    private final HashMap<Long, EmbedderRegistryEntry> embedderRegistry =
        new HashMap<>();

    protected final void putEmbedderRegistryEntry(long embedder,
                                                  boolean overridden,
                                                  int version,
                                                  long proxy) {
        synchronized (this) {
            embedderRegistry.put(Long.valueOf(embedder),
                                 new EmbedderRegistryEntry(overridden, version,
                                                           proxy));
        }
    }

    protected final EmbedderRegistryEntry getEmbedderRegistryEntry(long embedder) {
        synchronized (this) {
            return embedderRegistry.get(Long.valueOf(embedder));
        }
    }

    protected final void removeEmbedderRegistryEntry(long embedder) {
        synchronized (this) {
            embedderRegistry.remove(Long.valueOf(embedder));
        }
    }
}
