ThemisDSMPanel.java

/*
 * Themis: Java Project 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.themis.lethe.ui;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
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.button.TethysUIScrollButtonManager;
import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControlFactory;
import io.github.tonywasher.joceanus.tethys.api.control.TethysUIHTMLManager;
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.TethysUIScrollMenu;
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.lethe.analysis.ThemisAnalysisProject;
import io.github.tonywasher.joceanus.themis.lethe.dsm.ThemisDSMModule;
import io.github.tonywasher.joceanus.themis.lethe.dsm.ThemisDSMPackage;
import io.github.tonywasher.joceanus.themis.lethe.dsm.ThemisDSMProject;
import io.github.tonywasher.joceanus.themis.lethe.dsm.ThemisDSMReport;
import io.github.tonywasher.joceanus.themis.lethe.statistics.ThemisStatsParser;
import io.github.tonywasher.joceanus.themis.lethe.statistics.ThemisStatsProject;

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

/**
 * DSMPanel.
 */
public class ThemisDSMPanel
        implements TethysUIMainPanel {
    /**
     * The GUI Factory.
     */
    private final TethysUIFactory<?> theGuiFactory;

    /**
     * The Tab Manager Pane.
     */
    private final TethysUITabPaneManager theTabPane;

    /**
     * The Matrix HTML Pane.
     */
    private final TethysUIHTMLManager theMatrixHTML;

    /**
     * The Dependency HTML Pane.
     */
    private final TethysUIHTMLManager theDependencyHTML;

    /**
     * The Dependency Tab.
     */
    private final TethysUITabItem theDependencyTab;

    /**
     * The Statistics Panel.
     */
    private final ThemisStatsPanel theStatsPanel;

    /**
     * The Source Panel.
     */
    private final ThemisSourcePanel theSourcePanel;

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

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

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

    /**
     * The ModuleButton.
     */
    private final TethysUIScrollButtonManager<ThemisDSMModule> theModuleButton;

    /**
     * The FromButton.
     */
    private final TethysUIScrollButtonManager<ThemisDSMPackage> theFromButton;

    /**
     * The ToButton.
     */
    private final TethysUIScrollButtonManager<ThemisDSMPackage> theToButton;

    /**
     * The current project.
     */
    private ThemisDSMProject theProject;

    /**
     * The current module.
     */
    private ThemisDSMModule theModule;

    /**
     * The current from package.
     */
    private ThemisDSMPackage theFrom;

    /**
     * The error.
     */
    private OceanusException theError;

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

        /* Create the HTML Panes */
        final TethysUIControlFactory myControls = theGuiFactory.controlFactory();
        final TethysUIButtonFactory<?> myButtons = theGuiFactory.buttonFactory();
        theDependencyHTML = myControls.newHTMLManager();
        theDependencyHTML.setCSSContent(ThemisDSMStyleSheet.CSS_DSM);
        theMatrixHTML = myControls.newHTMLManager();
        theMatrixHTML.setCSSContent(ThemisDSMStyleSheet.CSS_DSM);
        theMatrixHTML.getEventRegistrar().addEventListener(TethysUIEvent.BUILDPAGE, e -> {
            processReference(e.getDetails(String.class));
            e.consume();
        });

        /* Create the module selection panel */
        final TethysUIPaneFactory myPanes = theGuiFactory.paneFactory();
        final TethysUIBoxPaneManager myModuleSelect = myPanes.newHBoxPane();
        myModuleSelect.addNode(myControls.newLabel("Module:"));
        theModuleButton = myButtons.newScrollButton(ThemisDSMModule.class);
        myModuleSelect.addNode(theModuleButton);
        theModuleButton.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewModule());
        theModuleButton.setMenuConfigurator(e -> buildModuleMenu());

        /* Create the project selection panel */
        final TethysUIBoxPaneManager myProjectSelect = myPanes.newHBoxPane();
        myProjectSelect.addNode(myControls.newLabel("Project:"));
        theProjectButton = myButtons.newButton();
        theProjectButton.setTextOnly();
        myProjectSelect.addNode(theProjectButton);
        theProjectButton.getEventRegistrar().addEventListener(e -> selectProject());

        /* create the overall matrix status panel */
        final TethysUIBoxPaneManager myMatrixControl = myPanes.newHBoxPane();
        myMatrixControl.addSpacer();
        myMatrixControl.addNode(myProjectSelect);
        myMatrixControl.addSpacer();
        myMatrixControl.addNode(myModuleSelect);
        myMatrixControl.addSpacer();

        /* create the matrix panel */
        final TethysUIBorderPaneManager myMatrixPanel = myPanes.newBorderPane();
        myMatrixPanel.setCentre(theMatrixHTML);
        myMatrixPanel.setNorth(myMatrixControl);

        /* Create the from selection panel */
        final TethysUIBoxPaneManager myFromSelect = myPanes.newHBoxPane();
        myFromSelect.addNode(myControls.newLabel("From:"));
        theFromButton = myButtons.newScrollButton(ThemisDSMPackage.class);
        myFromSelect.addNode(theFromButton);
        theFromButton.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewFrom());
        theFromButton.setMenuConfigurator(e -> buildFromMenu());

        /* Create the project selection panel */
        final TethysUIBoxPaneManager myToSelect = myPanes.newHBoxPane();
        myToSelect.addNode(myControls.newLabel("To:"));
        theToButton = myButtons.newScrollButton(ThemisDSMPackage.class);
        myToSelect.addNode(theToButton);
        theToButton.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewTo());
        theToButton.setMenuConfigurator(e -> buildToMenu());

        /* create the overall matrix status panel */
        final TethysUIBoxPaneManager myDependencyControl = myPanes.newHBoxPane();
        myDependencyControl.addSpacer();
        myDependencyControl.addNode(myFromSelect);
        myDependencyControl.addSpacer();
        myDependencyControl.addNode(myToSelect);
        myDependencyControl.addSpacer();

        /* create the matrix panel */
        final TethysUIBorderPaneManager myDependencyPanel = myPanes.newBorderPane();
        myDependencyPanel.setCentre(theDependencyHTML);
        myDependencyPanel.setNorth(myDependencyControl);

        /* Create the TabPane */
        theTabPane = myPanes.newTabPane();
        theTabPane.addTabItem("Matrix", myMatrixPanel);
        theDependencyTab = theTabPane.addTabItem("Dependencies", myDependencyPanel);

        /* Create the Stats panel */
        theStatsPanel = new ThemisStatsPanel(theGuiFactory);
        theTabPane.addTabItem("Stats", theStatsPanel.getComponent());

        /* Create the Source panel */
        theSourcePanel = new ThemisSourcePanel(theGuiFactory);
        theTabPane.addTabItem("Source", theSourcePanel.getComponent());

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

        /* Initialise status */
        theProjectButton.setText("None");
        theDependencyTab.setVisible(false);
        theModuleButton.setEnabled(false);
        theFromButton.setEnabled(false);
        theToButton.setEnabled(false);

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

    @Override
    public TethysUITabPaneManager getComponent() {
        return theTabPane;
    }

    /**
     * Handle select project.
     */
    void selectProject() {
        /* Determine initial directory */
        File myInit = getDefaultLocation();
        if (myInit == null) {
            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("Select Project");
        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) {
        /* Parse the project*/
        theError = null;
        final ThemisDSMProject myProject = new ThemisDSMProject(pProjectDir);
        if (myProject.getError() != null) {
            theError = myProject.getError();

        } else if (myProject.hasModules()) {
            /* Save details */
            storeDefaultLocation(pProjectDir);

            /* Store details */
            theProject = myProject;
            theProjectButton.setText(theProject.toString());

            /* Set the new module */
            processNewModule(theProject.getDefaultModule());

            /* Load statistics */
            handleNewStats(pProjectDir);
        }

        /* Display the error */
        if (theError != null) {
            theLogSink.writeLogMessage(theError.getMessage());
            theLogTab.setVisible(true);
        }
    }

    /**
     * Handle the new project.
     *
     * @param pProjectDir the new project directory
     */
    private void handleNewStats(final File pProjectDir) {
        /* Analyse source of project */
        final ThemisAnalysisProject myProj = new ThemisAnalysisProject(pProjectDir);
        if (myProj.getError() != null) {
            theError = myProj.getError();
        } else {
            /* Parse the base project */
            final ThemisStatsParser myParser = new ThemisStatsParser();
            final ThemisStatsProject myProject = myParser.parseProject(myProj);
            theStatsPanel.initialiseTree(myProject);
            theSourcePanel.initialiseTree(myProj);
        }
    }

    /**
     * Handle the new module.
     */
    private void handleNewModule() {
        processNewModule(theModuleButton.getValue());
    }

    /**
     * Process new module.
     *
     * @param pModule the new Module
     */
    private void processNewModule(final ThemisDSMModule pModule) {
        /* Store the module */
        theModule = pModule;
        theModuleButton.setValue(pModule);
        theModuleButton.setEnabled(true);

        /* Build matrix report */
        final String myDoc = ThemisDSMReport.reportOnModule(theModule);
        theMatrixHTML.setHTMLContent(myDoc, "");

        /* Process the new from Package */
        processNewFrom(theModule.getDefaultPackage());
    }

    /**
     * Build the module menu.
     */
    private void buildModuleMenu() {
        /* Access builder */
        final TethysUIScrollMenu<ThemisDSMModule> myBuilder = theModuleButton.getMenu();
        myBuilder.removeAllItems();

        /* Loop through to add each module */
        for (ThemisDSMModule myModule : theProject.listModules()) {
            myBuilder.addItem(myModule);
        }
    }

    /**
     * Handle the new fromPackage.
     */
    private void handleNewFrom() {
        processNewFrom(theFromButton.getValue());
    }

    /**
     * Process new module.
     *
     * @param pPackage the new fromPackage
     */
    private void processNewFrom(final ThemisDSMPackage pPackage) {
        /* Store the module */
        theFrom = pPackage;
        theFromButton.setValue(pPackage);
        theFromButton.setEnabled(true);
        theDependencyTab.setVisible(true);

        /* Process the new from Package */
        processNewTo(theFrom.getDefaultReference());
    }

    /**
     * Build the from menu.
     */
    private void buildFromMenu() {
        /* Access builder */
        final TethysUIScrollMenu<ThemisDSMPackage> myBuilder = theFromButton.getMenu();
        myBuilder.removeAllItems();

        /* Loop through to add each package */
        for (ThemisDSMPackage myPackage : theModule.listPackages()) {
            if (myPackage.hasReferences()) {
                myBuilder.addItem(myPackage);
            }
        }
    }

    /**
     * Handle the new toPackage.
     */
    private void handleNewTo() {
        processNewTo(theToButton.getValue());
    }

    /**
     * Process new toPackage.
     *
     * @param pPackage the new toPackage
     */
    private void processNewTo(final ThemisDSMPackage pPackage) {
        /* Store the package */
        theToButton.setValue(pPackage);
        theToButton.setEnabled(true);

        /* Build matrix report */
        final String myDoc = ThemisDSMReport.reportOnPackageLinks(theFrom, pPackage);
        theDependencyHTML.setHTMLContent(myDoc, "");
    }

    /**
     * Build the to menu.
     */
    private void buildToMenu() {
        /* Access builder */
        final TethysUIScrollMenu<ThemisDSMPackage> myBuilder = theToButton.getMenu();
        myBuilder.removeAllItems();

        /* Loop through to add each package */
        for (ThemisDSMPackage myPackage : theFrom.listReferences()) {
            myBuilder.addItem(myPackage);
        }
    }

    /**
     * Process the reference.
     *
     * @param pReference the reference
     */
    private void processReference(final String pReference) {
        /* Split on the "-" */
        final String[] myTokens = pReference.split(ThemisDSMReport.SEP_REF);
        if (myTokens.length == 2) {
            /* Access the relevant packages */
            final ThemisDSMPackage myFrom = theModule.getIndexedPackage(ThemisDSMReport.getIndexForKey(myTokens[0]));
            final ThemisDSMPackage myTo = theModule.getIndexedPackage(ThemisDSMReport.getIndexForKey(myTokens[1]));

            /* Set the new details */
            processNewFrom(myFrom);
            processNewTo(myTo);

            /* Select the tab */
            theDependencyTab.selectItem();
        }
    }

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

    /**
     * Store the default location.
     *
     * @param pLocation the default location
     */
    private void storeDefaultLocation(final File pLocation) {
        /* Protect against exceptions */
        try {
            final Preferences myPreferences = deriveHandle();
            myPreferences.put("DefaultProject", pLocation.getAbsolutePath());
            myPreferences.flush();
        } catch (BackingStoreException e) {
            theError = 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);
    }
}