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.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.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.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.themis.exc.ThemisIOException;
import io.github.tonywasher.joceanus.themis.gui.base.ThemisUIResource;
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.ThemisParser;
import io.github.tonywasher.joceanus.themis.parser.proj.ThemisProject;
import io.github.tonywasher.joceanus.themis.solver.ThemisSolver;
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 {
    /**
     * 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 ProjectButton.
     */
    private final TethysUIBorderPaneManager thePanel;

    /**
     * Constructor.
     *
     * @param pFactory the GuiFactory
     * @throws OceanusException on error
     */
    public ThemisUIMainPanel(final TethysUIFactory<?> pFactory) throws OceanusException {
        /* Store guiFactory */
        theGuiFactory = pFactory;

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

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

        /* Hide tabs */
        theSourceTab.setVisible(false);
        theStatsTab.setVisible(false);
        theRefsTab.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("None");

        /* create the overall project select panel */
        final TethysUIBoxPaneManager myProjectControl = myPanes.newHBoxPane();
        myProjectControl.addSpacer();
        myProjectControl.addNode(myProjectSelect);
        myProjectControl.addSpacer();

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

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

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

    /**
     * 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) {
        /* Hide tabs */
        theSourceTab.setVisible(false);
        theRefsTab.setVisible(false);
        theStatsTab.setVisible(false);

        /* Save details */
        OceanusException myError = storeDefaultLocation(pProjectDir);
        if (myError != null) {
            writeErrorToLog(myError);
            return;
        }

        /* Parse the project */
        final ThemisParser myParser = new ThemisParser(pProjectDir);
        myError = myParser.getError();
        if (myError == null) {
            final ThemisProject myProject = myParser.getProject();
            theSource.setCurrentProject(myProject);
            theProjectButton.setText(myProject.toString());
            theSourceTab.setVisible(true);
        }

        /* If we parsed successfully */
        if (myError == null) {
            /* Resolve references */
            final ThemisSolverProject myProject = new ThemisSolverProject(myParser);
            final ThemisSolver mySolver = new ThemisSolver(myProject);
            myError = mySolver.getError();
            if (myError == null) {
                theRefs.setCurrentProject(myProject);
                theRefsTab.setVisible(true);
            }
        }

        /* If we parsed successfully */
        if (myError == null) {
            /* Resolve stats */
            final ThemisStatsProject myStats = new ThemisStatsProject(myParser);
            myError = myStats.getError();
            if (myError == null) {
                theStats.setCurrentProject(myStats);
                theStatsTab.setVisible(true);
            }
        }

        /* Display any error */
        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);
    }
}