/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * @author Dmitry A. Durnev, Pavel Dolgov
 * @version $Revision$
 */
package org.apache.harmony.awt.wtk.linux;

import java.awt.Frame;
import java.awt.Insets;

import org.apache.harmony.awt.wtk.CreationParams;

import org.apache.harmony.awt.nativebridge.CLongPointer;
import org.apache.harmony.awt.nativebridge.Int32Pointer;
import org.apache.harmony.awt.nativebridge.Int8Pointer;
import org.apache.harmony.awt.nativebridge.NativeBridge;
import org.apache.harmony.awt.nativebridge.PointerPointer;
import org.apache.harmony.awt.nativebridge.VoidPointer;
import org.apache.harmony.awt.nativebridge.linux.X11;
import org.apache.harmony.awt.nativebridge.linux.X11Defs;



/**
 * Window manager specific actions
 *
 */
class WindowManager {

    private static final X11 x11 = X11.getInstance();
    private static final NativeBridge bridge = NativeBridge.getInstance();

    private final LinuxWindowFactory factory;
    private final long display;

    //atom which defines window property type "ATOM"
    private final long XA_ATOM;
    private final long XA_CARDINAL;


    // WM hints
    public final long WM_PROTOCOLS;
    public final long WM_DELETE_WINDOW;
    public final long WM_TAKE_FOCUS;

    // _NET extended hints
    private final long NET_WM_WINDOW_TYPE;
    private final long NET_WM_WINDOW_TYPE_DIALOG;
    private final long NET_WM_WINDOW_TYPE_NORMAL;
    private final long NET_REQUEST_FRAME_EXTENTS;
    public final long NET_WM_STATE;
    public final long NET_WM_STATE_HIDDEN;
    public final long NET_WM_STATE_MAXIMIZED_VERT;
    public final long NET_WM_STATE_MAXIMIZED_HORZ;
    public final long NET_WM_STATE_ABOVE;
    public final long NET_WM_STATE_STAYS_ON_TOP; // same as ABOVE, only for KDE
    public final long NET_FRAME_EXTENTS;

    // KDE specific hints
    private final long KDE_NET_WM_WINDOW_TYPE_OVERRIDE;
    public final long KDE_NET_WM_FRAME_STRUT;

    // Motif hints (for Gnome)
    private final long XA_MOTIF_WM_HINTS;

    private long focusedWindow;

