ThemisUIRefDocument.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.reference;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.gui.base.ThemisUIBaseDocument;
import io.github.tonywasher.joceanus.themis.gui.base.ThemisUIHTMLAttr;
import io.github.tonywasher.joceanus.themis.gui.base.ThemisUIHTMLTag;
import io.github.tonywasher.joceanus.themis.gui.base.ThemisUIResource;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverModule;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverPackage;
import org.w3c.dom.Element;

import java.util.Map;

/**
 * Document Builder for sibling reference.
 */
public class ThemisUIRefDocument
        extends ThemisUIBaseDocument {
    /**
     * The active module.
     */
    private Map<String, ThemisSolverPackage> thePackageMap;

    /**
     * The family builder.
     */
    private final ThemisUIRefFamily theFamily;

    /**
     * The local builder.
     */
    private final ThemisUIRefLocal theLocal;

    /**
     * The links builder.
     */
    private final ThemisUIRefLinks theLinks;

    /**
     * The default.
     */
    private ThemisSolverPackage theDefault;

    /**
     * Constructor.
     *
     * @throws OceanusException on error
     */
    ThemisUIRefDocument() throws OceanusException {
        /* Create builders */
        theFamily = new ThemisUIRefFamily(this);
        theLocal = new ThemisUIRefLocal(this);
        theLinks = new ThemisUIRefLinks(this);
    }

    /**
     * Set the module.
     *
     * @param pModule the module
     */
    void setModule(final ThemisSolverModule pModule) {
        /* Store the module */
        thePackageMap = pModule.getPackages();

        /* DetermineDefault */
        determineDefault(pModule);
    }

    /**
     * Is the link a new package link?
     *
     * @param pLink the link
     * @return true/false
     */
    boolean isNewPackageLink(final String pLink) {
        return pLink.startsWith(ThemisUIRefConstants.LINKPACKAGE)
                || pLink.startsWith(ThemisUIRefConstants.LINKLOCAL);
    }

    /**
     * Is the link a list link?
     *
     * @param pLink the link
     * @return true/false
     */
    boolean isListLink(final String pLink) {
        return pLink.startsWith(ThemisUIRefConstants.LINKLIST);
    }

    /**
     * Format the default package.
     *
     * @return the HTML
     */
    String formatDefaultPackage() {
        /* Handle no default package */
        if (theDefault == null) {
            throw new IllegalArgumentException("No default package");
        }

        /* Format the packageHTML */
        return formatPackage(theDefault, false);
    }

    /**
     * Format the new package.
     *
     * @param pLink the link to the new package
     * @return the HTML
     */
    String formatNewPackageLink(final String pLink) {
        /* If this is a package link */
        if (pLink.startsWith(ThemisUIRefConstants.LINKPACKAGE)) {
            /* Strip off the header and locate the package */
            final String myName = pLink.substring(ThemisUIRefConstants.LINKPACKAGE.length());
            final ThemisSolverPackage myPackage = resolvePackage(myName);

            /* Format the packageHTML */
            return formatPackage(myPackage, false);
        }

        /* If this is a local link */
        if (pLink.startsWith(ThemisUIRefConstants.LINKLOCAL)) {
            /* Strip off the header and locate the package */
            final String myName = pLink.substring(ThemisUIRefConstants.LINKLOCAL.length());
            final ThemisSolverPackage myPackage = resolvePackage(myName);

            /* Format the packageHTML */
            return formatPackage(myPackage, true);
        }

        /* Unrecognised link */
        throw new IllegalArgumentException("Invalid Link header");
    }

    /**
     * Format the linkList.
     *
     * @param pLink the link to the linkList
     * @return the HTML
     */
    String formatListLink(final String pLink) {
        /* If this is a list link */
        if (pLink.startsWith(ThemisUIRefConstants.LINKLIST)) {
            /* Strip off the header and locate the packages */
            final String myNames = pLink.substring(ThemisUIRefConstants.LINKLIST.length());
            final int myIndex = myNames.indexOf(ThemisUIRefConstants.SEPCHAR);
            final String mySourceName = myNames.substring(0, myIndex);
            final String myTargetName = myNames.substring(myIndex + 1);
            final ThemisSolverPackage mySource = thePackageMap.get(mySourceName);
            final ThemisSolverPackage myTarget = thePackageMap.get(myTargetName);

            /* Handle unknown packages */
            if (mySource == null || myTarget == null) {
                throw new IllegalArgumentException("Unknown package");
            }

            /* Format the listHTML */
            return formatLinkList(mySource, myTarget);
        }

        /* Unrecognised link */
        throw new IllegalArgumentException("Invalid Link header");
    }

    /**
     * Create document for package.
     *
     * @param pPackage the package
     * @param pLocal   chose local Classes
     * @return the formatted document
     */
    private String formatPackage(final ThemisSolverPackage pPackage,
                                 final boolean pLocal) {
        /* Create new document and obtain the body */
        final Element myBody = newDocument();

        /* Determine whether to do local classes */
        final boolean doLocal = pPackage.getChildren().isEmpty() || pLocal;
        final ThemisUIResource myTitle = doLocal
                ? ThemisUIResource.HEADER_CLASS : ThemisUIResource.HEADER_PACKAGE;

        /* Create the header */
        final Element myHeader = createElement(ThemisUIHTMLTag.H1);
        myBody.appendChild(myHeader);
        myHeader.setTextContent(myTitle.getValue());

        /* Create the title */
        buildPackageTitle(myBody, pPackage);

        /* Create the links */
        buildPackageLinks(myBody, pPackage, pLocal);

        /* Create new table */
        final Element myTable = createElement(ThemisUIHTMLTag.TABLE);
        myBody.appendChild(myTable);

        /* Format appropriate table */
        if (doLocal) {
            /* Format local classes */
            theLocal.formatLocal(pPackage, myTable);
        } else {
            /* Format family links */
            theFamily.formatFamily(pPackage, myTable);
        }

        /* Return the formatted HTML */
        return formatXML();
    }

    /**
     * Create document for linkList.
     *
     * @param pSource the source package
     * @param pTarget the target package
     * @return the formatted document
     */
    private String formatLinkList(final ThemisSolverPackage pSource,
                                  final ThemisSolverPackage pTarget) {
        /* Create new document and obtain the body */
        final Element myBody = newDocument();

        /* format the package Links */
        final Element myHeader = createElement(ThemisUIHTMLTag.H1);
        myBody.appendChild(myHeader);
        myHeader.setTextContent(ThemisUIResource.REF_LINKS.getValue());

        /* Create new table */
        final Element myTable = createElement(ThemisUIHTMLTag.TABLE);
        myBody.appendChild(myTable);

        /* Format link list */
        theLinks.formatLinks(pSource, pTarget, myTable);

        /* Return the formatted HTML */
        return formatXML();
    }

    /**
     * Build package title.
     *
     * @param pBody    the document body
     * @param pPackage the package
     */
    private void buildPackageTitle(final Element pBody,
                                   final ThemisSolverPackage pPackage) {
        /* Create the header */
        final Element myHeader = createElement(ThemisUIHTMLTag.H3);
        pBody.appendChild(myHeader);

        /* Set name of package */
        final String myName = pPackage.getPackageName().isEmpty()
                ? ThemisUIResource.REF_ROOT.getValue()
                : pPackage.getPackageName();
        myHeader.setTextContent(myName);
    }

    /**
     * Build package links.
     *
     * @param pBody    the document body
     * @param pPackage the package
     * @param pLocal   chose local Classes
     */
    private void buildPackageLinks(final Element pBody,
                                   final ThemisSolverPackage pPackage,
                                   final boolean pLocal) {
        /* Create the links */
        final Element myLinks = createElement(ThemisUIHTMLTag.TR);

        /* Create links */
        createHomeLink(myLinks, pPackage);
        createParentLink(myLinks, pPackage);
        if (pLocal) {
            createFamilyLink(myLinks, pPackage);
        }

        /* If we have links, then add to body */
        if (myLinks.hasChildNodes()) {
            /* Create the table */
            final Element myTable = createElement(ThemisUIHTMLTag.TABLE);
            pBody.appendChild(myTable);
            myTable.appendChild(myLinks);
            addClassToElement(myTable, ThemisUIRefConstants.CLASSNAVTABLE);
        }
    }

    /**
     * Create home link.
     *
     * @param pLinks   the document links
     * @param pPackage the package
     */
    private void createHomeLink(final Element pLinks,
                                final ThemisSolverPackage pPackage) {
        /* If we are not the default package */
        if (!pPackage.equals(theDefault)) {
            /* Create the cell */
            final Element myCell = createElement(ThemisUIHTMLTag.TD);
            pLinks.appendChild(myCell);

            /* Create the link */
            final Element myLink = createElement(ThemisUIHTMLTag.A);
            myCell.appendChild(myLink);

            /* Set link details */
            final String myLinkRef = ThemisUIRefConstants.LINKPACKAGE + theDefault.getPackageName();
            setAttribute(myLink, ThemisUIHTMLAttr.HREF, myLinkRef);

            /* Set name of package */
            myLink.setTextContent(ThemisUIResource.REF_HOME.getValue());
        }
    }

    /**
     * Create parent link.
     *
     * @param pLinks   the document links
     * @param pPackage the package
     */
    private void createParentLink(final Element pLinks,
                                  final ThemisSolverPackage pPackage) {
        /* If we are not the default package */
        final ThemisSolverPackage myParent = getParentForLink(pPackage);
        if (!pPackage.equals(theDefault)
                && !theDefault.equals(myParent)) {
            /* Create the cell */
            final Element myCell = createElement(ThemisUIHTMLTag.TD);
            pLinks.appendChild(myCell);

            /* Create the link */
            final Element myLink = createElement(ThemisUIHTMLTag.A);
            myCell.appendChild(myLink);

            /* Set link details */
            final String myLinkRef = ThemisUIRefConstants.LINKPACKAGE + myParent.getPackageName();
            setAttribute(myLink, ThemisUIHTMLAttr.HREF, myLinkRef);

            /* Set name of package */
            myLink.setTextContent(ThemisUIResource.REF_PARENT.getValue());
        }
    }

    /**
     * Obtain parent for link.
     *
     * @param pPackage the package
     * @return the parent
     */
    private ThemisSolverPackage getParentForLink(final ThemisSolverPackage pPackage) {
        /* Handle default package */
        if (pPackage.equals(theDefault)) {
            return theDefault;
        }

        /* Skip to non-placeholder */
        final ThemisSolverPackage myParent = pPackage.getParent();
        return isPlaceHolder(myParent) ? getParentForLink(myParent) : myParent;
    }

    /**
     * Resolve package.
     *
     * @param pName the package name
     * @return the package
     */
    private ThemisSolverPackage resolvePackage(final String pName) {
        /* Resolve the package */
        ThemisSolverPackage myPackage = thePackageMap.get(pName);

        /* Handle unknown package */
        if (myPackage == null) {
            throw new IllegalArgumentException("Unknown package: " + pName);
        }

        /* Skip over placeHolders */
        while (isPlaceHolder(myPackage)) {
            myPackage = myPackage.getChildren().getFirst();
        }

        /* Return the package */
        return myPackage;
    }

    /**
     * Obtain parent for link.
     *
     * @param pPackage the package
     * @return the parent
     */
    private boolean isPlaceHolder(final ThemisSolverPackage pPackage) {
        return pPackage.getFiles().isEmpty()
                && pPackage.getChildren().size() == 1;
    }

    /**
     * Create parent link.
     *
     * @param pLinks   the document links
     * @param pPackage the package
     */
    private void createFamilyLink(final Element pLinks,
                                  final ThemisSolverPackage pPackage) {
        /* If we are not the default package */
        if (!pPackage.getChildren().isEmpty()) {
            /* Create the cell */
            final Element myCell = createElement(ThemisUIHTMLTag.TD);
            pLinks.appendChild(myCell);

            /* Create the link */
            final Element myLink = createElement(ThemisUIHTMLTag.A);
            myCell.appendChild(myLink);

            /* Set link details */
            final String myLinkRef = ThemisUIRefConstants.LINKPACKAGE + pPackage.getPackageName();
            setAttribute(myLink, ThemisUIHTMLAttr.HREF, myLinkRef);

            /* Set name of package */
            myLink.setTextContent(ThemisUIResource.REF_FAMILY.getValue());
        }
    }

    /**
     * Determine the default package.
     *
     * @param pModule the module
     */
    private void determineDefault(final ThemisSolverModule pModule) {
        /* Initialise the default */
        theDefault = null;

        /* If we have a non-null modules */
        if (pModule != null) {
            /* Loop through the available packages */
            for (ThemisSolverPackage myPackage : pModule.getPackages().values()) {
                /* Adjust the default */
                adjustDefault(myPackage);
            }
        }
    }

    /**
     * Adjust default.
     *
     * @param pPackage the package
     */
    private void adjustDefault(final ThemisSolverPackage pPackage) {
        /* Ignore placeHolder */
        if (skipPackage(pPackage)) {
            return;
        }

        /* Store first package as default */
        if (theDefault == null) {
            theDefault = pPackage;

            /* else we need to find a common parent */
        } else {
            theDefault = getCommonParent(theDefault, pPackage);
        }
    }

    /**
     * Obtain common parent.
     *
     * @param pFirst  the first package
     * @param pSecond the second package
     * @return the common parent
     */
    private ThemisSolverPackage getCommonParent(final ThemisSolverPackage pFirst,
                                                final ThemisSolverPackage pSecond) {
        if (pFirst.equals(pSecond)) {
            return pFirst;
        }
        final String myFirst = pFirst.getPackageName();
        final String mySecond = pSecond.getPackageName();
        return myFirst.length() >= mySecond.length()
                ? getCommonParent(pFirst.getParent(), pSecond)
                : getCommonParent(pFirst, pSecond.getParent());
    }

    /**
     * Should we skip the package?
     *
     * @param pPackage the package
     * @return true/false
     */
    private boolean skipPackage(final ThemisSolverPackage pPackage) {
        /* Skip placeholders */
        return pPackage.isPlaceHolder();
    }
}