/*
 * Copyright (c) 2011, 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.lwawt.macosx;

import java.awt.*;
import java.awt.dnd.DropTarget;

import sun.awt.dnd.SunDropTargetContextPeer;
import sun.awt.dnd.SunDropTargetEvent;

import javax.swing.*;


final class CDropTargetContextPeer extends SunDropTargetContextPeer {

    private long    fNativeDropTransfer = 0;
    private long    fNativeDataAvailable = 0;
    private Object  fNativeData    = null;
    private DropTarget insideTarget = null;

    Object awtLockAccess = new Object();

    static CDropTargetContextPeer getDropTargetContextPeer() {
        return new CDropTargetContextPeer();
    }

    private CDropTargetContextPeer() {
        super();
    }

    // We block, waiting for an empty event to get posted (CToolkit.invokeAndWait)
    // This is so we finish dispatching DropTarget events before we dispatch the dragDropFinished event (which is a higher priority).
    private void flushEvents(Component c) {
        try {
            LWCToolkit.invokeAndWait(new Runnable() {
                public synchronized void run() {
                }
            }, c);
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }

    protected Object getNativeData(long format) {
        long nativeDropTarget = this.getNativeDragContext();

        synchronized (awtLockAccess) {
            fNativeDataAvailable = 0;

            if (fNativeDropTransfer == 0) {
                fNativeDropTransfer = startTransfer(nativeDropTarget, format);
            } else {
                addTransfer(nativeDropTarget, fNativeDropTransfer, format);
            }

            while (format != fNativeDataAvailable) {
                try {
                    awtLockAccess.wait();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        }

        return fNativeData;
    }

    // We need to take care of dragEnter and dragExit messages because
    // native system generates them only for heavyweights
    @Override
    protected void processMotionMessage(SunDropTargetEvent event, boolean operationChanged) {
        boolean eventInsideTarget = isEventInsideTarget(event);
        if (event.getComponent().getDropTarget() == insideTarget) {
            if (!eventInsideTarget) {
                processExitMessage(event);
                return;
            }
        } else {
            if (eventInsideTarget) {
                processEnterMessage(event);
            } else {
                return;
            }
        }
        super.processMotionMessage(event, operationChanged);
    }

    /**
     * Could be called when DnD enters a heavyweight or synthesized in processMotionMessage
     */
    @Override
    protected void processEnterMessage(SunDropTargetEvent event) {
        Component c = event.getComponent();
        DropTarget dt = event.getComponent().getDropTarget();
        if (isEventInsideTarget(event)
                && dt != insideTarget
                && c.isShowing()
                && dt != null
                && dt.isActive()) {
            insideTarget = dt;
            super.processEnterMessage(event);
        }
    }

    /**
     * Could be called when DnD exits a heavyweight or synthesized in processMotionMessage
     */
    @Override
    protected void processExitMessage(SunDropTargetEvent event) {
        if (event.getComponent().getDropTarget() == insideTarget) {
            insideTarget = null;
            super.processExitMessage(event);
        }
    }

    @Override
    protected void processDropMessage(SunDropTargetEvent event) {
        if (isEventInsideTarget(event)) {
            super.processDropMessage(event);
            insideTarget = null;
        }
    }

    private boolean isEventInsideTarget(SunDropTargetEvent event) {
        Component eventSource = event.getComponent();
        Point screenPoint = event.getPoint();
        SwingUtilities.convertPointToScreen(screenPoint, eventSource);
        Point locationOnScreen = eventSource.getLocationOnScreen();
        Rectangle screenBounds = new Rectangle(locationOnScreen.x,
                                               locationOnScreen.y,
                                               eventSource.getWidth(),
                                               eventSource.getHeight());
        return screenBounds.contains(screenPoint);
    }

    @Override
    protected int postDropTargetEvent(Component component, int x, int y, int dropAction,
                                      int actions, long[] formats, long nativeCtxt, int eventID,
                                      boolean dispatchType) {
        // On MacOS X all the DnD events should be synchronous
        return super.postDropTargetEvent(component, x, y, dropAction, actions, formats, nativeCtxt,
                eventID, SunDropTargetContextPeer.DISPATCH_SYNC);
    }

    // Signal drop complete:
    protected void doDropDone(boolean success, int dropAction, boolean isLocal) {
        long nativeDropTarget = this.getNativeDragContext();

        dropDone(nativeDropTarget, fNativeDropTransfer, isLocal, success, dropAction);
    }

    // Notify transfer complete - this is an upcall from getNativeData's native calls:
    private void newData(long format, byte[] data) {
        fNativeDataAvailable = format;
        fNativeData          = data;

        awtLockAccess.notifyAll();
    }

    // Notify transfer failed - this is an upcall from getNativeData's native calls:
    private void transferFailed(long format) {
        fNativeDataAvailable = format;
        fNativeData          = null;

        awtLockAccess.notifyAll();
    }

    // Schedule a native dnd transfer:
    private native long startTransfer(long nativeDropTarget, long format);

    // Schedule a native dnd data transfer:
    private native void addTransfer(long nativeDropTarget, long nativeDropTransfer, long format);

    // Notify drop completed:
    private native void dropDone(long nativeDropTarget, long nativeDropTransfer, boolean isLocal, boolean success, int dropAction);
}
