ThemisUIMainPanel.java

/*
 * Themis: Java Project Framework
 * Copyright 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.themis.gui.launch;

import io.github.tonywasher.joceanus.metis.toolkit.MetisToolkit;
import io.github.tonywasher.joceanus.metis.ui.MetisIcon;
import io.github.tonywasher.joceanus.metis.viewer.MetisViewerEntry;
import io.github.tonywasher.joceanus.metis.viewer.MetisViewerManager;
import io.github.tonywasher.joceanus.metis.viewer.MetisViewerStandardEntry;
import io.github.tonywasher.joceanus.metis.viewer.MetisViewerWindow;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
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.button.TethysUIButton;
import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButtonFactory;
import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControlFactory;
import io.github.tonywasher.joceanus.tethys.api.dialog.TethysUIAboutBox;
import io.github.tonywasher.joceanus.tethys.api.dialog.TethysUIDialogFactory;
import io.github.tonywasher.joceanus.tethys.api.dialog.TethysUIDirectorySelector;
import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
import io.github.tonywasher.joceanus.tethys.api.factory.TethysUILogTextArea;
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.pane.TethysUIBoxPaneManager;
import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIPaneFactory;
import io.github.tonywasher.joceanus.tethys.api.pane.TethysUITabPaneManager;
import io.github.tonywasher.joceanus.tethys.api.pane.TethysUITabPaneManager.TethysUITabItem;
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;
import io.github.tonywasher.joceanus.themis.exc.ThemisIOException;
import io.github.tonywasher.joceanus.themis.gui.base.ThemisUIResource;
import io.github.tonywasher.joceanus.themis.gui.launch.ThemisUIThread.ThemisUIThreadData;
import io.github.tonywasher.joceanus.themis.gui.reference.ThemisUIRefPanel;
import io.github.tonywasher.joceanus.themis.gui.source.ThemisUISourcePanel;
import io.github.tonywasher.joceanus.themis.gui.stats.ThemisUIStatsPanel;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisDataResource;
import io.github.tonywasher.joceanus.themis.parser.project.ThemisProject;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverProject;
import io.github.tonywasher.joceanus.themis.stats.ThemisStatsProject;

import java.io.File;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

/**
 * Main panel.
 */
