/*
 * 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.awt.*;
import java.awt.geom.Point2D;
import java.lang.ref.WeakReference;

import sun.awt.IconInfo;
import sun.awt.AWTAccessor;
import sun.awt.SunToolkit;

class XWarningWindow extends XWindow {
    private final static int SHOWING_DELAY = 330;
    private final static int HIDING_DELAY = 2000;

    private final Window ownerWindow;
    private WeakReference<XWindowPeer> ownerPeer;
    private long parentWindow;

    private final static String OWNER = "OWNER";
    private InfoWindow.Tooltip tooltip;

    /**
     * Animation stage.
     */
    private volatile int currentIcon = 0;

    /* -1 - uninitialized.
     * 0 - 16x16
     * 1 - 24x24
     * 2 - 32x32
     * 3 - 48x48
     */
    private int currentSize = -1;
    private static IconInfo[][] icons;
    private static IconInfo getSecurityIconInfo(int size, int num) {
        synchronized (XWarningWindow.class) {
            if (icons == null) {
                icons = new IconInfo[4][3];
                if (XlibWrapper.dataModel == 32) {
                    icons[0][0] = new IconInfo(sun.awt.AWTIcon32_security_icon_bw16_png.security_icon_bw16_png);
                    icons[0][1] = new IconInfo(sun.awt.AWTIcon32_security_icon_interim16_png.security_icon_interim16_png);
                    icons[0][2] = new IconInfo(sun.awt.AWTIcon32_security_icon_yellow16_png.security_icon_yellow16_png);
                    icons[1][0] = new IconInfo(sun.awt.AWTIcon32_security_icon_bw24_png.security_icon_bw24_png);
                    icons[1][1] = new IconInfo(sun.awt.AWTIcon32_security_icon_interim24_png.security_icon_interim24_png);
                    icons[1][2] = new IconInfo(sun.awt.AWTIcon32_security_icon_yellow24_png.security_icon_yellow24_png);
                    icons[2][0] = new IconInfo(sun.awt.AWTIcon32_security_icon_bw32_png.security_icon_bw32_png);
                    icons[2][1] = new IconInfo(sun.awt.AWTIcon32_security_icon_interim32_png.security_icon_interim32_png);
                    icons[2][2] = new IconInfo(sun.awt.AWTIcon32_security_icon_yellow32_png.security_icon_yellow32_png);
                    icons[3][0] = new IconInfo(sun.awt.AWTIcon32_security_icon_bw48_png.security_icon_bw48_png);
                    icons[3][1] = new IconInfo(sun.awt.AWTIcon32_security_icon_interim48_png.security_icon_interim48_png);
                    icons[3][2] = new IconInfo(sun.awt.AWTIcon32_security_icon_yellow48_png.security_icon_yellow48_png);
                } else {
                    icons[0][0] = new IconInfo(sun.awt.AWTIcon64_security_icon_bw16_png.security_icon_bw16_png);
                    icons[0][1] = new IconInfo(sun.awt.AWTIcon64_security_icon_interim16_png.security_icon_interim16_png);
                    icons[0][2] = new IconInfo(sun.awt.AWTIcon64_security_icon_yellow16_png.security_icon_yellow16_png);
                    icons[1][0] = new IconInfo(sun.awt.AWTIcon64_security_icon_bw24_png.security_icon_bw24_png);
                    icons[1][1] = new IconInfo(sun.awt.AWTIcon64_security_icon_interim24_png.security_icon_interim24_png);
                    icons[1][2] = new IconInfo(sun.awt.AWTIcon64_security_icon_yellow24_png.security_icon_yellow24_png);
                    icons[2][0] = new IconInfo(sun.awt.AWTIcon64_security_icon_bw32_png.security_icon_bw32_png);
                    icons[2][1] = new IconInfo(sun.awt.AWTIcon64_security_icon_interim32_png.security_icon_interim32_png);
                    icons[2][2] = new IconInfo(sun.awt.AWTIcon64_security_icon_yellow32_png.security_icon_yellow32_png);
                    icons[3][0] = new IconInfo(sun.awt.AWTIcon64_security_icon_bw48_png.security_icon_bw48_png);
                    icons[3][1] = new IconInfo(sun.awt.AWTIcon64_security_icon_interim48_png.security_icon_interim48_png);
                    icons[3][2] = new IconInfo(sun.awt.AWTIcon64_security_icon_yellow48_png.security_icon_yellow48_png);
                }
            }
        }
        final int sizeIndex = size % icons.length;
        return icons[sizeIndex][num % icons[sizeIndex].length];
    }

    private void updateIconSize() {
        int newSize = -1;

        if (ownerWindow != null) {
            Insets insets = ownerWindow.getInsets();
            int max = Math.max(insets.top, Math.max(insets.bottom,
                        Math.max(insets.left, insets.right)));
            if (max < 24) {
                newSize = 0;
            } else if (max < 32) {
                newSize = 1;
            } else if (max < 48) {
                newSize = 2;
            } else {
                newSize = 3;
            }
        }
        // Make sure we have a valid size
        if (newSize == -1) {
            newSize = 0;
        }

        // Note: this is not the most wise solution to use awtLock here,
        // this should have been sync'ed with the stateLock. However,
        // the awtLock must be taken first (see XBaseWindow.getStateLock()),
        // and we need the awtLock anyway to update the shape of the icon.
        // So it's easier to use just one lock instead.
        XToolkit.awtLock();
        try {
            if (newSize != currentSize) {
                currentSize = newSize;
                IconInfo ico = getSecurityIconInfo(currentSize, 0);
                XlibWrapper.SetBitmapShape(XToolkit.getDisplay(), getWindow(),
                        ico.getWidth(), ico.getHeight(), ico.getIntData());
                AWTAccessor.getWindowAccessor().setSecurityWarningSize(
                        ownerWindow, ico.getWidth(), ico.getHeight());
            }
        } finally {
            XToolkit.awtUnlock();
        }
    }

    private IconInfo getSecurityIconInfo() {
        updateIconSize();
        return getSecurityIconInfo(currentSize, currentIcon);
    }

    XWarningWindow(final Window ownerWindow, long parentWindow, XWindowPeer ownerPeer) {
        super(new XCreateWindowParams(new Object[] {
                        TARGET, ownerWindow,
                        OWNER, Long.valueOf(parentWindow)
        }));
        this.ownerWindow = ownerWindow;
        this.parentWindow = parentWindow;
        this.tooltip = new InfoWindow.Tooltip(null, getTarget(),
                new InfoWindow.Tooltip.LiveArguments() {
                    public boolean isDisposed() {
                        return XWarningWindow.this.isDisposed();
                    }
                    public Rectangle getBounds() {
                        return XWarningWindow.this.getBounds();
                    }
                    public String getTooltipString() {
                        return XWarningWindow.this.ownerWindow.getWarningString();
                    }
                });
        this.ownerPeer = new WeakReference<XWindowPeer>(ownerPeer);
    }

    private void requestNoTaskbar() {
        XNETProtocol netProtocol = XWM.getWM().getNETProtocol();
        if (netProtocol != null) {
            netProtocol.requestState(this, netProtocol.XA_NET_WM_STATE_SKIP_TASKBAR, true);
        }
    }

    @Override
    void postInit(XCreateWindowParams params) {
        super.postInit(params);
        XToolkit.awtLock();
        try {
            XWM.setMotifDecor(this, false, 0, 0);
            XWM.setOLDecor(this, false, 0);

            long parentWindow = ((Long)params.get(OWNER)).longValue();
            XlibWrapper.XSetTransientFor(XToolkit.getDisplay(),
                    getWindow(), parentWindow);

            XWMHints hints = getWMHints();
            hints.set_flags(hints.get_flags() | (int)XUtilConstants.InputHint | (int)XUtilConstants.StateHint);
            hints.set_input(false);
            hints.set_initial_state(XUtilConstants.NormalState);
            XlibWrapper.XSetWMHints(XToolkit.getDisplay(), getWindow(), hints.pData);

            initWMProtocols();
            requestNoTaskbar();
        } finally {
            XToolkit.awtUnlock();
        }
    }

    /**
     * @param x,y,w,h coordinates of the untrusted window
     */
    public void reposition(int x, int y, int w, int h) {
        Point2D point = AWTAccessor.getWindowAccessor().
            calculateSecurityWarningPosition(ownerWindow,
                x, y, w, h);
        reshape((int)point.getX(), (int)point.getY(), getWidth(), getHeight());
    }

    protected String getWMName() {
        return "Warning window";
    }

    public Graphics getGraphics() {
        if ((surfaceData == null) || (ownerWindow == null)) return null;
        return getGraphics(surfaceData,
                                 getColor(),
                                 getBackground(),
                                 getFont());
    }
    void paint(Graphics g, int x, int y, int width, int height) {
        g.drawImage(getSecurityIconInfo().getImage(), 0, 0, null);
    }

    String getWarningString() {
        return ownerWindow.getWarningString();
    }

    int getWidth() {
        return getSecurityIconInfo().getWidth();
    }

    int getHeight() {
        return getSecurityIconInfo().getHeight();
    }

    Color getBackground() {
        return SystemColor.window;
    }
    Color getColor() {
        return Color.black;
    }
    Font getFont () {
        return ownerWindow.getFont();
    }
    public void repaint() {
        Rectangle bounds = getBounds();
        Graphics g = getGraphics();
        try {
            paint(g, 0, 0, bounds.width, bounds.height);
        } finally {
            g.dispose();
        }
    }

    @Override
    public void handleExposeEvent(XEvent xev) {
        super.handleExposeEvent(xev);

        XExposeEvent xe = xev.get_xexpose();
        final int x = xe.get_x();
        final int y = xe.get_y();
        final int width = xe.get_width();
        final int height = xe.get_height();
        SunToolkit.executeOnEventHandlerThread(target,
                new Runnable() {
                    public void run() {
                        Graphics g = getGraphics();
                        try {
                            paint(g, x, y, width, height);
                        } finally {
                            g.dispose();
                        }
                    }
                });
    }

    @Override
    protected boolean isEventDisabled(XEvent e) {
        return true;
    }

    /** Send a synthetic UnmapNotify in order to withdraw the window.
     */
    private void withdraw() {
        XEvent req = new XEvent();
        try {
            long root;
            XToolkit.awtLock();
            try {
                root = XlibWrapper.RootWindow(XToolkit.getDisplay(), getScreenNumber());
            }
            finally {
                XToolkit.awtUnlock();
            }

            req.set_type(XConstants.UnmapNotify);

            XUnmapEvent umev = req.get_xunmap();

            umev.set_event(root);
            umev.set_window(getWindow());
            umev.set_from_configure(false);

            XToolkit.awtLock();
            try {
                XlibWrapper.XSendEvent(XToolkit.getDisplay(),
                        root,
                        false,
                        XConstants.SubstructureRedirectMask | XConstants.SubstructureNotifyMask,
                        req.pData);
            }
            finally {
                XToolkit.awtUnlock();
            }
        } finally {
            req.dispose();
        }
    }

    @Override
    protected void stateChanged(long time, int oldState, int newState) {
        if (newState == XUtilConstants.IconicState) {
            super.xSetVisible(false);
            withdraw();
        }
    }

    @Override
    protected void setMouseAbove(boolean above) {
        super.setMouseAbove(above);
        XWindowPeer p = ownerPeer.get();
        if (p != null) {
            p.updateSecurityWarningVisibility();
        }
    }

    @Override
    protected void enterNotify(long window) {
        super.enterNotify(window);
        if (window == getWindow()) {
            tooltip.enter();
        }
    }

    @Override
    protected void leaveNotify(long window) {
        super.leaveNotify(window);
        if (window == getWindow()) {
            tooltip.exit();
        }
    }

    @Override
    public void xSetVisible(boolean visible) {
        super.xSetVisible(visible);

        // The _NET_WM_STATE_SKIP_TASKBAR got reset upon hiding/showing,
        // so we request it every time whenever we change the visibility.
        requestNoTaskbar();
    }

    private final Runnable hidingTask = new Runnable() {
        public void run() {
            xSetVisible(false);
        }
    };

    private final Runnable showingTask = new Runnable() {
        public void run() {
            if (!isVisible()) {
                xSetVisible(true);
                updateIconSize();
                XWindowPeer peer = ownerPeer.get();
                if (peer != null) {
                    peer.repositionSecurityWarning();
                }
            }
            repaint();
            if (currentIcon > 0) {
                currentIcon--;
                XToolkit.schedule(showingTask, SHOWING_DELAY);
            }
        }
    };

    public void setSecurityWarningVisible(boolean visible, boolean doSchedule) {
        if (visible) {
            XToolkit.remove(hidingTask);
            XToolkit.remove(showingTask);
            if (isVisible()) {
                currentIcon = 0;
            } else {
                currentIcon = 3;
            }
            if (doSchedule) {
                XToolkit.schedule(showingTask, 1);
            } else {
                showingTask.run();
            }
        } else {
            XToolkit.remove(showingTask);
            XToolkit.remove(hidingTask);
            if (!isVisible()) {
                return;
            }
            if (doSchedule) {
                XToolkit.schedule(hidingTask, HIDING_DELAY);
            } else {
                hidingTask.run();
            }
        }
    }
}
