/*
 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package jdk.jpackage.internal.model;

import java.nio.file.Path;
import java.util.Optional;

/**
 * Native application package.
 *
 * The interface specifies the source app image layout with two transformations:
 * package app image layout and installed app image layout.
 * <p>
 * Use {@link #appImageLayout()} or {@link #asApplicationLayout()} to get the
 * unresolved source app image layout.
 * <p>
 * Package app image layout is the source app image layout resolved at the
 * relative installation directory of the package. Additionally, to resolve the
 * source layout, some packages may transform the source layout.
 * <p>
 * Use {@link #packageLayout()} or {@link #asPackageApplicationLayout()} to get
 * the package app image layout.
 * <p>
 * Installed app image layout is the layout of the installed app image.
 * <p>
 * Use {@link #installedPackageLayout()} or
 * {@link #asInstalledPackageApplicationLayout()} to get the installed app image
 * layout.
 * <p>
 * The following table shows app image layouts of the application named "Duke"
 * on different platforms:
 * <table border="1">
 * <tr>
 * <th></th>
 * <th>Source app image layout</th>
 * <th>Package app image layout</th>
 * <th>Installed app image layout</th>
 * </tr>
 * <tr>
 * <th>Windows</th>
 * <td>bin/foo.exe app/foo.jar</td>
 * <td>Duke/bin/foo.exe Duke/app/foo.jar</td>
 * <td>Duke/bin/foo.exe Duke/app/foo.jar</td>
 * </tr>
 * <tr>
 * <th>Linux</th>
 * <td>bin/foo lib/app/foo.jar</td>
 * <td>opt/duke/bin/foo opt/duke/lib/app/foo.jar</td>
 * <td>/opt/duke/bin/foo /opt/duke/lib/app/foo.jar</td>
 * </tr>
 * <tr>
 * <th>OSX</th>
 * <td>Contents/MacOS/foo Contents/app/foo.jar</td>
 * <td>Applications/Duke.app/Contents/MacOS/foo Applications/Duke.app/Contents/app/foo.jar</td>
 * <td>/Applications/Duke.app/Contents/MacOS/foo /Applications/Duke.app/Contents/app/foo.jar</td>
 * </tr>
 * </table>
 */
public interface Package extends BundleSpec {

    /**
     * Gets the application of this package.
     *
     * @return the application of this package
     */
    Application app();

    /**
     * Gets the type of this package.
     *
     * @return the type of this package
     */
    PackageType type();

    /**
     * Gets the type of this package as {@link StandardPackageType} type or an empty
     * {@link Optional} instance if the return value of {@link #type()} call is not
     * an instance of {@link StandardPackageType} type.
     *
     * @return the type of this package as {@link StandardPackageType} type
     */
    default Optional<StandardPackageType> asStandardPackageType() {
        if (type() instanceof StandardPackageType stdType) {
            return Optional.of(stdType);
        } else {
            return Optional.empty();
        }
    }

    /**
     * Gets the name of the native package of this package.
     * <p>
     * The value is a valid file system name and can be safely used to name
     * files/directories in the file system.
     *
     * @return the name of the native package of this package
     */
    String packageName();

    /**
     * Gets the description of this package.
     * @return the description of this package
     */
    String description();

    /**
     * Gets the version of this package.
     * @return the version of this package
     */
    String version();

    /**
     * Gets the "About" URL of this package if available or an empty
     * {@link Optional} instance otherwise.
     *
     * @return the "About" URL of this package
     */
    Optional<String> aboutURL();

    /**
     * Gets the path to a license file of this package if available or an empty
     * {@link Optional} instance otherwise.
     *
     * @return the path to a license file of this package
     */
    Optional<Path> licenseFile();

    /**
     * Gets the path to a directory with the application app image of this package
     * if available or an empty {@link Optional} instance otherwise.
     *
     * @return the path to a directory with the application app image of this
     *         package
     */
    Optional<Path> predefinedAppImage();

    /**
     * Gets the unresolved source app image layout of the application of this package.
     *
     * @return the unresolved app image layout of the application of this package
     *
     * @see #packageLayout
     * @see #installedPackageLayout
     */
    default AppImageLayout appImageLayout() {
        return app().imageLayout();
    }

    /**
     * Returns the unresolved source app image layout of the application of this
     * package as {@link ApplicationLayout} type or an empty {@link Optional}
     * instance if the layout object is of incompatible type.
     * <p>
     * Returns an empty {@link Optional} instance if {@link #isRuntimeInstaller()}
     * returns <code>true</code>.
     *
     * @return the unresolved source app image layout of the application of this
     *         package as {@link ApplicationLayout} type
     *
     * @see #appImageLayout
     */
    default Optional<ApplicationLayout> asApplicationLayout() {
        return app().asApplicationLayout();
    }

