/*
 * Copyright (c) 1995, 2007, 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.motif;

import java.util.Vector;
import java.awt.*;
import java.awt.peer.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.ImageObserver;
import sun.awt.image.ImageRepresentation;
import sun.awt.motif.MInputMethod;
import sun.awt.motif.MInputMethodControl;
import sun.awt.im.*;
import sun.awt.DisplayChangedListener;
import sun.awt.SunToolkit;
import sun.awt.X11GraphicsDevice;

class MWindowPeer extends MPanelPeer implements WindowPeer,
DisplayChangedListener {

    Insets insets = new Insets( 0, 0, 0, 0 );
    MWindowAttributes winAttr;
    static Vector allWindows = new Vector();
    int         iconWidth  = -1;
    int         iconHeight = -1;

    int dropTargetCount = 0;
    boolean alwaysOnTop;

    native void pCreate(MComponentPeer parent, String targetClassName, boolean isFocusableWindow);
    native void pShow();
    native void pToFront();
    native void pShowModal(boolean isModal);
    native void pHide();
    native void pReshape(int x, int y, int width, int height);
    native void pDispose();
    native void pSetTitle(String title);
    public native void setState(int state);
    public native int getState();

    public native void setResizable(boolean resizable);
    native void addTextComponentNative(MComponentPeer tc);
    native void removeTextComponentNative();
    native void pSetIMMOption(String option);
    native void pSetMenuBar(MMenuBarPeer mbpeer);
    native void setSaveUnder(boolean state);

    native void registerX11DropTarget(Component target);
    native void unregisterX11DropTarget(Component target);
    native void updateAlwaysOnTop(boolean isAlwaysOnTop);

    private static native void initIDs();

    static {
        initIDs();
    }

    // this function is privileged! do not change it to public!
    private static int getInset(final String name, final int def) {
        Integer tmp = (Integer) java.security.AccessController.doPrivileged(
            new sun.security.action.GetIntegerAction(name, def));
        return tmp.intValue();
    }

    MWindowPeer() {
        insets = new Insets(0,0,0,0);
        winAttr = new MWindowAttributes();
    }

    MWindowPeer(Window target) {

        this();
        init(target);

        allWindows.addElement(this);
    }

    void create(MComponentPeer parent) {
        pCreate(parent, target.getClass().getName(), ((Window)target).isFocusableWindow());
    }

    void init( Window target ) {
        if ( winAttr.nativeDecor == true ) {
            insets.top = getInset("awt.frame.topInset", -1);
            insets.left = getInset("awt.frame.leftInset", -1);
            insets.bottom = getInset("awt.frame.bottomInset", -1);
            insets.right = getInset("awt.frame.rightInset", -1);
        }

        Rectangle bounds = target.getBounds();
        sysX = bounds.x;
        sysY = bounds.y;
        sysW = bounds.width;
        sysH = bounds.height;

        super.init(target);
        InputMethodManager imm = InputMethodManager.getInstance();
        String menuString = imm.getTriggerMenuString();
        if (menuString != null)
        {
            pSetIMMOption(menuString);
        }
        pSetTitle(winAttr.title);

        /*
         * For Windows and undecorated Frames and Dialogs this just
         * disables/enables resizing functions in the system menu.
         */
        setResizable(winAttr.isResizable);

        setSaveUnder(true);

        Font f = target.getFont();
        if (f == null) {
            f = defaultFont;
            target.setFont(f);
            setFont(f);
        }
        Color c = target.getBackground();
        if (c == null) {
            target.setBackground(SystemColor.window);
            setBackground(SystemColor.window);
        }
        c = target.getForeground();
        if (c == null) {
            target.setForeground(SystemColor.windowText);
            setForeground(SystemColor.windowText);
        }
        alwaysOnTop = ((Window)target).isAlwaysOnTop() && ((Window)target).isAlwaysOnTopSupported();

        GraphicsConfiguration gc = getGraphicsConfiguration();
        ((X11GraphicsDevice)gc.getDevice()).addDisplayChangedListener(this);

    }

    /* Support for multiple icons is not implemented in MAWT */
    public void updateIconImages() {
        if (this instanceof MFramePeer) {
            ((MFramePeer)this).setIconImage(((Frame)target).getIconImage());
        }
    }


    /* Not implemented in MAWT */
    public void updateMinimumSize() {
    }

    protected void disposeImpl() {
        allWindows.removeElement(this);
        super.disposeImpl();
    }

    public native void toBack();

    public void setAlwaysOnTop(boolean alwaysOnTop) {
        this.alwaysOnTop = alwaysOnTop;
        updateAlwaysOnTop(alwaysOnTop);
    }

    public void updateAlwaysOnTopState() {
        setAlwaysOnTop(((Window)target).isAlwaysOnTop());
    }

    public void toFront() {
        if (target.isVisible()) {
            updateFocusableWindowState();
            pToFront();
        }
    }

    public void updateFocusableWindowState() {
        setFocusableWindow(((Window)target).isFocusableWindow());
    }
    native void setFocusableWindow(boolean value);

    public void setVisible( boolean b ) {
        if (b) {
            updateFocusableWindowState();
        }
        super.setVisible(b);
        updateAlwaysOnTop(alwaysOnTop);
    }

    public Insets getInsets() {
        return insets;
    }

    public void handleQuit() {
        postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_CLOSING));
    }

    // XXX: nasty WM, foul play.  spank WM author.
    public void handleDestroy() {
        final Window target = (Window)this.target;
        SunToolkit.executeOnEventHandlerThread(target,
                                               new Runnable() {
                                                   public void run() {
                                                       // This seems like the only reasonable thing we
                                                       // could do in this situation as the native window
                                                       // is already dead.
                                                       target.dispose();
                                                   }
                                               });
    }


    // NOTE: This method may be called by privileged threads.
    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
    public void handleIconify() {
        postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_ICONIFIED));
    }

    // NOTE: This method may be called by privileged threads.
    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
    public void handleDeiconify() {
        postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_DEICONIFIED));
    }

    // NOTE: This method may be called by privileged threads.
    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
    public void handleStateChange(int oldState, int newState) {
        postEvent(new WindowEvent((Window)target,
                                  WindowEvent.WINDOW_STATE_CHANGED,
                                  oldState, newState));
    }

    /**
     * Called to inform the Window that its size has changed and it
     * should layout its children.
     */
    // NOTE: This method may be called by privileged threads.
    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
    public void handleResize(int width, int height) {
        sysW = width;
        sysH = height;

        // REMIND: Is this secure? Can client code subclass input method?
        if (!tcList.isEmpty() &&
            !imList.isEmpty()){
            int i;
            for (i = 0; i < imList.size(); i++){
                ((MInputMethod)imList.elementAt(i)).configureStatus();
            }
        }
        validateSurface(width, height);
        postEvent(new ComponentEvent(target, ComponentEvent.COMPONENT_RESIZED));
    }


    /**
     * DEPRECATED:  Replaced by getInsets().
     */
    public Insets insets() {
        return getInsets();
    }

    public void handleMoved(int x, int y) {
        sysX = x;
        sysY = y;
        postEvent(new ComponentEvent(target, ComponentEvent.COMPONENT_MOVED));
    }

    private native AWTEvent wrapInSequenced(AWTEvent event);

    // NOTE: This method may be called by privileged threads.
    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
    public void handleWindowFocusIn() {
        WindowEvent we = new WindowEvent((Window)target, WindowEvent.WINDOW_GAINED_FOCUS);
        /* wrap in Sequenced, then post*/
        postEvent(wrapInSequenced((AWTEvent) we));
    }

    // NOTE: This method may be called by privileged threads.
    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
    public void handleWindowFocusOut(Window oppositeWindow) {
        WindowEvent we = new WindowEvent((Window)target, WindowEvent.WINDOW_LOST_FOCUS,
                                         oppositeWindow);
        /* wrap in Sequenced, then post*/
        postEvent(wrapInSequenced((AWTEvent) we));
    }


