/*
 * Copyright (c) 2002, 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.Dimension;
import java.awt.Graphics;
import java.awt.MenuItem;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;

import java.util.Vector;

import sun.awt.SunToolkit;
import sun.util.logging.PlatformLogger;

import javax.swing.SwingUtilities;

public class XMenuWindow extends XBaseMenuWindow {

    /************************************************
     *
     * Data members
     *
     ************************************************/

    private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XMenuWindow");

    /*
     * Primary members
     */
    private XMenuPeer menuPeer;

    /*
     * dimension constants
     */
    private static final int WINDOW_SPACING_LEFT = 2;
    private static final int WINDOW_SPACING_RIGHT = 2;
    private static final int WINDOW_SPACING_TOP = 2;
    private static final int WINDOW_SPACING_BOTTOM = 2;
    private static final int WINDOW_ITEM_INDENT = 15;
    private static final int WINDOW_ITEM_MARGIN_LEFT = 2;
    private static final int WINDOW_ITEM_MARGIN_RIGHT = 2;
    private static final int WINDOW_ITEM_MARGIN_TOP = 2;
    private static final int WINDOW_ITEM_MARGIN_BOTTOM = 2;
    private static final int WINDOW_SHORTCUT_SPACING = 10;

    /*
     * Checkmark
     */
    private static final int CHECKMARK_SIZE = 128;
    private static final int[] CHECKMARK_X = new int[] {1, 25,56,124,124,85, 64};  // X-coords
    private static final int[] CHECKMARK_Y = new int[] {59,35,67,  0, 12,66,123};  // Y-coords

    /************************************************
     *
     * Mapping data
     *
     ************************************************/

    static final class MappingData extends XBaseMenuWindow.MappingData {
        /**
         * Rectangle for the caption
         * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit
         */
        private Rectangle captionRect;

        /**
         * Desired size of menu window
         */
        private Dimension desiredSize;

        /**
         * Width of largest checkmark
         * At the same time the left origin
         * of all item's text
         */
        private int leftMarkWidth;

        /**
         * Left origin of all shortcut labels
         */
        private int shortcutOrigin;

        /**
         * The origin of right mark
         * (submenu's arrow)
         */
        private int rightMarkOrigin;

        MappingData(XMenuItemPeer[] items, Rectangle captionRect, Dimension desiredSize, int leftMarkWidth, int shortcutOrigin, int rightMarkOrigin) {
            super(items);
            this.captionRect = captionRect;
            this.desiredSize = desiredSize;
            this.leftMarkWidth = leftMarkWidth;
            this.shortcutOrigin = shortcutOrigin;
            this.rightMarkOrigin = rightMarkOrigin;
        }

        /**
         * Constructs MappingData without items
         * This constructor should be used in case of errors
         */
        MappingData() {
            this.desiredSize = new Dimension(0, 0);
            this.leftMarkWidth = 0;
            this.shortcutOrigin = 0;
            this.rightMarkOrigin = 0;
        }

        public Rectangle getCaptionRect() {
            return this.captionRect;
        }

        public Dimension getDesiredSize() {
            return this.desiredSize;
        }

        public int getShortcutOrigin() {
            return this.shortcutOrigin;
        }

        public int getLeftMarkWidth() {
            return this.leftMarkWidth;
        }

        public int getRightMarkOrigin() {
            return this.rightMarkOrigin;
        }

    }


    /************************************************
     *
     * Construction
     *
     ************************************************/

    /**
     * Constructs XMenuWindow for specified XMenuPeer
     * null for XPopupMenuWindow
     */
    XMenuWindow(XMenuPeer menuPeer) {
        if (menuPeer != null) {
            this.menuPeer = menuPeer;
            this.target = menuPeer.getContainer().target;
            // Get menus from the target.
            Vector<MenuItem> targetItemVector = null;
            targetItemVector = getMenuTargetItems();
            reloadItems(targetItemVector);
        }
    }

    /************************************************
     *
     * Initialization
     *
     ************************************************/
    /*
     * Overridden initialization
     */
    @Override
    void postInit(XCreateWindowParams params) {
        super.postInit(params);
        //Fixed 6267182: PIT: Menu is not visible after
        //showing and disposing a file dialog, XToolkit
        //toFront() is called on every show
    }

    /************************************************
     *
     * Implementation of abstract methods
     *
     ************************************************/

    /**
     * @see XBaseMenuWindow#getParentMenuWindow()
     */
    @Override
    protected XBaseMenuWindow getParentMenuWindow() {
        return (menuPeer != null) ? menuPeer.getContainer() : null;
    }

    /**
     * @see XBaseMenuWindow#map()
     */
    @Override
    protected MappingData map() {
        //TODO:Implement popup-menu caption mapping and painting and tear-off
        int itemCnt;
        if (!isCreated()) {
            MappingData mappingData = new MappingData(new XMenuItemPeer[0], new Rectangle(0, 0, 0, 0), new Dimension(0, 0), 0, 0, 0);
            return mappingData;
        }
        XMenuItemPeer[] itemVector = copyItems();
        itemCnt = itemVector.length;
        //We need maximum width of components before calculating item's bounds
        Dimension captionSize = getCaptionSize();
        int maxWidth = (captionSize != null) ? captionSize.width : 0;
        int maxLeftIndent = 0;
        int maxRightIndent = 0;
        int maxShortcutWidth = 0;
        XMenuItemPeer.TextMetrics[] itemMetrics = new XMenuItemPeer.TextMetrics[itemCnt];
        for (int i = 0; i < itemCnt; i++) {
            XMenuItemPeer item = itemVector[i];
            itemMetrics[i] = itemVector[i].getTextMetrics();
            Dimension dim = itemMetrics[i].getTextDimension();
            if (dim != null) {
                if (itemVector[i] instanceof XCheckboxMenuItemPeer) {
                    maxLeftIndent = Math.max(maxLeftIndent, dim.height);
                } else if (itemVector[i] instanceof XMenuPeer) {
                    maxRightIndent = Math.max(maxRightIndent, dim.height);
                }
                maxWidth = Math.max(maxWidth, dim.width);
                maxShortcutWidth = Math.max(maxShortcutWidth, itemMetrics[i].getShortcutWidth());
            }
        }
        //Calculate bounds
        int nextOffset = WINDOW_SPACING_TOP;
        int shortcutOrigin = WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent + maxWidth;
        if (maxShortcutWidth > 0) {
            shortcutOrigin = shortcutOrigin + WINDOW_SHORTCUT_SPACING;
        }
        int rightMarkOrigin = shortcutOrigin + maxShortcutWidth;
        int itemWidth = rightMarkOrigin + maxRightIndent + WINDOW_ITEM_MARGIN_RIGHT;
        int width = WINDOW_SPACING_LEFT + itemWidth + WINDOW_SPACING_RIGHT;
        //Caption rectangle
        Rectangle captionRect = null;
        if (captionSize != null) {
            captionRect = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, itemWidth, captionSize.height);
            nextOffset += captionSize.height;
        } else {
            captionRect = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, maxWidth, 0);
        }
        //Item rectangles
        for (int i = 0; i < itemCnt; i++) {
            XMenuItemPeer item = itemVector[i];
            XMenuItemPeer.TextMetrics metrics = itemMetrics[i];
            Dimension dim = metrics.getTextDimension();
            if (dim != null) {
                int itemHeight = WINDOW_ITEM_MARGIN_TOP + dim.height + WINDOW_ITEM_MARGIN_BOTTOM;
                Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, itemWidth, itemHeight);
                int y = (itemHeight + dim.height) / 2  - metrics.getTextBaseline();
                Point textOrigin = new Point(WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent, nextOffset + y);
                nextOffset += itemHeight;
                item.map(bounds, textOrigin);
            } else {
                //Text metrics could not be determined because of errors
                //Map item with empty rectangle
                Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, 0, 0);
                Point textOrigin = new Point(WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent, nextOffset);
                item.map(bounds, textOrigin);
            }
        }
        int height = nextOffset + WINDOW_SPACING_BOTTOM;
        MappingData mappingData = new MappingData(itemVector, captionRect, new Dimension(width, height), maxLeftIndent, shortcutOrigin, rightMarkOrigin);
        return mappingData;
    }

    /**
     * @see XBaseMenuWindow#getSubmenuBounds
     */
    @Override
    protected Rectangle getSubmenuBounds(Rectangle itemBounds, Dimension windowSize) {
        Rectangle globalBounds = toGlobal(itemBounds);
        Rectangle screenBounds = getCurrentGraphicsConfiguration().getBounds();
        Rectangle res;
        res = fitWindowRight(globalBounds, windowSize, screenBounds);
        if (res != null) {
            return res;
        }
        res = fitWindowBelow(globalBounds, windowSize, screenBounds);
        if (res != null) {
            return res;
        }
        res = fitWindowAbove(globalBounds, windowSize, screenBounds);
        if (res != null) {
            return res;
        }
        res = fitWindowLeft(globalBounds, windowSize, screenBounds);
        if (res != null) {
            return res;
        }
        return fitWindowToScreen(windowSize, screenBounds);
   }

    /**
     * It's likely that size of items was changed
     * invoke resizing of window on eventHandlerThread
     */
    @Override
    protected void updateSize() {
        resetMapping();
        if (isShowing()) {
            XToolkit.executeOnEventHandlerThread(target, new Runnable() {
                    public void run() {
                        Dimension dim = getDesiredSize();
                        reshape(x, y, dim.width, dim.height);
                    }
                });
        }
    }

    /************************************************
     *
     * Overridable caption-painting functions
     * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit
     *
     ************************************************/

    /**
     * Returns size of menu window's caption or null
     * if window has no caption.
     * Can be overridden for popup menus and tear-off menus
     */
    protected Dimension getCaptionSize() {
        return null;
    }

    /**
     * Paints menu window's caption.
     * Can be overridden for popup menus and tear-off menus.
     * Default implementation does nothing
     */
    protected void paintCaption(Graphics g, Rectangle rect) {
    }

    /************************************************
     *
     * General-purpose utility functions
     *
     ************************************************/

    /**
     * Returns corresponding menu peer
     */
    XMenuPeer getMenuPeer() {
        return menuPeer;
    }

    /**
     * Reads vector of items from target
     * This function is overridden in XPopupMenuPeer
     */
    Vector<MenuItem> getMenuTargetItems() {
        return menuPeer.getTargetItems();
    }

    /**
     * Returns desired size calculated while mapping
     */
    Dimension getDesiredSize() {
        MappingData mappingData = (MappingData)getMappingData();
        return mappingData.getDesiredSize();
    }

    /**
     * Checks if menu window is created
     */
    boolean isCreated() {
        return getWindow() != 0;
    }

    /**
     * Performs delayed creation of menu window if necessary
     */
    boolean ensureCreated() {
        if (!isCreated()) {
            XCreateWindowParams params = getDelayedParams();
            params.remove(DELAYED);
            params.add(OVERRIDE_REDIRECT, Boolean.TRUE);
            params.add(XWindow.TARGET, target);
            init(params);
        }
        return true;
    }

    protected Window getMenuTarget() {
        if (target instanceof Window targetWindow) {
            return targetWindow;
        } else {
            return target == null
                    ? null
                    : SwingUtilities.getWindowAncestor(target);
        }
    }

    /**
     * Init window if it's not inited yet
     * and show it at specified coordinates
     * @param bounds bounding rectangle of window
     * in global coordinates
     */
    void show(Rectangle bounds) {
        if (!isCreated()) {
            return;
        }
        if (log.isLoggable(PlatformLogger.Level.FINER)) {
            log.finer("showing menu window + " + getWindow() + " at " + bounds);
        }
        XToolkit.awtLock();
        try {
            reshape(bounds.x, bounds.y, bounds.width, bounds.height);
            if (Toolkit.getDefaultToolkit() instanceof SunToolkit sunToolkit) {
                sunToolkit.dismissPopupOnFocusLostIfNeeded(getMenuTarget());
            }
            xSetVisible(true);
            //Fixed 6267182: PIT: Menu is not visible after
            //showing and disposing a file dialog, XToolkit
            toFront();
            selectItem(getFirstSelectableItem(), false);
        } finally {
            XToolkit.awtUnlock();
        }
    }

    /**
     * Hides menu window
     */
    void hide() {
        selectItem(null, false);
        xSetVisible(false);
        if (Toolkit.getDefaultToolkit() instanceof SunToolkit sunToolkit) {
            sunToolkit.dismissPopupOnFocusLostIfNeededCleanUp(getMenuTarget());
        }
    }

    /************************************************
     *
     * Painting
     *
     ************************************************/

    /**
     * Paints menu window
     */
    @Override
    public void paintPeer(Graphics g) {
        resetColors();
        int width = getWidth();
        int height = getHeight();

        flush();
        //Fill background of rectangle
        g.setColor(getBackgroundColor());
        g.fillRect(1, 1, width - 2, height - 2);
        draw3DRect(g, 0, 0, width, height, true);

        //Mapping data
        MappingData mappingData = (MappingData)getMappingData();

        //Paint caption
        paintCaption(g, mappingData.getCaptionRect());

        //Paint menus
        XMenuItemPeer[] itemVector = mappingData.getItems();
        Dimension windowSize =  mappingData.getDesiredSize();
        XMenuItemPeer selectedItem = getSelectedItem();
        for (int i = 0; i < itemVector.length; i++) {
            XMenuItemPeer item = itemVector[i];
            XMenuItemPeer.TextMetrics metrics = item.getTextMetrics();
            Rectangle bounds = item.getBounds();
            if (item.isSeparator()) {
                draw3DRect(g, bounds.x, bounds.y + bounds.height / 2,  bounds.width, 2, false);
            } else {
                //paint item
                g.setFont(item.getTargetFont());
                Point textOrigin = item.getTextOrigin();
                Dimension textDim = metrics.getTextDimension();
                if (item == selectedItem) {
                    g.setColor(getSelectedColor());
                    g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
                    draw3DRect(g, bounds.x, bounds.y, bounds.width, bounds.height, false);
                }
                g.setColor(item.isTargetItemEnabled() ? getForegroundColor() : getDisabledColor());
                g.drawString(item.getTargetLabel(), textOrigin.x, textOrigin.y);
                String shortcutText = item.getShortcutText();
                if (shortcutText != null) {
                    g.drawString(shortcutText, mappingData.getShortcutOrigin(), textOrigin.y);
                }
                if (item instanceof XMenuPeer) {
                    //calculate arrow coordinates
                    int markWidth = textDim.height * 4 / 5;
                    int markHeight = textDim.height * 4 / 5;
                    int markX = bounds.x + bounds.width - markWidth - WINDOW_SPACING_RIGHT - WINDOW_ITEM_MARGIN_RIGHT;
                    int markY = bounds.y + (bounds.height - markHeight) / 2;
                    //draw arrow
                    g.setColor(item.isTargetItemEnabled() ? getDarkShadowColor() : getDisabledColor());
                    g.drawLine(markX, markY + markHeight, markX + markWidth, markY + markHeight / 2);
                    g.setColor(item.isTargetItemEnabled() ? getLightShadowColor() : getDisabledColor());
                    g.drawLine(markX, markY, markX + markWidth, markY + markHeight / 2);
                    g.drawLine(markX, markY, markX, markY + markHeight);
                } else if (item instanceof XCheckboxMenuItemPeer) {
                    //calculate checkmark coordinates
                    int markWidth = textDim.height * 4 / 5;
                    int markHeight = textDim.height * 4 / 5;
                    int markX = WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT;
                    int markY = bounds.y + (bounds.height - markHeight) / 2;
                    boolean checkState = ((XCheckboxMenuItemPeer)item).getTargetState();
                    //draw checkmark
                    if (checkState) {
                        g.setColor(getSelectedColor());
                        g.fillRect(markX, markY, markWidth, markHeight);
                        draw3DRect(g, markX, markY, markWidth, markHeight, false);
                        int[] px = new int[CHECKMARK_X.length];
                        int[] py = new int[CHECKMARK_X.length];
                        for (int j = 0; j < CHECKMARK_X.length; j++) {
                            px[j] = markX + CHECKMARK_X[j] * markWidth / CHECKMARK_SIZE;
                            py[j] = markY + CHECKMARK_Y[j] * markHeight / CHECKMARK_SIZE;
                        }
                        g.setColor(item.isTargetItemEnabled() ? getForegroundColor() : getDisabledColor());
                        g.fillPolygon(px, py, CHECKMARK_X.length);
                    } else {
                        g.setColor(getBackgroundColor());
                        g.fillRect(markX, markY, markWidth, markHeight);
                        draw3DRect(g, markX, markY, markWidth, markHeight, true);
                    }
                }
            }
        }
        flush();
    }

}
