PrometheusMainWindow.java

/*
 * Prometheus: Application Framework
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.prometheus.ui.panel;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
import io.github.tonywasher.joceanus.metis.help.MetisHelpModule;
import io.github.tonywasher.joceanus.metis.help.MetisHelpWindow;
import io.github.tonywasher.joceanus.metis.toolkit.MetisToolkit;
import io.github.tonywasher.joceanus.metis.viewer.MetisViewerWindow;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadCreateBackup;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadCreateDatabase;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadCreateTables;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadCreateXMLFile;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadId;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadLoadBackup;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadLoadDatabase;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadLoadXMLFile;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadPurgeDatabase;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadRenewSecurity;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadStoreDatabase;
import io.github.tonywasher.joceanus.prometheus.threads.PrometheusThreadUpdatePassword;
import io.github.tonywasher.joceanus.prometheus.toolkit.PrometheusToolkit;
import io.github.tonywasher.joceanus.prometheus.ui.PrometheusMenuId;
import io.github.tonywasher.joceanus.prometheus.ui.PrometheusUIResource;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusDataControl;
import io.github.tonywasher.joceanus.tethys.api.base.TethysUIComponent;
import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
import io.github.tonywasher.joceanus.tethys.api.dialog.TethysUIAlert;
import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIMainPanel;
import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIMenuBarManager;
import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIMenuBarManager.TethysUIMenuSubMenu;
import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBorderPaneManager;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThread;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadEvent;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadManager;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusManager;

/**
 * Main window for application.
 */