// relocation of Imm stuff
    private Vector imList = new Vector();
    private Vector tcList = new Vector();

    // NOTE: This method is called by privileged threads.
    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
    void notifyIMMOptionChange(){

        // REMIND: IS THIS SECURE??? CAN USER CODE SUBCLASS INPUTMETHODMGR???
        InputMethodManager.getInstance().notifyChangeRequest(target);
    }

    public void addInputMethod(MInputMethod im) {
        if (!imList.contains(im))
            imList.addElement(im);
    }

    public void removeInputMethod(MInputMethod im) {
        if (imList.contains(im))
            imList.removeElement(im);
    }

    public void addTextComponent(MComponentPeer tc) {
        if (tcList.contains(tc))
            return;
        if (tcList.isEmpty()){
            addTextComponentNative(tc);
            if (!imList.isEmpty()) {
                for (int i = 0; i < imList.size(); i++) {
                    ((MInputMethod)imList.elementAt(i)).reconfigureXIC((MInputMethodControl)this);
                }
            }
            MToolkit.executeOnEventHandlerThread(target, new Runnable() {
                    public void run() {
                        synchronized(target.getTreeLock()) {
                            target.doLayout();
                        }
                    }
                });
        }
        tcList.addElement(tc);

    }

    public void removeTextComponent(MComponentPeer tc) {
        if (!tcList.contains(tc))
            return;
        tcList.removeElement(tc);
        if (tcList.isEmpty()){
            removeTextComponentNative();
            if (!imList.isEmpty()) {
                for (int i = 0; i < imList.size(); i++) {
                    ((MInputMethod)imList.elementAt(i)).reconfigureXIC((MInputMethodControl)this);
                }
            }
            target.doLayout();
        }
    }

    public MComponentPeer getTextComponent() {
        if (!tcList.isEmpty()) {
            return (MComponentPeer)tcList.firstElement();
        } else {
            return null;
        }
    }

    boolean hasDecorations(int decor) {
        if (!winAttr.nativeDecor) {
            return false;
        }
        else {
            int myDecor = winAttr.decorations;
            boolean hasBits = ((myDecor & decor) == decor);
            if ((myDecor & MWindowAttributes.AWT_DECOR_ALL) != 0)
                return !hasBits;
            else
                return hasBits;
        }
    }

    /* Returns the native paint should be posted after setting new size
     */
    public boolean checkNativePaintOnSetBounds(int width, int height) {
        // Fix for 4418155. Window does not repaint
        // automticaly if shrinking. Should not wait for Expose
        return (width > oldWidth) || (height > oldHeight);
    }

