////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////

package pp.dialog;

import com.jme3.app.SimpleApplication;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.Panel;
import com.simsilica.lemur.focus.FocusManagerState;

import java.util.ArrayDeque;
import java.util.Deque;

import static java.lang.Math.max;

/**
 * Manages dialog boxes within the application, handling their display, positioning, and focus.
 */
public class DialogManager {
    /**
     * The application instance.
     */
    private final SimpleApplication app;

    /**
     * A stack to keep track of the dialogs.
     */
    private final Deque<Dialog> dialogStack = new ArrayDeque<>();

    /**
     * Constructs a DialogManager for the specified application.
     *
     * @param app the SimpleApplication instance
     */
    public DialogManager(SimpleApplication app) {
        this.app = app;
    }

    /**
     * Checks if any dialog is currently displayed.
     *
     * @return true if any dialog is currently shown, false otherwise
     */
    public boolean showsDialog() {
        return !dialogStack.isEmpty();
    }

    /**
     * Retrieves the stack of dialogs.
     *
     * @return the dialog stack
     */
    public Deque<Dialog> getDialogStack() {
        return dialogStack;
    }

    /**
     * Calculates the depth for the next dialog to be displayed.
     *
     * @return the next depth value
     */
    public int nextDepth() {
        return dialogStack.isEmpty() ? 10 : dialogStack.peek().depth + 10;
    }

    /**
     * Positions the specified panel in the center of the screen, with a specified z coordinate.
     *
     * @param panel the panel to center
     * @param z     the z coordinate
     */
    public void centering(Panel panel, float z) {
        final Vector3f size = panel.getPreferredSize();
        centering(panel, size.getX(), size.getY(), z);
    }

    /**
     * Positions the specified spatial in the center of the screen, with specified width, height, and z coordinate.
     *
     * @param spatial the spatial to center
     * @param width   the width reserved for the spatial
     * @param height  the height reserved for the spatial
     * @param z       the z coordinate
     */
    public void centering(Spatial spatial, float width, float height, float z) {
        final AppSettings settings = app.getContext().getSettings();
        spatial.setLocalTranslation(max(0f, 0.5f * (settings.getWidth() - width)),
                                    max(0f, 0.5f * (settings.getHeight() + height)),
                                    z);
    }

    /**
     * Arranges for the specified spatial to receive the focus.
     *
     * @param spatial the spatial to focus
     */
    public void setFocus(Spatial spatial) {
        final var focusManager = app.getStateManager().getState(FocusManagerState.class);
        if (focusManager != null)
            focusManager.setFocus(spatial);
    }

    /**
     * Opens the specified dialog and adds it to the dialog stack.
     *
     * @param dialog the dialog to open
     */
    void open(Dialog dialog) {
        dialogStack.push(dialog);
        dialog.update();
        app.getGuiNode().attachChild(dialog);
    }

    /**
     * Checks if the specified dialog is the topmost dialog in the dialog stack.
     *
     * @param dialog a dialog.
     * @return true if the dialog is the top dialog, false otherwise.
     */
    boolean isTop(Dialog dialog) {
        return !dialogStack.isEmpty() && dialogStack.peek() == dialog;
    }

    /**
     * Closes the specified dialog, removing it from the dialog stack.
     *
     * @param dialog the dialog to close
     * @throws IllegalArgumentException if the specified dialog is not the top dialog
     */
    void close(Dialog dialog) {
        if (!isTop(dialog))
            throw new IllegalArgumentException(dialog + " is not the top dialog");
        dialogStack.pop();
        if (!dialogStack.isEmpty())
            dialogStack.peek().update();
        app.getGuiNode().detachChild(dialog);
    }

    /**
     * Calls the escape action of the top dialog, if a dialog is shown.
     */
    public void escape() {
        if (dialogStack.isEmpty()) return;
        dialogStack.peek().escape();
    }

    public void update(float delta) {
        for (Dialog dialog : dialogStack)
            dialog.update(delta);
    }
}
