/*
 * Copyright (c) 2003, 2025, 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.AWTKeyStroke;
import sun.awt.SunToolkit;
import java.awt.Component;
import java.awt.Container;
import sun.util.logging.PlatformLogger;

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

/**
 * Helper class implementing XEmbed protocol handling routines(client side)
 * Window which wants to participate in a protocol should create an instance,
 * call install and forward all XClientMessageEvents to it.
 */
public final class XEmbedClientHelper extends XEmbedHelper implements XEventDispatcher {
    private static final PlatformLogger xembedLog = PlatformLogger.getLogger("sun.awt.X11.xembed.XEmbedClientHelper");

    private XEmbeddedFramePeer embedded; // XEmbed client
    private long server; // XEmbed server

    private boolean active;
    private boolean applicationActive;

    XEmbedClientHelper() {
        super();
    }

    void setClient(XEmbeddedFramePeer client) {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
            xembedLog.fine("XEmbed client: " + client);
        }
        if (embedded != null) {
            XToolkit.removeEventDispatcher(embedded.getWindow(), this);
            active = false;
        }
        embedded = client;
        if (embedded != null) {
            XToolkit.addEventDispatcher(embedded.getWindow(), this);
        }
    }

    void install() {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
            xembedLog.fine("Installing xembedder on " + embedded);
        }
        long[] info = new long[] { XEMBED_VERSION, XEMBED_MAPPED };
        long data = Native.card32ToData(info);
        try {
            XEmbedInfo.setAtomData(embedded.getWindow(), data, 2);
        } finally {
            unsafe.freeMemory(data);
        }
        // XEmbeddedFrame is initially created with a null parent..
        // Here it is reparented to the proper parent window.
        long parentWindow = embedded.getParentWindowHandle();
        if (parentWindow != 0) {
            XToolkit.awtLock();
            try {
                XlibWrapper.XReparentWindow(XToolkit.getDisplay(),
                                            embedded.getWindow(),
                                            parentWindow,
                                            0, 0);
            } finally {
                XToolkit.awtUnlock();
            }
        }
    }

    void handleClientMessage(XEvent xev) {
        XClientMessageEvent msg = xev.get_xclient();
        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
            xembedLog.fine(msg.toString());
        }
        if (msg.get_message_type() == XEmbed.getAtom()) {
            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                xembedLog.fine("Embedded message: " + msgidToString((int)msg.get_data(1)));
            }
            switch ((int)msg.get_data(1)) {
              case XEMBED_EMBEDDED_NOTIFY: // Notification about embedding protocol start
                  active = true;
                  server = getEmbedder(embedded, msg);
                  // Check if window is reparented. If not - it was created with
                  // parent and so we should update it here.
                  if (!embedded.isReparented()) {
                      embedded.setReparented(true);
                      embedded.updateSizeHints();
                  }
                  embedded.notifyStarted();
                  break;
              case XEMBED_WINDOW_ACTIVATE:
                  applicationActive = true;
                  break;
              case XEMBED_WINDOW_DEACTIVATE:
                  if (applicationActive) {
                      applicationActive = false;
                      handleWindowFocusOut();
                  }
                  break;
              case XEMBED_FOCUS_IN: // We got focus!
                  // Check for direction
                  handleFocusIn((int)msg.get_data(2));
                  break;
              case XEMBED_FOCUS_OUT:
                  if (applicationActive) {
                      handleWindowFocusOut();
                  }
                  break;
            }
        }
    }
    void handleFocusIn(int detail) {
        if (embedded.focusAllowedFor()) {
            embedded.handleWindowFocusIn(0);
        }
        switch(detail) {
          case XEMBED_FOCUS_CURRENT:
              // Do nothing - just restore to the current value
              break;
          case XEMBED_FOCUS_FIRST:
              SunToolkit.executeOnEventHandlerThread(embedded.target, new Runnable() {
                      public void run() {
                          Component comp = ((Container)embedded.target).getFocusTraversalPolicy().getFirstComponent((Container)embedded.target);
                          if (comp != null) {
                              comp.requestFocusInWindow();
                          }
                      }});
              break;
          case XEMBED_FOCUS_LAST:
              SunToolkit.executeOnEventHandlerThread(embedded.target, new Runnable() {
                      public void run() {
                          Component comp = ((Container)embedded.target).getFocusTraversalPolicy().getLastComponent((Container)embedded.target);
                          if (comp != null) {
                              comp.requestFocusInWindow();
                          }
                      }});
              break;
        }
    }

    @Override
    public void dispatchEvent(XEvent xev) {
        switch(xev.get_type()) {
          case XConstants.ClientMessage:
              handleClientMessage(xev);
              break;
          case XConstants.ReparentNotify:
              handleReparentNotify(xev);
              break;
        }
    }
    public void handleReparentNotify(XEvent xev) {
        XReparentEvent re = xev.get_xreparent();
        long newParent = re.get_parent();
        if (active) {
            // unregister accelerators, etc. for old parent
            embedded.notifyStopped();
            // check if newParent is a root window
            X11GraphicsConfig gc = (X11GraphicsConfig)embedded.getGraphicsConfiguration();
            X11GraphicsDevice gd = gc.getDevice();
            if ((newParent == XlibUtil.getRootWindow(gd.getScreen())) ||
                (newParent == XToolkit.getDefaultRootWindow()))
            {
                // reparenting to root means XEmbed termination
                active = false;
            } else {
                // continue XEmbed with a new parent
                server = newParent;
                embedded.notifyStarted();
            }
        }
    }
    boolean requestFocus() {
        if (active && embedded.focusAllowedFor()) {
            sendMessage(server, XEMBED_REQUEST_FOCUS);
            return true;
        }
        return false;
    }
    void handleWindowFocusOut() {
        // fix for 6269309: it is possible that we call this method twice
        // (for example, when receiving XEMBED_WINDOW_DEACTIVATE and then
        // XEMBED_FOCUS_OUT client messages), so we first need to check if
        // embedded is an active window before sending WINDOW_LOST_FOCUS
        // to shared code
        if (XKeyboardFocusManagerPeer.getInstance().getCurrentFocusedWindow() == embedded.target) {
            embedded.handleWindowFocusOut(null, 0);
        }
    }

    long getEmbedder(XWindowPeer embedded, XClientMessageEvent info) {
        // Embedder is the parent of embedded.
        return XlibUtil.getParentWindow(embedded.getWindow());
    }

    boolean isApplicationActive() {
        return applicationActive;
    }

    boolean isActive() {
        return active;
    }

    void traverseOutForward() {
        if (active) {
            sendMessage(server, XEMBED_FOCUS_NEXT);
        }
    }

    void traverseOutBackward() {
        if (active) {
            sendMessage(server, XEMBED_FOCUS_PREV);
        }
    }

    void registerAccelerator(AWTKeyStroke stroke, int id) {
        if (active) {
            long sym = getX11KeySym(stroke);
            long mods = getX11Mods(stroke);
            sendMessage(server, XEMBED_REGISTER_ACCELERATOR, id, sym, mods);
        }
    }
    void unregisterAccelerator(int id) {
        if (active) {
            sendMessage(server, XEMBED_UNREGISTER_ACCELERATOR, id, 0, 0);
        }
    }

    long getX11KeySym(AWTKeyStroke stroke) {
        XToolkit.awtLock();
        try {
            return XWindow.getKeySymForAWTKeyCode(stroke.getKeyCode());
        } finally {
            XToolkit.awtUnlock();
        }
    }

    long getX11Mods(AWTKeyStroke stroke) {
        return XWindow.getXModifiers(stroke);
    }
}
