/*
 * Copyright (c) 2011, 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 com.apple.laf;

import java.awt.*;

import javax.swing.SwingConstants;

final class AquaTabbedPaneTabState {
    static final int FIXED_SCROLL_TAB_LENGTH = 27;

    protected final Rectangle leftScrollTabRect = new Rectangle();
    protected final Rectangle rightScrollTabRect = new Rectangle();

    protected int numberOfVisibleTabs = 0;
    protected int[] visibleTabList = new int[10];
    protected int lastLeftmostTab;
    protected int lastReturnAt;

    private boolean needsScrollers;
    private boolean hasMoreLeftTabs;
    private boolean hasMoreRightTabs;

    private final AquaTabbedPaneUI pane;

    protected AquaTabbedPaneTabState(final AquaTabbedPaneUI pane) {
        this.pane = pane;
    }

    protected int getIndex(final int i) {
        if (i >= visibleTabList.length) return Integer.MIN_VALUE;
        return visibleTabList[i];
    }

    protected void init(final int tabCount) {
        if (tabCount < 1) needsScrollers = false;
        if (tabCount == visibleTabList.length) return;
        final int[] tempVisibleTabs = new int[tabCount];
        System.arraycopy(visibleTabList, 0, tempVisibleTabs, 0, Math.min(visibleTabList.length, tabCount));
        visibleTabList = tempVisibleTabs;
    }

    int getTotal() {
        return numberOfVisibleTabs;
    }

    boolean needsScrollTabs() {
        return needsScrollers;
    }

    void setNeedsScrollers(final boolean needsScrollers) {
        this.needsScrollers = needsScrollers;
    }

    boolean needsLeftScrollTab() {
        return hasMoreLeftTabs;
    }

    boolean needsRightScrollTab() {
        return hasMoreRightTabs;
    }

    Rectangle getLeftScrollTabRect() {
        return leftScrollTabRect;
    }

    Rectangle getRightScrollTabRect() {
        return rightScrollTabRect;
    }

    boolean isBefore(final int i) {
        if (numberOfVisibleTabs == 0) return true;
        if (i < visibleTabList[0]) return true;
        return false;
    }

    boolean isAfter(final int i) {
        if (i > visibleTabList[numberOfVisibleTabs - 1]) return true;
        return false;
    }

    private void addToEnd(final int idToAdd, final int length) {
        visibleTabList[length] = idToAdd;
    }

    private void addToBeginning(final int idToAdd, final int length) {
        System.arraycopy(visibleTabList, 0, visibleTabList, 1, length);
        visibleTabList[0] = idToAdd;
    }


    void relayoutForScrolling(final Rectangle[] rects, final int startX, final int startY, final int returnAt, final int selectedIndex, final boolean verticalTabRuns, final int tabCount, final boolean isLeftToRight) {
        if (!needsScrollers) {
            hasMoreLeftTabs = false;
            hasMoreRightTabs = false;
            return;
        }

        // we don't fit, so we need to figure the space based on the size of the popup
        // tab, then add the tabs, centering the selected tab as much as possible.

        // Tabs on TOP or BOTTOM or LEFT or RIGHT
        // if top or bottom, width is hardocoded
        // if left or right height should be hardcoded.
        if (verticalTabRuns) {
            rightScrollTabRect.height = FIXED_SCROLL_TAB_LENGTH;
            leftScrollTabRect.height = FIXED_SCROLL_TAB_LENGTH;
        } else {
            rightScrollTabRect.width = FIXED_SCROLL_TAB_LENGTH;
            leftScrollTabRect.width = FIXED_SCROLL_TAB_LENGTH;
        }

        // we have all the tab rects, we just need to adjust the x coordinates
        // and populate the visible list

        // sja fix what do we do if remaining width is <0??

        // we could try to center it based on width of tabs, but for now
        // we try to center based on number of tabs on each side, putting the extra
        // on the left (since the first right is the selected tab).
        // if we have 0 selected we will just go right, and if we have

        // the logic here is start with the selected tab, and then fit
        // in as many tabs as possible on each side until we don't fit any more.
        // but if all we did was change selection then we need to try to keep the same
        // tabs on screen so we don't get a jarring tab moving out from under the mouse
        // effect.

        final boolean sizeChanged = returnAt != lastReturnAt;
        // so if we stay the same, make right the first tab and say left done = true
        if (pane.popupSelectionChanged || sizeChanged) {
            pane.popupSelectionChanged = false;
            lastLeftmostTab = -1;
        }

        int right = selectedIndex;
        int left = selectedIndex - 1;

        // if we had a good last leftmost tab then we set left to unused and
        // start at that tab.
        if (lastLeftmostTab >= 0) {
            right = lastLeftmostTab;
            left = -1;
        } else if (selectedIndex < 0) {
            // this is if there is none selected see radar 3138137
            right = 0;
            left = -1;
        }

        int remainingSpace = returnAt - pane.tabAreaInsets.right - pane.tabAreaInsets.left - FIXED_SCROLL_TAB_LENGTH * 2;
        int visibleCount = 0;

        final Rectangle firstRect = rects[right];
        if ((verticalTabRuns ? firstRect.height : firstRect.width) > remainingSpace) {
            // always show at least the selected one!
            addToEnd(right, visibleCount);
            if (verticalTabRuns) {
                firstRect.height = remainingSpace; // force it to fit!
            } else {
                firstRect.width = remainingSpace; // force it to fit!
            }
            visibleCount++;
        } else {
            boolean rightDone = false;
            boolean leftDone = false;

            // at least one if not more will fit
            while ((visibleCount < tabCount) && !(rightDone && leftDone)) {
                if (!rightDone && right >= 0 && right < tabCount) {
                    final Rectangle rightRect = rects[right];
                    if ((verticalTabRuns ? rightRect.height : rightRect.width) > remainingSpace) {
                        rightDone = true;
                    } else {
                        addToEnd(right, visibleCount);
                        visibleCount++;
                        remainingSpace -= (verticalTabRuns ? rightRect.height : rightRect.width);
                        right++;
                        continue; // this gives a bias to "paging forward", and "inching backward"
                    }
                } else {
                    rightDone = true;
                }

                if (!leftDone && left >= 0 && left < tabCount) {
                    final Rectangle leftRect = rects[left];
                    if ((verticalTabRuns ? leftRect.height : leftRect.width) > remainingSpace) {
                        leftDone = true;
                    } else {
                        addToBeginning(left, visibleCount);
                        visibleCount++;
                        remainingSpace -= (verticalTabRuns ? leftRect.height : leftRect.width);
                        left--;
                    }
                } else {
                    leftDone = true;
                }
            }
        }

        if (visibleCount > visibleTabList.length) visibleCount = visibleTabList.length;

        hasMoreLeftTabs = visibleTabList[0] > 0;
        hasMoreRightTabs = visibleTabList[visibleCount - 1] < visibleTabList.length - 1;

        numberOfVisibleTabs = visibleCount;
        // add the scroll tab at the end;
        lastLeftmostTab = getIndex(0);
        lastReturnAt = returnAt;

        final int firstTabIndex = getIndex(0);
        final int lastTabIndex = getIndex(visibleCount - 1);

        // move all "invisible" tabs beyond the edge of known space...
        for (int i = 0; i < tabCount; i++) {
            if (i < firstTabIndex || i > lastTabIndex) {
                final Rectangle rect = rects[i];
                rect.x = Short.MAX_VALUE;
                rect.y = Short.MAX_VALUE;
            }
        }
    }

    protected void alignRectsRunFor(final Rectangle[] rects, final Dimension tabPaneSize, final int tabPlacement, final boolean isRightToLeft) {
        final boolean isVertical = tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT;

        if (isVertical) {
            if (needsScrollers) {
                stretchScrollingVerticalRun(rects, tabPaneSize);
            } else {
                centerVerticalRun(rects, tabPaneSize);
            }
        } else {
            if (needsScrollers) {
                stretchScrollingHorizontalRun(rects, tabPaneSize, isRightToLeft);
            } else {
                centerHorizontalRun(rects, tabPaneSize, isRightToLeft);
            }
        }
    }

    private void centerHorizontalRun(final Rectangle[] rects, final Dimension size, final boolean isRightToLeft) {
        int totalLength = 0;
        for (final Rectangle element : rects) {
            totalLength += element.width;
        }

        int x = size.width / 2 - totalLength / 2;

        if (isRightToLeft) {
            for (final Rectangle rect : rects) {
                rect.x = x;
                x += rect.width;
            }
        } else {
            for (int i = rects.length - 1; i >= 0; i--) {
                final Rectangle rect = rects[i];
                rect.x = x;
                x += rect.width;
            }
        }
    }

    private void centerVerticalRun(final Rectangle[] rects, final Dimension size) {
        int totalLength = 0;
        for (final Rectangle element : rects) {
            totalLength += element.height;
        }

        int y = size.height / 2 - totalLength / 2;

        if (true) {
            for (final Rectangle rect : rects) {
                rect.y = y;
                y += rect.height;
            }
        } else {
            for (int i = rects.length - 1; i >= 0; i--) {
                final Rectangle rect = rects[i];
                rect.y = y;
                y += rect.height;
            }
        }
    }

    private void stretchScrollingHorizontalRun(final Rectangle[] rects, final Dimension size, final boolean isRightToLeft) {
        final int totalTabs = getTotal();
        final int firstTabIndex = getIndex(0);
        final int lastTabIndex = getIndex(totalTabs - 1);

        int totalRunLength = 0;
        for (int i = firstTabIndex; i <= lastTabIndex; i++) {
            totalRunLength += rects[i].width;
        }

        int slack = size.width - totalRunLength - pane.tabAreaInsets.left - pane.tabAreaInsets.right;
        if (needsLeftScrollTab()) {
            slack -= FIXED_SCROLL_TAB_LENGTH;
        }
        if (needsRightScrollTab()) {
            slack -= FIXED_SCROLL_TAB_LENGTH;
        }

        final int minSlack = (int)((float)(slack) / (float)(totalTabs));
        int extraSlack = slack - (minSlack * totalTabs);
        int runningLength = 0;
        final int xOffset = pane.tabAreaInsets.left + (needsLeftScrollTab() ? FIXED_SCROLL_TAB_LENGTH : 0);

        if (isRightToLeft) {
            for (int i = firstTabIndex; i <= lastTabIndex; i++) {
                final Rectangle rect = rects[i];
                int slackToAdd = minSlack;
                if (extraSlack > 0) {
                    slackToAdd++;
                    extraSlack--;
                }
                rect.x = runningLength + xOffset;
                rect.width += slackToAdd;
                runningLength += rect.width;
            }
        } else {
            for (int i = lastTabIndex; i >= firstTabIndex; i--) {
                final Rectangle rect = rects[i];
                int slackToAdd = minSlack;
                if (extraSlack > 0) {
                    slackToAdd++;
                    extraSlack--;
                }
                rect.x = runningLength + xOffset;
                rect.width += slackToAdd;
                runningLength += rect.width;
            }
        }

        if (isRightToLeft) {
            leftScrollTabRect.x = pane.tabAreaInsets.left;
            leftScrollTabRect.y = rects[firstTabIndex].y;
            leftScrollTabRect.height = rects[firstTabIndex].height;

            rightScrollTabRect.x = size.width - pane.tabAreaInsets.right - rightScrollTabRect.width;
            rightScrollTabRect.y = rects[lastTabIndex].y;
            rightScrollTabRect.height = rects[lastTabIndex].height;
        } else {
            rightScrollTabRect.x = pane.tabAreaInsets.left;
            rightScrollTabRect.y = rects[firstTabIndex].y;
            rightScrollTabRect.height = rects[firstTabIndex].height;

            leftScrollTabRect.x = size.width - pane.tabAreaInsets.right - rightScrollTabRect.width;
            leftScrollTabRect.y = rects[lastTabIndex].y;
            leftScrollTabRect.height = rects[lastTabIndex].height;

            if (needsLeftScrollTab()) {
                for (int i = lastTabIndex; i >= firstTabIndex; i--) {
                    final Rectangle rect = rects[i];
                    rect.x -= FIXED_SCROLL_TAB_LENGTH;
                }
            }

            if (needsRightScrollTab()) {
                for (int i = lastTabIndex; i >= firstTabIndex; i--) {
                    final Rectangle rect = rects[i];
                    rect.x += FIXED_SCROLL_TAB_LENGTH;
                }
            }
        }
    }

    private void stretchScrollingVerticalRun(final Rectangle[] rects, final Dimension size) {
        final int totalTabs = getTotal();
        final int firstTabIndex = getIndex(0);
        final int lastTabIndex = getIndex(totalTabs - 1);

        int totalRunLength = 0;
        for (int i = firstTabIndex; i <= lastTabIndex; i++) {
            totalRunLength += rects[i].height;
        }

        int slack = size.height - totalRunLength - pane.tabAreaInsets.top - pane.tabAreaInsets.bottom;
        if (needsLeftScrollTab()) {
            slack -= FIXED_SCROLL_TAB_LENGTH;
        }
        if (needsRightScrollTab()) {
            slack -= FIXED_SCROLL_TAB_LENGTH;
        }

        final int minSlack = (int)((float)(slack) / (float)(totalTabs));
        int extraSlack = slack - (minSlack * totalTabs);
        int runningLength = 0;
        final int yOffset = pane.tabAreaInsets.top + (needsLeftScrollTab() ? FIXED_SCROLL_TAB_LENGTH : 0);

        for (int i = firstTabIndex; i <= lastTabIndex; i++) {
            final Rectangle rect = rects[i];
            int slackToAdd = minSlack;
            if (extraSlack > 0) {
                slackToAdd++;
                extraSlack--;
            }
            rect.y = runningLength + yOffset;
            rect.height += slackToAdd;
            runningLength += rect.height;
        }

        leftScrollTabRect.x = rects[firstTabIndex].x;
        leftScrollTabRect.y = pane.tabAreaInsets.top;
        leftScrollTabRect.width = rects[firstTabIndex].width;

        rightScrollTabRect.x = rects[lastTabIndex].x;
        rightScrollTabRect.y = size.height - pane.tabAreaInsets.bottom - rightScrollTabRect.height;
        rightScrollTabRect.width = rects[lastTabIndex].width;
    }
}