    WindowManager(LinuxWindowFactory factory) {
        this.factory = factory;
        display = factory.getDisplay();


        XA_ATOM = internAtom("ATOM"); //$NON-NLS-1$
        XA_CARDINAL = internAtom("CARDINAL"); //$NON-NLS-1$

        // WM hints
        WM_PROTOCOLS = internAtom("WM_PROTOCOLS"); //$NON-NLS-1$
        WM_DELETE_WINDOW = internAtom("WM_DELETE_WINDOW"); //$NON-NLS-1$
        WM_TAKE_FOCUS = internAtom("WM_TAKE_FOCUS"); //$NON-NLS-1$

        // _NET extended hints
        NET_WM_WINDOW_TYPE = internAtom("_NET_WM_WINDOW_TYPE"); //$NON-NLS-1$
        NET_WM_WINDOW_TYPE_DIALOG = internAtom("_NET_WM_WINDOW_TYPE_DIALOG"); //$NON-NLS-1$
        NET_WM_WINDOW_TYPE_NORMAL = internAtom("_NET_WM_WINDOW_TYPE_NORMAL"); //$NON-NLS-1$
        NET_REQUEST_FRAME_EXTENTS = internAtom("_NET_REQUEST_FRAME_EXTENTS"); //$NON-NLS-1$
        NET_WM_STATE = internAtom("_NET_WM_STATE"); //$NON-NLS-1$
        NET_WM_STATE_HIDDEN = internAtom("_NET_WM_STATE_HIDDEN"); //$NON-NLS-1$
        NET_WM_STATE_MAXIMIZED_VERT = internAtom("_NET_WM_STATE_MAXIMIZED_VERT"); //$NON-NLS-1$
        NET_WM_STATE_MAXIMIZED_HORZ = internAtom("_NET_WM_STATE_MAXIMIZED_HORZ"); //$NON-NLS-1$
        NET_WM_STATE_ABOVE = internAtom("_NET_WM_STATE_ABOVE"); //$NON-NLS-1$
        NET_WM_STATE_STAYS_ON_TOP = internAtom("_NET_WM_STATE_STAYS_ON_TOP"); //$NON-NLS-1$
        NET_FRAME_EXTENTS = internAtom("_NET_FRAME_EXTENTS"); //$NON-NLS-1$

        // KDE specific hints
        KDE_NET_WM_WINDOW_TYPE_OVERRIDE = internAtom("_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); //$NON-NLS-1$
        KDE_NET_WM_FRAME_STRUT = internAtom("_KDE_NET_WM_FRAME_STRUT"); //$NON-NLS-1$

        // Motif hints (for Gnome)
        XA_MOTIF_WM_HINTS = internAtom("_MOTIF_WM_HINTS"); //$NON-NLS-1$
    }

    long internAtom(String atomName) {
        return factory.internAtom(atomName);
    }

    String getAtomName(int atom) {
        return factory.getAtomName(atom);
    }

    void changeWindowProperty(long winId, long property,
                              long type, long values[]) {
        int count = values.length;
        Int8Pointer dataPtr = null;

        if (count > 0) {
            CLongPointer data = bridge.createCLongPointer(count, false);
            data.set(values, 0, count);
            dataPtr = bridge.createInt8Pointer(data);
        }

        x11.XChangeProperty(display, winId, property, type, 32,
                X11Defs.PropModeReplace, dataPtr, count);
    }

    void setDecorType(long winId, int decorType, boolean undecorated) {

        long styles[] = null;
        if(undecorated || decorType == CreationParams.DECOR_TYPE_UNDECOR) {
            styles = new long[] {
                    KDE_NET_WM_WINDOW_TYPE_OVERRIDE,
                    NET_WM_WINDOW_TYPE_NORMAL };

        } else if (decorType == CreationParams.DECOR_TYPE_FRAME) {
            styles = new long[] { NET_WM_WINDOW_TYPE_NORMAL };

        } else if (decorType == CreationParams.DECOR_TYPE_DIALOG) {
            styles = new long[] { NET_WM_WINDOW_TYPE_DIALOG };
        }

        if (styles != null) {
            changeWindowProperty(winId, NET_WM_WINDOW_TYPE, XA_ATOM, styles);
        }

        if (undecorated || decorType == CreationParams.DECOR_TYPE_UNDECOR) {
            long hint[] = new long[] { 2, 0, 0, 0, 0 };
            changeWindowProperty(winId, XA_MOTIF_WM_HINTS, XA_MOTIF_WM_HINTS, hint);
        }
    }

    /**
     * Set non-geometry WM Hints, such as keyboard input,
     * window group leader, icon, etc.
     */
    void setWMHints(int winId, int ownerID) {
        long wmHintsPtr = x11.XAllocWMHints();
        X11.XWMHints wmHints = x11.createXWMHints(wmHintsPtr);
        int flags = X11Defs.InputHint;
        wmHints.set_input(1);
        if (ownerID != 0) {
            flags |= X11Defs.WindowGroupHint;
            wmHints.set_window_group(ownerID);//set group leader ID to owner
        }
        wmHints.set_flags(flags);
        x11.XSetWMHints(display, winId, wmHintsPtr);
        x11.XFree(wmHintsPtr);
    }

    void setWMProtocols(long windowID) {
        CLongPointer protocols = bridge.createCLongPointer(2, false);
        protocols.set(0, WM_DELETE_WINDOW);
        protocols.set(1, WM_TAKE_FOCUS);
        x11.XSetWMProtocols(display, windowID, protocols, 2);
    }

    void setInputAllowed(long winId, boolean allow) {
        long wmHintsPtr = x11.XGetWMHints(display, winId);
        if (wmHintsPtr == 0) {
            wmHintsPtr = x11.XAllocWMHints();
        }
        X11.XWMHints wmHints = x11.createXWMHints(wmHintsPtr);
        wmHints.set_input(allow ? 1 : 0);
        wmHints.set_flags(wmHints.get_flags() | X11Defs.InputHint);
        x11.XSetWMHints(display, winId, wmHintsPtr);

        x11.XFree(wmHintsPtr);
        x11.XFlush(display);
    }

    boolean isInputAllowed(long winId) {
        long wmHintsPtr = x11.XGetWMHints(display, winId);
        if (wmHintsPtr == 0) {
            return true;
        }
        X11.XWMHints wmHints = x11.createXWMHints(wmHintsPtr);
        long flags = wmHints.get_flags();
        int input = wmHints.get_input();
        x11.XFree(wmHintsPtr);
        if ((flags & X11Defs.InputHint) == 0) {
            return true;
        }
        return input != 0;
    }

    /**
     * @return
     */
    long getInputFocus() {
        CLongPointer window = bridge.createCLongPointer(1, false);
        Int32Pointer revertStatus = bridge.createInt32Pointer(1, false);
        x11.XGetInputFocus(display, window, revertStatus);
        return window.get(0);
    }

    long getFocusedWindow() {
        return focusedWindow;
    }

    void setFocusedWindow(long windowId) {
        focusedWindow = windowId;
    }

    /**
     *
     * @param winId - the window ID
     * @param resizable - the window should be resizable
     * @param width - width to set if the window isn't resizable
     * @param height - height to set if the window isn't resizable
     */
    void setResizableHint(long winId, boolean resizable, int x, int y,
                          int width, int height) {
        setResizableHint(winId, resizable, false, x, y, width, height);
    }

    void setResizableHint(long winId, boolean resizable, boolean zoomed,
                          int x, int y, int width, int height) {
        final int MAX = Integer.MAX_VALUE;
        boolean setMaxSize = (!resizable ||
                              (zoomed && (width < MAX) && (height < MAX)));
        int flags = X11Defs.PMinSize | X11Defs.PSize | X11Defs.PPosition |
         X11Defs.USSize | X11Defs.USPosition;
        long sizeHintsPtr = x11.XAllocSizeHints();
        X11.XSizeHints sizeHints = x11.createXSizeHints(sizeHintsPtr);
        sizeHints.set_flags(flags);

        //save old hints settings
        x11.XGetWMNormalHints(display, winId, sizeHintsPtr, sizeHintsPtr);
        //sizeHints = x11.new XSizeHints(sizeHintsPtr);
        if (!zoomed) {
            sizeHints.set_min_height(!resizable ? height
                                               : LinuxWindow.MIN_WINDOW_HEIGHT);
            sizeHints.set_min_width(!resizable ? width
                                              : LinuxWindow.MIN_WINDOW_WIDTH);

            // Dummy values for obsolete fields
            sizeHints.set_x(x);
            sizeHints.set_y(y);
            sizeHints.set_width(width);
            sizeHints.set_height(height);
        }

        if (setMaxSize) {
            flags |= X11Defs.PMaxSize;
            sizeHints.set_max_height(height);
            sizeHints.set_max_width(width);
        }

        sizeHints.set_flags(flags);
        x11.XSetWMNormalHints(display, winId, sizeHintsPtr);
        x11.XFree(sizeHintsPtr);

    }
    /** register a window to receive notifications when WM frame extents
     * (i. e. native insets) change
     * @param winId - id of a window which wants to receive WM notifications
     */
    void requestFrameExtents(long winId) {
        X11.XEvent event =
            createClientMessage(winId, NET_REQUEST_FRAME_EXTENTS);
        sendClientMessage(event);
    }

    int sendClientMessage(X11.XEvent event) {
        int mask = (X11Defs.SubstructureNotifyMask |
                    X11Defs.SubstructureRedirectMask);
        int status = x11.XSendEvent(display, factory.getRootWindow(),
                                    X11Defs.False, mask, event);
        return status;
    }

    X11.XEvent createClientMessage(long winId, long msgType) {
        X11.XEvent retEvent = x11.createXEvent(false);
        X11.XClientMessageEvent cme = retEvent.get_xclient();
        cme.set_display(display);
        cme.set_window(winId);
        cme.set_message_type(msgType);
        cme.set_type(X11Defs.ClientMessage);
        cme.set_format(32);
        return retEvent;
    }

    int changeWindowState(LinuxWindow wnd, int action, long[] properties) {
        int mapState = wnd.getMapState();
        long winId = wnd.getId();
        if (mapState == X11Defs.IsUnmapped) {

            long[] props = getStateProps(wnd);
            if (props != null) {
                changeWindowProperty(winId, NET_WM_STATE, XA_ATOM, props);
            }
            return 0;
        }
        X11.XEvent clientEvent = createClientMessage(winId, NET_WM_STATE);
        CLongPointer data = clientEvent.get_xclient().get_l();
        data.set(0, action);  // add/remove/toggle
        for (int i = 0; i < properties.length; i++) {
            data.set(i + 1, properties[i]);
        }
        return sendClientMessage(clientEvent);
    }


    /**
     * @param wnd
     * @return
     */
    private long[] getStateProps(LinuxWindow wnd) {
        long props[] = new long[]{0l, 0l, 0l, 0l};
        int state = wnd.getCurrentState();
        int k = 0;
        if ((state & Frame.MAXIMIZED_HORIZ) != 0) {
            props[k++] = NET_WM_STATE_MAXIMIZED_HORZ;
        }
        if ((state & Frame.MAXIMIZED_VERT) != 0) {
            props[k++] = NET_WM_STATE_MAXIMIZED_VERT;
        }
        if ((state & Frame.ICONIFIED) != 0) {
            props[k++] = NET_WM_STATE_HIDDEN;
        }
        if (wnd.alwaysOnTop) {
            props[k++] = NET_WM_STATE_ABOVE;
        }
        long[] ret = new long[k];
        System.arraycopy(props, 0, ret, 0, k);
        return ret;
    }

    long[] getSupportedHints() {
        final long NET_SUPPORTED = internAtom("_NET_SUPPORTED"); //$NON-NLS-1$
        long root = factory.getRootWindow();
        long [] supportedHints = getWindowProperty(root, NET_SUPPORTED);
        return supportedHints;
    }

    private long[] getWindowProperty(long winId, final long propertyAtom) {
        CLongPointer type = bridge.createCLongPointer(1, false);
        Int32Pointer formatPtr = bridge.createInt32Pointer(1, false);
        CLongPointer nItemsPtr = bridge.createCLongPointer(1, false);
        CLongPointer bytesRemaining = bridge.createCLongPointer(1, false);
        PointerPointer data = bridge.createPointerPointer(1, false);

        final int anyType = X11Defs.AnyPropertyType;
        x11.XGetWindowProperty(display, winId, propertyAtom, 0, 1,
                               anyType, X11Defs.False, type,
                               formatPtr, nItemsPtr,
                               bytesRemaining, data);
        VoidPointer dataPtr = data.get(0);
        if (dataPtr == null) {
            return null;
        }
        x11.XFree(dataPtr);
        long nBytes = bytesRemaining.get(0);
        long typeAtom = type.get(0);
        if (typeAtom == X11Defs.None) {
            // the property doesn't exist
            return null;
        }
        int bitFormat = formatPtr.get(0);
        long nItems = (nBytes + 4) * 8 / bitFormat;
        long n32bitItems = nItems / (32 / bitFormat);
        x11.XGetWindowProperty(display, winId, propertyAtom, 0,
                               n32bitItems, anyType, X11Defs.False,
                               type, formatPtr, nItemsPtr,
                               bytesRemaining, data);

        nBytes = bytesRemaining.get(0);
        assert nBytes == 0;
        //read the data:
        int itemSize = bitFormat / 8;
        CLongPointer dataArray = bridge.createCLongPointer(data.get(0));
        long[] props = new long [(int)nItems];
        for (int i = 0; i < nItems; i++) {
            int item = 0;
            if (itemSize == 4) {
                item = (int)dataArray.get(i);
            } else if (itemSize == 2) {
                item = (short)dataArray.get(i);
            } else if (itemSize == 1) {
                item = (byte)dataArray.get(i);
            }
            props[i] = item;
        }
        x11.XFree(dataPtr);

        return props;
    }

    /**
     * @return array of child window id's
     * (for a top-level this maybe a WM frame window)
     */
    long[] getChildrenIDs(long windowID) {
        if (windowID == 0) {
            return new long[0];
        }

        CLongPointer root = bridge.createCLongPointer(1, false);
        CLongPointer parent = bridge.createCLongPointer(1, false);
        PointerPointer childrenArray = bridge.createPointerPointer(1, false);
        Int32Pointer childrenCount = bridge.createInt32Pointer(1, false);;
        x11.XQueryTree(display, windowID, root, parent,
                       childrenArray, childrenCount);

        int count = childrenCount.get(0);
        CLongPointer children = bridge.createCLongPointer(childrenArray.get(0));
        if (children == null) {
            return new long[0];
        }
        long[] result = new long[count];
        children.get(result, 0, count);
        x11.XFree(children);
        return result;
    }

    int setIcon(long windowID, long pixmap, int mask) {
        // save old WM hints
        long wmHintsPtr = x11.XGetWMHints(display, windowID);
        if (wmHintsPtr == 0) {
            wmHintsPtr = x11.XAllocWMHints();
        }
        X11.XWMHints wmHints = x11.createXWMHints(wmHintsPtr);
        int flags = (int) wmHints.get_flags() | X11Defs.IconPixmapHint;
        wmHints.set_icon_pixmap(pixmap);
        if (mask != 0) {
            flags |= X11Defs.IconMaskHint;
            wmHints.set_icon_mask(mask);
        }
        wmHints.set_flags(flags);
        int result = x11.XSetWMHints(display, windowID, wmHintsPtr);
        x11.XFree(wmHintsPtr);
        return result;
    }

    /**
     *
     * @param windowId - native window ID
     * @param property - could be NET_FRAME_EXTENTS or KDE_NET_WM_FRAME_STRUT
     * @return native insets set by Window Manager, or null if Window manager
     * doesn't support this property or property has unexpected formats
     */
    Insets getNativeInsets(long windowId, long property) {
        Insets insets = null;

        CLongPointer actualTypeReturn = bridge.createCLongPointer(1, false);
        Int32Pointer actualFormatReturn = bridge.createInt32Pointer(1, false);
        CLongPointer nitemsReturn = bridge.createCLongPointer(1, false);
        CLongPointer bytesAfterReturn = bridge.createCLongPointer(1, false);
        PointerPointer propReturn = bridge.createPointerPointer(1, false);

        int result = x11.XGetWindowProperty(factory.getDisplay(), windowId,
                property, 0, 4, X11Defs.FALSE,
                X11Defs.AnyPropertyType, actualTypeReturn, actualFormatReturn,
                nitemsReturn, bytesAfterReturn, propReturn);

        if (result == X11Defs.Success) {
            long nItems = nitemsReturn.get(0);
            long actualType = actualTypeReturn.get(0);
            int actualFormat = actualFormatReturn.get(0);
            CLongPointer ptrData = bridge.createCLongPointer(propReturn.get(0));
            if (ptrData == null) {
                return insets;
            }

            if ((nItems == 4) && (actualType == XA_CARDINAL)
                    && (actualFormat == 32)) {

                insets = new Insets(0, 0, 0, 0);
                insets.left = (int)ptrData.get(0);
                insets.right = (int)ptrData.get(1);
                insets.top = (int)ptrData.get(2);
                insets.bottom = (int)ptrData.get(3);
            }
            x11.XFree(ptrData);
        }

        return insets;
    }
}