/* --- DisplayChangedListener Stuff --- */

    native void resetTargetGC(Component target);

    /* Xinerama
     * called to update our GC when dragged onto another screen
     */
    public void draggedToNewScreen(int screenNum) {
        final int finalScreenNum = screenNum;

        SunToolkit.executeOnEventHandlerThread((Component)target, new Runnable()
            {
                public void run() {
                    displayChanged(finalScreenNum);
                }
            });
    }

    /* Xinerama
     * called to update our GC when dragged onto another screen
     */
    public void displayChanged(int screenNum) {
        // update our GC
        resetLocalGC(screenNum);         /* upcall to MCanvasPeer */
        resetTargetGC(target);           /* call Window.resetGC() via native */

        //propagate to children
        super.displayChanged(screenNum); /* upcall to MPanelPeer */
    }

    /**
     * Helper method that executes the displayChanged(screen) method on
     * the event dispatch thread.  This method is used in the Xinerama case
     * and after display mode change events.
     */
    private void executeDisplayChangedOnEDT(int screenNum) {
        final int finalScreenNum = screenNum;
        Runnable dc = new Runnable() {
            public void run() {
                displayChanged(finalScreenNum);
            }
        };
        SunToolkit.executeOnEventHandlerThread((Component)target, dc);
    }

    /**
     * From the DisplayChangedListener interface; called from
     * X11GraphicsDevice when the display mode has been changed.
     */
    public void displayChanged() {
        GraphicsConfiguration gc = getGraphicsConfiguration();
        int curScreenNum = ((X11GraphicsDevice)gc.getDevice()).getScreen();
        executeDisplayChangedOnEDT(curScreenNum);
    }

    /**
     * From the DisplayChangedListener interface; top-levels do not need
     * to react to this event.
     */
    public void paletteChanged() {
    }

    public synchronized void addDropTarget() {
        if (dropTargetCount == 0) {
            registerX11DropTarget(target);
        }
        dropTargetCount++;
    }

    public synchronized void removeDropTarget() {
        dropTargetCount--;
        if (dropTargetCount == 0) {
            unregisterX11DropTarget(target);
        }
    }

    protected synchronized void updateDropTarget() {
        if (dropTargetCount > 0) {
            unregisterX11DropTarget(target);
            registerX11DropTarget(target);
        }
    }

    public boolean requestWindowFocus() {
        return false;
    }

    public void setModalBlocked(Dialog blocker, boolean blocked) {
        // do nothing
    }

    public void postUngrabEvent() {
        postEvent(new sun.awt.UngrabEvent((Window)target));
    }

    boolean isOwnerOf(MComponentPeer child) {
        if (child == null) return false;

        Component comp = child.target;
        while (comp != null && !(comp instanceof Window)) {
            comp = getParent_NoClientCode(comp);
        }
        if (!(comp instanceof Window)) {
            return false;
        }

        while (comp != null && !(comp == target) && !(comp instanceof Dialog)) {
            comp = getParent_NoClientCode(comp);
        }
        return (comp == target);
    }

    boolean processUngrabMouseEvent(MComponentPeer compPeer, int x_root, int y_root, int type) {
        switch (type) {
          case 4: // ButtonPress
              // Check that the target is the child of the grabbed
              // window or the child of one of the owned windows of
              // the grabbed window
              if (!isOwnerOf(compPeer)) {
                  postUngrabEvent();
                  return true;
              }
        }
        return false;
    }

    private final boolean hasWarningWindow() {
        return ((Window)target).getWarningString() != null;
    }

    // This method is overriden at Dialog and Frame peers.
    boolean isTargetUndecorated() {
        return true;
    }

    private volatile int sysX = 0;
    private volatile int sysY = 0;
    private volatile int sysW = 0;
    private volatile int sysH = 0;

    Rectangle constrainBounds(int x, int y, int width, int height) {
        // We don't restrict the setBounds() operation if the code is trusted.
        if (!hasWarningWindow()) {
            return new Rectangle(x, y, width, height);
        }

        int newX = x;
        int newY = y;
        int newW = width;
        int newH = height;

        GraphicsConfiguration gc = ((Window)target).getGraphicsConfiguration();
        Rectangle sB = gc.getBounds();
        Insets sIn = ((Window)target).getToolkit().getScreenInsets(gc);

        int screenW = sB.width - sIn.left - sIn.right;
        int screenH = sB.height - sIn.top - sIn.bottom;

        // If it's undecorated or is not currently visible,
        // then check each point is within the visible part of the screen
        if (!target.isVisible() || isTargetUndecorated()) {
            int screenX = sB.x + sIn.left;
            int screenY = sB.y + sIn.top;

            // First make sure the size is withing the visible part of the screen
            if (newW > screenW) {
                newW = screenW;
            }

            if (newH > screenH) {
                newH = screenH;
            }

            // Tweak the location if needed
            if (newX < screenX) {
                newX = screenX;
            } else if (newX + newW > screenX + screenW) {
                newX = screenX + screenW - newW;
            }

            if (newY < screenY) {
                newY = screenY;
            } else if (newY + newH > screenY + screenH) {
                newY = screenY + screenH - newH;
            }
        } else {
            int maxW = Math.max(screenW, sysW);
            int maxH = Math.max(screenH, sysH);

            // Make sure the size is withing the visible part of the screen
            // OR is less that the current size of the window.
            if (newW > maxW) {
                newW = maxW;
            }

            if (newH > maxH) {
                newH = maxH;
            }
        }

        return new Rectangle(newX, newY, newW, newH);
    }

    public void setBounds(int x, int y, int width, int height, int op) {
        Rectangle newBounds = constrainBounds(x, y, width, height);
        super.setBounds(newBounds.x, newBounds.y, newBounds.width, newBounds.height, op);
    }

    @Override
    public void setOpacity(float opacity) {
	// not implemented
    }

    @Override
    public void setOpaque(boolean isOpaque) {
        // no-op
    }

    @Override
    public void updateWindow(BufferedImage backBuffer) {
        // no-op
    }

    public void repositionSecurityWarning() {
	// not implemented
    }

}