public class ThemisUIMainPanel
        implements TethysUIMainPanel, ThemisUIThreadData {
    /**
     * Default Project Preference.
     */
    private static final String PREF_PROJECT = "DefaultLocation";

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

    /**
     * Source Panel.
     */
    private final ThemisUISourcePanel theSource;

    /**
     * Stats Panel.
     */
    private final ThemisUIStatsPanel theStats;

    /**
     * Reference Panel.
     */
    private final ThemisUIRefPanel theRefs;

    /**
     * The Source Tab.
     */
    private final TethysUITabItem theSourceTab;

    /**
     * The Stats Tab.
     */
    private final TethysUITabItem theStatsTab;

    /**
     * The Refs Tab.
     */
    private final TethysUITabItem theRefsTab;

    /**
     * The Log Tab.
     */
    private final TethysUITabItem theLogTab;

    /**
     * The log sink.
     */
    private final TethysUILogTextArea theLogSink;

    /**
     * The ProjectButton.
     */
    private final TethysUIButton theProjectButton;

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

    /**
     * The ProjectButton.
     */
    private final TethysUIBorderPaneManager thePanel;

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

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

    /**
     * The Project control.
     */
    private final TethysUIBoxPaneManager theProjectControl;

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

    /**
     * The error viewer entry.
     */
    private final MetisViewerEntry theErrorEntry;

    /**
     * The solver viewer entry.
     */
    private final MetisViewerEntry theSolverEntry;

    /**
     * The stats viewer entry.
     */
    private final MetisViewerEntry theStatsEntry;

    /**
     * The source viewer entry.
     */
    private final MetisViewerEntry theSourceEntry;

    /**
     * The aboutBox.
     */
    private TethysUIAboutBox theAboutBox;

    /**
     * Constructor.
     *
     * @param pFactory the gui factory
     * @throws OceanusException on error
     */
    public ThemisUIMainPanel(final TethysUIFactory<?> pFactory) throws OceanusException {
        /* Store guiFactory */
        theGuiFactory = pFactory;
        final MetisToolkit myToolkit = new MetisToolkit(pFactory, false);

        /* Create the subPanels */
        theRefs = new ThemisUIRefPanel(theGuiFactory);
        theStats = new ThemisUIStatsPanel(theGuiFactory);
        theSource = new ThemisUISourcePanel(theGuiFactory);

        /* Create the tabs */
        final TethysUIPaneFactory myPanes = theGuiFactory.paneFactory();
        final TethysUITabPaneManager myTabs = myPanes.newTabPane();
        theRefsTab = myTabs.addTabItem(ThemisUIResource.TAB_REFERENCES.getValue(), theRefs);
        theStatsTab = myTabs.addTabItem(ThemisUIResource.TAB_STATS.getValue(), theStats);
        theSourceTab = myTabs.addTabItem(ThemisUIResource.TAB_SOURCE.getValue(), theSource);

        /* Hide tabs */
        theRefsTab.setVisible(false);
        theStatsTab.setVisible(false);
        theSourceTab.setVisible(false);

        /* Create the log tab */
        theLogSink = theGuiFactory.getLogSink();
        theLogTab = myTabs.addTabItem(ThemisUIResource.TAB_LOG.getValue(), theLogSink);
        theLogSink.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> theLogTab.setVisible(true));
        theLogSink.getEventRegistrar().addEventListener(TethysUIEvent.WINDOWCLOSED, e -> theLogTab.setVisible(false));
        theLogTab.setVisible(theLogSink.isActive());

        /* Create the project selection panel */
        final TethysUIControlFactory myControls = theGuiFactory.controlFactory();
        final TethysUIButtonFactory<?> myButtons = theGuiFactory.buttonFactory();
        final TethysUIBoxPaneManager myProjectSelect = myPanes.newHBoxPane();
        myProjectSelect.addNode(myControls.newLabel(ThemisUIResource.PROMPT_PROJECT.getValue()));
        theProjectButton = myButtons.newButton();
        theProjectButton.setTextOnly();
        myProjectSelect.addNode(theProjectButton);
        theProjectButton.getEventRegistrar().addEventListener(e -> selectProject());
        theProjectButton.setText(ThemisUIResource.PROMPT_NONE.getValue());

        /* Configure refresh button */
        final TethysUIButton myRefreshButton = myButtons.newButton();
        MetisIcon.configureButton(myRefreshButton);
        myRefreshButton.setIcon(ThemisUIIcon.REFRESH);
        myRefreshButton.setToolTip(ThemisUIResource.TOOLTIP_REFRESH.getValue());
        myRefreshButton.getEventRegistrar().addEventListener(TethysUIEvent.PRESSED,
                e -> handleNewProject(getDefaultLocation()));

        /* create the overall project select panel */
        theProjectControl = myPanes.newHBoxPane();
        theProjectControl.addSpacer();
        theProjectControl.addNode(myProjectSelect);
        theProjectControl.addSpacer();
        theProjectControl.addNode(myRefreshButton);
        theProjectControl.addStrut();

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

        /* create the overall project select panel */
        final TethysUIBoxPaneManager myBanner = myPanes.newVBoxPane();
        myBanner.addNode(theProjectControl);
        myBanner.addNode(theStatusBar);

        /* create the overall panel */
        thePanel = myPanes.newBorderPane();
        thePanel.setNorth(myBanner);
        thePanel.setCentre(myTabs);

        /* Create the menu bar */
        theMenuBar = theGuiFactory.menuFactory().newMenuBar();
        final TethysUIMenuSubMenu myMenu = theMenuBar.newSubMenu(ThemisUIMenuId.HELP);
        myMenu.newMenuItem(ThemisUIMenuId.DATAVIEWER, e -> displayViewerMgr());
        myMenu.newMenuItem(ThemisUIMenuId.ABOUT, e -> displayAbout());

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

        /* Create viewer locations */
        final MetisViewerManager myViewer = myToolkit.getViewerManager();
        theErrorEntry = myViewer.getStandardEntry(MetisViewerStandardEntry.ERROR);
        theSolverEntry = myViewer.newEntry(ThemisDataResource.DATA_SOLVER.getValue());
        theStatsEntry = myViewer.newEntry(ThemisDataResource.DATA_STATS.getValue());
        theSourceEntry = myViewer.newEntry(ThemisDataResource.DATA_SOURCE.getValue());

        /* Handle the default location */
        final File myLocation = getDefaultLocation();
        if (myLocation != null) {
            handleNewProject(myLocation);
        }
    }

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

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

    /**
     * Handle select project.
     */
    void selectProject() {
        /* Determine initial directory */
        final File myInit = new File(System.getProperty("user.home"));

        /* Determine the name of the directory to load */
        final TethysUIDialogFactory myDialogs = theGuiFactory.dialogFactory();
        final TethysUIDirectorySelector myDialog = myDialogs.newDirectorySelector();
        myDialog.setTitle(ThemisUIResource.SELECT_PROJECT.getValue());
        myDialog.setInitialDirectory(myInit);
        final File myFile = myDialog.selectDirectory();

        /* If we selected a file */
        if (myFile != null) {
            /* Handle the new project */
            handleNewProject(myFile);
        }
    }

    /**
     * Handle the new project.
     *
     * @param pProjectDir the new project directory
     */
    private void handleNewProject(final File pProjectDir) {
        /* Adjust visibility */
        setVisibility(true);

        /* Create and start thread */
        final ThemisUIThread myLoader = new ThemisUIThread(this, pProjectDir);
        theThreadMgr.startThread(myLoader);
    }

    /**
     * Set panel visibility.
     *
     * @param pLoading are we loading?
     */
    private void setVisibility(final boolean pLoading) {
        /* Set tabs visibility */
        theRefsTab.setVisible(!pLoading);
        theStatsTab.setVisible(!pLoading);
        theSourceTab.setVisible(!pLoading);

        /* Update selection bar and menuBar */
        theProjectControl.setVisible(!pLoading);
        theStatusBar.setVisible(pLoading);
        theMenuBar.setEnabled(ThemisUIMenuId.HELP, !pLoading);

        /* Set error entry visibility */
        theErrorEntry.setVisible(theErrorEntry.getObject() != null);
    }

    @Override
    public void setNewData(final ThemisUIData pData) {
        /* Update source */
        final ThemisProject myProject = pData.getParsedProject();
        theSource.setCurrentProject(myProject);
        theProjectButton.setText(myProject.toString());
        theSourceEntry.setObject(myProject);

        /* Update references */
        final ThemisSolverProject mySolved = pData.getSolvedProject();
        theRefs.setCurrentProject(mySolved);
        theSolverEntry.setObject(mySolved);

        /* Resolve stats */
        final ThemisStatsProject myStats = pData.getProjectStats();
        theStats.setCurrentProject(myStats);
        theStatsEntry.setObject(myStats);

        /* Save details */
        final OceanusException myError = storeDefaultLocation(pData.getProjectDir());
        if (myError != null) {
            writeErrorToLog(myError);
        }
    }

    /**
     * Write error to log.
     *
     * @param pError the error
     */
    private void writeErrorToLog(final OceanusException pError) {
        theLogSink.writeLogMessage(pError.getMessage());
        theLogTab.setVisible(true);
    }

    /**
     * Obtain the default location.
     *
     * @return the default location
     */
    private File getDefaultLocation() {
        final Preferences myPreferences = deriveHandle();
        final String myLocation = myPreferences.get(PREF_PROJECT, null);
        return myLocation == null ? null : new File(myLocation);
    }

    /**
     * Store the default location.
     *
     * @param pLocation the default location
     * @return exception or null
     */
    private OceanusException storeDefaultLocation(final File pLocation) {
        /* Protect against exceptions */
        try {
            final Preferences myPreferences = deriveHandle();
            myPreferences.put(PREF_PROJECT, pLocation.getAbsolutePath());
            myPreferences.flush();
            return null;
        } catch (BackingStoreException e) {
            return new ThemisIOException("Failed to save preference", e);
        }
    }

    /**
     * Derive handle for node.
     *
     * @return the class name
     */
    private Preferences deriveHandle() {
        /* Obtain the class name */
        final Class<?> myClass = this.getClass();
        String myName = myClass.getCanonicalName();

        /* Obtain the package name */
        final String myPackage = myClass.getPackage().getName();

        /* Strip off the package name */
        myName = myName.substring(myPackage.length() + 1);

        /* Derive the handle */
        final Preferences myHandle = Preferences.userNodeForPackage(myClass);
        return myHandle.node(myName);
    }

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

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

    /**
     * Display the about box.
     */
    private void displayAbout() {
        /* Create about box if it does not exist */
        if (theAboutBox == null) {
            theAboutBox = theGuiFactory.dialogFactory().newAboutBox();
        }
        theAboutBox.showDialog();
    }
}