public abstract class PrometheusMainWindow
        implements TethysUIMainPanel {
    /**
     * Logger.
     */
    private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(PrometheusMainWindow.class);

    /**
     * Discard prompt.
     */
    private static final String PROMPT_DISCARD = PrometheusUIResource.PROMPT_DISCARD.getValue();

    /**
     * Close Dialog title.
     */
    private static final String TITLE_CLOSE = PrometheusUIResource.TITLE_CLOSE.getValue();

    /**
     * The Toolkit.
     */
    private MetisToolkit theToolkit;

    /**
     * The GUI Factory.
     */
    private TethysUIFactory<?> theGuiFactory;

    /**
     * The data view.
     */
    private PrometheusDataControl theView;

    /**
     * The panel.
     */
    private TethysUIBorderPaneManager thePanel;

    /**
     * The data menu.
     */
    private TethysUIMenuBarManager theMenuBar;

    /**
     * The Thread Manager.
     */
    private TethysUIThreadManager theThreadMgr;

    /**
     * The Started data window.
     */
    private MetisViewerWindow theDataWdw;

    /**
     * The Status window.
     */
    private TethysUIThreadStatusManager theStatusBar;

    /**
     * Constructor.
     */
    protected PrometheusMainWindow() {
    }

    /**
     * Get the data view.
     *
     * @return the data view
     */
    public PrometheusDataControl getView() {
        return theView;
    }

    @Override
    public TethysUIComponent getComponent() {
        return thePanel;
    }

    @Override
    public TethysUIMenuBarManager getMenuBar() {
        return theMenuBar;
    }

    /**
     * Build the main panel.
     *
     * @return the main panel
     * @throws OceanusException on error
     */
    protected abstract TethysUIComponent buildMainPanel() throws OceanusException;

    /**
     * Obtain the Help Module.
     *
     * @return the help module
     * @throws OceanusException on error
     */
    protected abstract MetisHelpModule getHelpModule() throws OceanusException;

    /**
     * Build the main window.
     *
     * @param pView    the Data view
     * @param pToolkit the toolkit
     * @throws OceanusException on error
     */
    public void buildMainWindow(final PrometheusDataControl pView,
                                final PrometheusToolkit pToolkit) throws OceanusException {
        /* Store the view */
        theView = pView;
        theToolkit = pToolkit.getToolkit();
        theGuiFactory = theToolkit.getGuiFactory();
        theThreadMgr = theToolkit.getThreadManager();

        /* Obtain the active profile */
        final OceanusProfile myTask = theView.getActiveTask();
        myTask.startTask("buildGUI");

        /* Create the panel */
        thePanel = theGuiFactory.paneFactory().newBorderPane();

        /* Build the Main Panel */
        final TethysUIComponent myMainPanel = buildMainPanel();

        /* Access the status bar and set to invisible */
        theStatusBar = theThreadMgr.getStatusManager();
        theStatusBar.setVisible(false);
        theThreadMgr.getEventRegistrar().addEventListener(TethysUIThreadEvent.THREADEND, e -> {
            setVisibility();
            theStatusBar.setVisible(false);
        });

        /* Create the panel */
        thePanel.setNorth(theStatusBar);
        thePanel.setCentre(myMainPanel);

        /* Build the Main Menu */
        buildMainMenu();

        /* Create the data window */
        theDataWdw = theToolkit.newViewerWindow();
        theDataWdw.getEventRegistrar().addEventListener(TethysUIEvent.WINDOWCLOSED, e -> theMenuBar.setEnabled(PrometheusMenuId.DATAVIEWER, true));

        /* Complete task */
        myTask.end();
    }

    /**
     * Build Main Menu.
     */
    protected void buildMainMenu() {
        /* Create the menu bar */
        theMenuBar = theGuiFactory.menuFactory().newMenuBar();

        /* Add Data Menu Items */
        TethysUIMenuSubMenu myMenu = theMenuBar.newSubMenu(PrometheusMenuId.DATA);
        addDataMenuItems(myMenu);

        /* Add Edit Menu Items */
        myMenu = theMenuBar.newSubMenu(PrometheusMenuId.EDIT);
        addEditMenuItems(myMenu);

        /* Add Backup Menu Items */
        myMenu = theMenuBar.newSubMenu(PrometheusMenuId.BACKUP);
        addBackupMenuItems(myMenu);

        /* Add Security Menu Items */
        myMenu = theMenuBar.newSubMenu(PrometheusMenuId.SECURITY);
        addSecurityMenuItems(myMenu);

        /* Add Help Menu items */
        myMenu = theMenuBar.newSubMenu(PrometheusMenuId.HELP);
        addHelpMenuItems(myMenu);
    }

    /**
     * Add Data Menu items.
     *
     * @param pMenu the menu
     */
    protected void addDataMenuItems(final TethysUIMenuSubMenu pMenu) {
        /* Add Standard Data Menu items */
        pMenu.newMenuItem(PrometheusThreadId.LOADDB, e -> loadDatabase());
        pMenu.newMenuItem(PrometheusThreadId.STOREDB, e -> storeDatabase());
        pMenu.newMenuItem(PrometheusThreadId.CREATEDB, e -> createDatabase());
        pMenu.newMenuItem(PrometheusThreadId.CREATEDBTABLES, e -> createDatabaseTables());
        pMenu.newMenuItem(PrometheusThreadId.PURGEDB, e -> purgeDatabase());
    }

    /**
     * Add EditMenu items.
     *
     * @param pMenu the menu
     */
    protected void addEditMenuItems(final TethysUIMenuSubMenu pMenu) {
        /* Add Standard Edit Menu items */
        pMenu.newMenuItem(PrometheusMenuId.UNDO, e -> undoLastEdit());
        pMenu.newMenuItem(PrometheusMenuId.RESET, e -> resetEdit());
    }

    /**
     * Add Backup Menu items.
     *
     * @param pMenu the menu
     */
    protected void addBackupMenuItems(final TethysUIMenuSubMenu pMenu) {
        /* Add Standard Backup menu items */
        pMenu.newMenuItem(PrometheusThreadId.CREATEBACKUP, e -> writeBackup());
        pMenu.newMenuItem(PrometheusThreadId.RESTOREBACKUP, e -> restoreBackup());
        pMenu.newMenuItem(PrometheusThreadId.CREATEXML, e -> createXmlBackup());
        pMenu.newMenuItem(PrometheusThreadId.CREATEXTRACT, e -> createXmlXtract());
        pMenu.newMenuItem(PrometheusThreadId.RESTOREXML, e -> loadXmlFile());
    }

    /**
     * Add Security Menu items.
     *
     * @param pMenu the menu
     */
    protected void addSecurityMenuItems(final TethysUIMenuSubMenu pMenu) {
        /* Add Standard Security menu items */
        pMenu.newMenuItem(PrometheusThreadId.CHANGEPASS, e -> updatePassword());
        pMenu.newMenuItem(PrometheusThreadId.RENEWSECURITY, e -> reNewSecurity());
    }

    /**
     * Add Help Menu items.
     *
     * @param pMenu the menu
     */
    protected void addHelpMenuItems(final TethysUIMenuSubMenu pMenu) {
        /* Create the menu items */
        pMenu.newMenuItem(PrometheusMenuId.SHOWHELP, e -> displayHelp());
        pMenu.newMenuItem(PrometheusMenuId.DATAVIEWER, e -> displayViewerMgr());
        pMenu.newMenuItem(PrometheusMenuId.ABOUT, e -> displayAbout());
    }

    /**
     * Make the frame.
     */
    public void makeFrame() {
        /* Set visibility */
        setVisibility();
    }

    /**
     * Set visibility.
     */
    public void setVisibility() {
        /* Determine whether we have any updates */
        final boolean hasUpdates = hasUpdates();
        final boolean hasChanges = hasChanges();
        final boolean hasControl = getView().getData().getControl() != null;

        /* Note whether we have a worker thread */
        final boolean hasWorker = hasWorker();

        /* Disable menus if we have a worker thread */
        theMenuBar.setEnabled(PrometheusMenuId.DATA, !hasWorker);
        theMenuBar.setEnabled(PrometheusMenuId.BACKUP, !hasWorker);
        theMenuBar.setEnabled(PrometheusMenuId.SECURITY, !hasWorker && hasControl);

        /* If we have changes but no updates enable the undo/reset options */
        if (hasWorker || !hasControl) {
            theMenuBar.setEnabled(PrometheusMenuId.EDIT, false);
        } else {
            theMenuBar.setEnabled(PrometheusMenuId.EDIT, !hasUpdates && hasChanges);
        }

        /* If we have changes disable the create backup options */
        final boolean allowBackups = !hasChanges && !hasUpdates && hasControl;
        theMenuBar.setEnabled(PrometheusThreadId.CREATEBACKUP, allowBackups);
        theMenuBar.setEnabled(PrometheusThreadId.CREATEXML, allowBackups);
        theMenuBar.setEnabled(PrometheusThreadId.CREATEXTRACT, allowBackups);

        /* If we have changes disable the security options */
        final boolean allowSecurity = !hasChanges && !hasUpdates;
        theMenuBar.setEnabled(PrometheusThreadId.CHANGEPASS, allowSecurity);
        theMenuBar.setEnabled(PrometheusThreadId.RENEWSECURITY, allowSecurity);

        /* If we have updates disable the load backup/database option */
        theMenuBar.setEnabled(PrometheusThreadId.RESTOREBACKUP, !hasUpdates);
        theMenuBar.setEnabled(PrometheusThreadId.RESTOREXML, !hasUpdates);
        theMenuBar.setEnabled(PrometheusThreadId.LOADDB, !hasUpdates);

        /* If we have updates or no changes disable the save database */
        theMenuBar.setEnabled(PrometheusThreadId.STOREDB, !hasUpdates && hasChanges);
    }

    /**
     * Has the underlying data got changes.
     *
     * @return true/false
     */
    protected final boolean hasChanges() {
        return theView.getData().hasUpdates();
    }

    /**
     * Has the window got updates.
     *
     * @return true/false
     */
    protected abstract boolean hasUpdates();

    /**
     * Is a worker active.
     *
     * @return true/false
     */
    protected final boolean hasWorker() {
        return theThreadMgr.hasWorker();
    }

    /**
     * Start a thread.
     *
     * @param pThread the thread to start
     */
    protected void startThread(final TethysUIThread<?> pThread) {
        /* Execute the thread and record it */
        theThreadMgr.startThread(pThread);

        /* Adjust visible threads */
        theStatusBar.setVisible(true);
        setVisibility();
    }

    /**
     * Load Database.
     */
    private void loadDatabase() {
        /* Create the worker thread */
        final PrometheusThreadLoadDatabase myThread = new PrometheusThreadLoadDatabase(theView);
        startThread(myThread);
    }

    /**
     * Store Database.
     */
    private void storeDatabase() {
        /* Create the worker thread */
        final PrometheusThreadStoreDatabase myThread = new PrometheusThreadStoreDatabase(theView);
        startThread(myThread);
    }

    /**
     * Create Database.
     */
    private void createDatabase() {
        /* Create the worker thread */
        final PrometheusThreadCreateDatabase myThread = new PrometheusThreadCreateDatabase(theView);
        startThread(myThread);
    }

    /**
     * Create Database Tables.
     */
    private void createDatabaseTables() {
        /* Create the worker thread */
        final PrometheusThreadCreateTables myThread = new PrometheusThreadCreateTables(theView);
        startThread(myThread);
    }

    /**
     * Purge Database.
     */
    private void purgeDatabase() {
        /* Create the worker thread */
        final PrometheusThreadPurgeDatabase myThread = new PrometheusThreadPurgeDatabase(theView);
        startThread(myThread);
    }

    /**
     * UnDo last edit.
     */
    private void undoLastEdit() {
        /* Create a new profile */
        final OceanusProfile myTask = theView.getNewProfile("unDoLastEdit");

        /* Undo the last edit */
        theView.undoLastChange();

        /* Adjust visibility */
        setVisibility();

        /* Complete the task */
        myTask.end();
    }

    /**
     * reset Edit changes.
     */
    private void resetEdit() {
        /* Create a new profile */
        final OceanusProfile myTask = theView.getNewProfile("resetEdit");

        /* Reset the edit View */
        theView.resetChanges();

        /* Adjust visibility */
        setVisibility();

        /* Complete the task */
        myTask.end();
    }

    /**
     * Write Backup.
     */
    private void writeBackup() {
        /* Create the worker thread */
        final PrometheusThreadCreateBackup myThread = new PrometheusThreadCreateBackup(theView);
        startThread(myThread);
    }

    /**
     * Restore Backup.
     */
    private void restoreBackup() {
        /* Create the worker thread */
        final PrometheusThreadLoadBackup myThread = new PrometheusThreadLoadBackup(theView);
        startThread(myThread);
    }

    /**
     * Create XML Backup file.
     */
    private void createXmlBackup() {
        /* Create the worker thread */
        final PrometheusThreadCreateXMLFile myThread = new PrometheusThreadCreateXMLFile(theView, true);
        startThread(myThread);
    }

    /**
     * Create XML Extract file.
     */
    private void createXmlXtract() {
        /* Create the worker thread */
        final PrometheusThreadCreateXMLFile myThread = new PrometheusThreadCreateXMLFile(theView, false);
        startThread(myThread);
    }

    /**
     * Load XML Backup file.
     */
    private void loadXmlFile() {
        /* Create the worker thread */
        final PrometheusThreadLoadXMLFile myThread = new PrometheusThreadLoadXMLFile(theView);
        startThread(myThread);
    }

    /**
     * Update password.
     */
    private void updatePassword() {
        /* Create the worker thread */
        final PrometheusThreadUpdatePassword myThread = new PrometheusThreadUpdatePassword(theView);
        startThread(myThread);
    }

    /**
     * ReNew Security.
     */
    private void reNewSecurity() {
        /* Create the worker thread */
        final PrometheusThreadRenewSecurity myThread = new PrometheusThreadRenewSecurity(theView);
        startThread(myThread);
    }

    /**
     * Display ViewerMgr.
     */
    private void displayViewerMgr() {
        /* Disable the menu item */
        theMenuBar.setEnabled(PrometheusMenuId.DATAVIEWER, false);

        /* Display it */
        theDataWdw.showDialog();
    }

    /**
     * Display Help.
     */
    private void displayHelp() {
        try {
            /* Create the help window */
            final MetisHelpWindow myHelpWdw = theToolkit.newHelpWindow();
            myHelpWdw.setModule(getHelpModule());

            /* Listen for its closure */
            myHelpWdw.getEventRegistrar().addEventListener(e -> {
                theMenuBar.setEnabled(PrometheusMenuId.SHOWHELP, true);
                myHelpWdw.hideDialog();
            });

            /* Disable the menu item */
            theMenuBar.setEnabled(PrometheusMenuId.SHOWHELP, false);

            /* Display it */
            myHelpWdw.showDialog();

        } catch (OceanusException e) {
            LOGGER.error("Failed to start Help Window", e);
        }
    }

    /**
     * Display About Box.
     */
    protected abstract void displayAbout();

    @Override
    public boolean handleAppClose() {
        /* If we have updates or changes */
        if (hasUpdates() || hasChanges()) {
            /* Ask whether to continue */
            final TethysUIAlert myAlert = theGuiFactory.dialogFactory().newAlert();
            myAlert.setMessage(PROMPT_DISCARD);
            myAlert.setTitle(TITLE_CLOSE);
            final boolean myResult = myAlert.confirmYesNo();

            /* Ignore if not confirmed */
            if (!myResult) {
                return false;
            }
        }

        /* terminate the executor */
        theThreadMgr.shutdown();
        return true;
    }
}