    /**
     * Gets the layout of the installed app image of the application resolved at the
     * relative installation directory of this package.
     *
     * @return the layout of the installed app image of the application resolved at
     *         the relative installation directory of this package
     *
     * @see #relativeInstallDir
     * @see #appImageLayout
     * @see #installedPackageLayout
     */
    default AppImageLayout packageLayout() {
        return appImageLayout().resolveAt(relativeInstallDir());
    }

    /**
     * Returns the layout of the installed app image of the application resolved at
     * the relative installation directory of this package as
     * {@link ApplicationLayout} type or an empty {@link Optional} instance if the
     * layout object is of incompatible type.
     * <p>
     * Returns an empty {@link Optional} instance if {@link #isRuntimeInstaller()}
     * returns <code>true</code>.
     *
     * @return the layout of the installed app image of the application resolved at
     *         the relative installation directory of this package as
     *         {@link ApplicationLayout} type
     *
     * @see #packageLayout
     */
    default Optional<ApplicationLayout> asPackageApplicationLayout() {
        if (packageLayout() instanceof ApplicationLayout layout) {
            return Optional.of(layout);
        } else {
            return Optional.empty();
        }
    }

    /**
     * Gets the layout of the installed app image of this package.
     *
     * @return the layout of the installed app image of this package
     *
     * @see #appImageLayout
     * @see #packageLayout
     */
    default AppImageLayout installedPackageLayout() {
        return asStandardPackageType().map(stdType -> {
            switch (stdType) {
                case LINUX_DEB, LINUX_RPM, MAC_DMG, MAC_PKG -> {
                    return packageLayout().resolveAt(Path.of("/"));
                }
                case WIN_EXE, WIN_MSI -> {
                    return packageLayout();
                }
                default -> {
                    // Should never get here
                    throw new IllegalStateException();
                }
            }
        }).orElseThrow(UnsupportedOperationException::new);
    }

    /**
     * Returns the layout of the installed app image of this package as
     * {@link ApplicationLayout} type or an empty {@link Optional} instance if the
     * layout object is of incompatible type.
     * <p>
     * Returns an empty {@link Optional} instance if {@link #isRuntimeInstaller()}
     * returns <code>true</code>.
     *
     * @return the layout of the installed app image of this package as
     *         {@link ApplicationLayout} type
     *
     * @see #installedPackageLayout
     */
    default Optional<ApplicationLayout> asInstalledPackageApplicationLayout() {
        if (installedPackageLayout() instanceof ApplicationLayout layout) {
            return Optional.of(layout);
        } else {
            return Optional.empty();
        }
    }

    /**
     * Get the name without an extension of the package file of this package.
     *
     * @return the name without an extension of the package file of this package
     */
    default String packageFileName() {
        return String.format("%s-%s", packageName(), version());
    }

    /**
     * Gets the extension of the package file of this package if available or an
     * empty {@link Optional} instance otherwise.
     *
     * @return the extension of the package file of this package
     */
    default Optional<String> packageFileSuffix() {
        return asStandardPackageType().map(StandardPackageType::suffix);
    }

    /**
     * Gets the full name of the package file of this package. The full name
     * consists of the name and the extension.
     *
     * @return the full name of the package file of this package
     */
    default String packageFileNameWithSuffix() {
        return packageFileName() + packageFileSuffix().orElse("");
    }

    /**
     * Returns <code>true</code> if the application of this package is Java runtime.
     *
     * @return <code>true</code> if the application of this package is Java runtime
     *
     * @see Application#isRuntime()
     */
    default boolean isRuntimeInstaller() {
        return app().isRuntime();
    }

    /**
     * Gets the relative path to the package installation directory of this package.
     *
     * On Windows it is relative to the program files directory
     * (<code>"%ProgramFiles%"</code>) and to the system root (<code>"/"</code>) on
     * other platforms.
     *
     * @return the relative path to the package installation directory of this
     *         package
     */
    Path relativeInstallDir();

    /**
     * Default implementation of {@link Package} interface.
     */
    record Stub(Application app, PackageType type, String packageName, String description, String version,
            Optional<String> aboutURL, Optional<Path> licenseFile, Optional<Path> predefinedAppImage,
            Path relativeInstallDir) implements Package {
    }
}
