/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */
package com.sun.javafx.tk.quantum;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.sun.javafx.tk.FocusCause;
import com.sun.javafx.tk.TKScene;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.TKStageListener;
import com.sun.javafx.tk.Toolkit;
import java.security.AccessControlContext;
import java.security.AccessController;

/**
 * @author Artem Ananiev
 */
public abstract class GlassStage implements TKStage {

    // A list of all GlassStage objects regardless of visibility. Used in WindowStage.
    protected static List<GlassStage> windows = new ArrayList<GlassStage>();

    // A list of currently visible TKStage objects.
    private static List<TKStage> topLevelWindows = new ArrayList<TKStage>();

    private List<PopupStage> popups = new LinkedList<PopupStage>();

    // An active window is visible && enabled && focusable.
    // The list is maintained in the z-order, so that the last element
    // represents the topmost window (or more accurately, the last
    // focused window, which we assume is very close to the last topmost one).
    private static List<GlassStage> activeWindows = new LinkedList<GlassStage>();

    // True if any window has ever been made visible
    private static boolean firstWindowVisible = false;

    protected boolean verbose;

    protected GlassScene scene;

    protected TKStageListener stageListener;

    private boolean visible;

    private boolean important = true;

    private AccessControlContext accessCtrlCtx = null;

    protected GlassStage(boolean verbose) {
        this.verbose = verbose;
        windows.add(this);
    }

    /**
     * Listener for this stage peer to pass updates and events back to the stage
     *
     * @param listener The listener provided by the stage
     */
    @Override public void setTKStageListener(final TKStageListener listener) {
        this.stageListener = listener;
    }

    @Override public void setScene(TKScene scene) {
        if (this.scene != null) {
            this.scene.setGlassStage(null);
        }
        this.scene = (GlassScene)scene;
        if (this.scene != null) {
            this.scene.setGlassStage(this);
        }
    }

    protected AccessControlContext getAccessControlContext() {
        return accessCtrlCtx;
    }

    protected void setPlatformWindowClosed() {
    }

    /**
     * This method is used to set security context of the Stage if needed.
     * It is guaranteed to be called with user code on stack.
     */
    @Override public void initSecurityContext() {
        if (accessCtrlCtx != null) {
            return;
        }
        accessCtrlCtx = AccessController.getContext();
    }

    @Override public void requestFocus() {
        // TODO: implement requestFocus
    }

    @Override public void requestFocus(FocusCause cause) {
    }

    static void addActiveWindow(GlassStage window) {
        GlassStage.activeWindows.remove(window);
        GlassStage.activeWindows.add(window);
    }

    static void removeActiveWindow(GlassStage window) {
        GlassStage.activeWindows.remove(window);
    }

    final void handleFocusDisabled() {
        if (GlassStage.activeWindows.isEmpty()) {
            return;
        }
        GlassStage window = GlassStage.activeWindows.get(GlassStage.activeWindows.size() - 1);

        window.setIconified(false);
        window.requestToFront();
        window.requestFocus();
    }

    /**
     * Set if the stage is visible on screen
     *
     * @param visible True if the stage should be visible
     */
    @Override public void setVisible(boolean visible) {
        boolean isTopLevel = this.isImportant() && this.isTopLevel();
        boolean visibilityChanged = this.visible != visible;

        this.visible = visible;
        if (visible) {
            firstWindowVisible = true;
            if (visibilityChanged && isTopLevel) {
                topLevelWindows.add(this);
                notifyWindowListeners();
            }
        }
        if (!visible) {
            GlassStage.removeActiveWindow(this);
            if (visibilityChanged && isTopLevel) {
                topLevelWindows.remove(this);
                notifyWindowListeners();
            }
        }
        if (scene != null) {
            scene.stageVisible(visible);
        }
    }

    boolean isVisible() {
        return visible;
    }

    // We do blocking on windows that are backed by WindowStage and EmbeddedStage
    protected abstract void setPlatformEnabled(boolean enabled);

    protected abstract void requestToFront();

    @Override public void close() {
        windows.remove(this);
        topLevelWindows.remove(this);
        notifyWindowListeners();
    }

    static boolean windowsAreOpen() {
        for (GlassStage w : windows) {
            if (w.isVisible()) {
                return true;
            }
        }
        return false;
    }

    // True for unowned, non-Popup stage, or embedded stage
    public boolean isTopLevel() {
        return true;
    }

    @Override public void setImportant(boolean important) {
        this.important = important;
    }

    public boolean isImportant() {
        return important;
    }

    private static void notifyWindowListeners() {
        Toolkit.getToolkit().notifyWindowListeners(topLevelWindows);
    }

    protected void addPopup(final PopupStage popup) {
        popups.add(popup);
    }

    protected void removePopup(final PopupStage popup) {
        popups.remove(popup);
    }

    // Cmd+Q action
    static void requestClosingAllWindows() {
        for (GlassStage window : windows.toArray(new GlassStage[windows.size()])) {
            if (window.isVisible() && window.stageListener != null) {
                window.stageListener.closing();
            }
        }
    }
}
