#!/bin/bash
set -x

# Dependencies:
#    homebrew
#      brew install node
#      npm install -g appdmg
#    xcode 12.0+
#      command line utilities
#    keychain access
#      a valid signing i.d. from apple

# Provide valid Developer ID Application and path to jdk
SIGNING_ID=<Developer ID Application>
JDK_HOME=<Path to JDK>

echo
echo "- Calculated JDK_HOME=$JDK_HOME"
echo "  - Testing JDK..."
JDK_VERSION_RAW=$("$JDK_HOME/bin/java" --version 2>&1)
# TODO: Some JDKs return double quotes
JDK_VERSION="$(echo $JDK_VERSION_RAW | sed 's/ /\n/g' | sed -n 2p)"
JDK_VERSION_MAJOR=$(echo $JDK_VERSION | cut -d. -f1)
JDK_VERSION_MINOR=$(echo $JDK_VERSION | cut -d. -f2)
JDK_VERSION_PATCH=$(echo $JDK_VERSION | cut -d. -f3)
if [ -n "JDK_VERSION_PATCH" ]; then
    echo "     - Found version $JDK_VERSION_MAJOR.$JDK_VERSION_MINOR.$JDK_VERSION_PATCH"
else
    echo "     - BROKEN"; exit 1
fi

# Create a sample Java application
echo
echo "- Creating a sample Java application..."
mkdir -p src
pushd src > /dev/null
cat >Hello.java <<EOL
import javax.print.*;
import javax.swing.*;

public class Hello {

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeAndWait(() -> {
            boolean isSandboxed = System.getenv("APP_SANDBOX_CONTAINER_ID") != null;
            PrintService defaultPrinter = PrintServiceLookup.lookupDefaultPrintService();
            PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);

            StringBuilder builder = new StringBuilder();
            builder.append("is sandboxed: ").append(isSandboxed).append("\n");
            builder.append("default printer: ").append(defaultPrinter).append("\n");
            int size = services.length;
            for (int i = 0; i < size; i++) {
                builder.append("printer[").append(i).append("]=").append(services[i]).append("\n");
            }
            JOptionPane.showMessageDialog(null, builder.toString());
        });
    }
}
EOL
if "$JDK_HOME/bin/javac" Hello.java ; then
    echo "    - Compiled successfully"
else
    echo "    - Compilation failed"; exit 1
fi

echo "Main-Class: Hello" > MANIFEST.MF
rm -rf hello.jar
JAR_PATH="$(pwd)/hello.jar"
if "$JDK_HOME/bin/jar" cfm "$JAR_PATH" MANIFEST.MF Hello*.class ; then
    echo "  - $JAR_PATH created"
else
    echo "  - $JAR_PATH creation failed"; exit 1
fi
popd > /dev/null

# Setup some jlink tools
echo
echo "- Calculating some jlink tools..."
JMODS="$JDK_HOME/jmods"
JDEPS="$JDK_HOME/bin/jdeps"
JLINK="$JDK_HOME/bin/jlink"

# Make sure they work
echo "  - JMODS=$JMODS"
if [ -d "$JMODS" ]; then
    echo "     - EXISTS"
else
    echo "     - MISSING"; exit 1
fi
echo "  - JDEPS=$JDEPS"
if "$JDEPS" --help >/dev/null; then
    echo "     - WORKS"
else
    echo "     - BROKEN"; exit 1
fi
echo "  - JLINK=$JLINK"
if "$JLINK" --help >/dev/null; then
    echo "     - WORKS"
else
    echo "     - BROKEN"; exit 1
fi

# Start constructing a runtime from our jar
echo
echo "- Creating a suitable runtime in out/jlink for $JAR_PATH..."
# TODO: --ignore-missing-deps should be JDK 11.0.11+ only
echo "  - Calculating dependencies..."
# TODO: For most apps, this will spew out unusable modules which need to be filtered
NEEDED_MODULES=$("$JDEPS" --list-deps --ignore-missing-deps "$JAR_PATH" |tr '\n' ',' |tr -d '[:space:]')
TARGET_APP=out/Hello.app
TARGET_RUNTIME="$TARGET_APP/Contents/PlugIns/Java.runtime"
echo "  - Building $TARGET_RUNTIME..."
rm -rf "$TARGET_APP"
mkdir -p "$TARGET_RUNTIME/Contents"
"$JLINK" --strip-debug --compress=2 --no-header-files --no-man-pages --module-path "$JMODS" --add-modules $NEEDED_MODULES --output out/Hello.app/Contents/PlugIns/Java.runtime/Contents/Home

# Fix some layout nuances
echo
echo "- Fixing some Java.runtime layout nuances..."
echo "   - Copying lib/libjli.dylib to $TARGET_RUNTIME/Contents/MacOS..."
# TODO: Some JDKs offer this file in MacOS already, but jlink still won't deploy it.  Copy from lib/jli is a guess?
mkdir -p "$TARGET_RUNTIME/Contents/MacOS"
if ! cp "$TARGET_RUNTIME/Contents/Home/lib/libjli.dylib" "$TARGET_RUNTIME/Contents/MacOS" ; then
    echo "    - Failed"; exit 1
fi
echo "   - Removing unused files from $TARGET_RUNTIME/Contents/Home/bin..."
rm -f "$TARGET_RUNTIME/Contents/Home/bin/jfr"
rm -f "$TARGET_RUNTIME/Contents/Home/bin/rmid"
rm -f "$TARGET_RUNTIME/Contents/Home/bin/rmiregistry"
rm -f "$TARGET_RUNTIME/Contents/Home/bin/jrunscript"
rm -f "$TARGET_RUNTIME/Contents/Home/bin/keytool"

# Start building the app structure
echo
echo "  - Building the main bundle..."
mkdir "$TARGET_APP/Contents/Resources"
echo "    - Creating resources..."
cp /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Notifications.icns "$TARGET_APP/Contents/Resources/hello.icns"
cp "$JAR_PATH" "$TARGET_APP/Contents/Resources"
echo "    - Creating launcher..."
mkdir "$TARGET_APP/Contents/MacOS"
cat >"$TARGET_APP/Contents/MacOS/Hello" <<EOL
#!/bin/bash
SCRIPT_DIR="\$( cd "\$( dirname "\${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
pushd "\$SCRIPT_DIR/../PlugIns/Java.runtime/Contents/home/bin"
./java -jar "\$SCRIPT_DIR/../Resources/hello.jar"
popd
EOL
chmod +x "$TARGET_APP/Contents/MacOS/Hello"

echo "    - Writing Info.plist..."
cat>"$TARGET_APP/Contents/Info.plist" <<EOL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
    <key>CFBundleDevelopmentRegion</key><string>English</string>
    <key>CFBundleIconFile</key><string>hello</string>
    <key>CFBundleIdentifier</key><string>sample.hello</string>
    <key>CFBundlePackageType</key><string>APPL</string>
    <key>CFBundleGetInfoString</key><string>Hello 1.0.0</string>
    <key>CFBundleSignature</key><string>Hello</string>
    <key>CFBundleExecutable</key><string>Hello</string>
    <key>CFBundleVersion</key><string>1.0.0</string>
    <key>CFBundleShortVersionString</key><string>1.0.0</string>
    <key>CFBundleName</key><string>Hello</string>
    <key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
</dict></plist>
EOL

echo "    - You should have a usable app now, but we're going to try to sign and hopefully sandbox it."


echo
echo "- Preparing the entitlement files..."
echo "  - Writing bundle entitlements to src/entitlements.plist..."
cat>"src/entitlements.plist" <<EOL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key><true/>
    <key>com.apple.security.network.client</key><true/>
    <key>com.apple.security.network.server</key><true/>
    <key>com.apple.security.files.all</key><true/>
    <key>com.apple.security.print</key><true/>
    <key>com.apple.security.device.usb</key><true/>
    <key>com.apple.security.device.bluetooth</key><true/>
    <key>com.apple.security.cs.allow-jit</key><true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
    <key>com.apple.security.cs.disable-library-validation</key><true/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key><true/>
    <key>com.apple.security.cs.debugger</key><true/>
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
</dict>
</plist>
EOL

echo "  - Writing Info.plist for $TARGET_RUNTIME..."
# TODO: Write vendor and version information dynamically
cat>"$TARGET_RUNTIME/Contents/Info.plist" <<EOL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key><string>English</string>
	<key>CFBundleExecutable</key><string>libjli.dylib</string>
	<key>CFBundleGetInfoString</key><string>JDK 11.0.11+10</string>
	<key>CFBundleIdentifier</key><string>sample.hello.jre</string>
	<key>CFBundleInfoDictionaryVersion</key><string>7.0</string>
	<key>CFBundleName</key><string>Java Runtime Image</string>
	<key>CFBundlePackageType</key><string>BNDL</string>
	<key>CFBundleShortVersionString</key><string>11.0.11</string>
	<key>CFBundleSignature</key><string>????</string>
	<key>CFBundleVersion</key><string>11.0.11</string>
	<key>JavaVM</key>
	<dict>
		<key>JVMCapabilities</key>
		<array>
			<string>CommandLine</string>
			<string>JNI</string>
			<string>BundledApp</string>
		</array>
		<key>JVMMinimumFrameworkVersion</key><string>17.0.0</string>
		<key>JVMMinimumSystemVersion</key><string>10.6.0</string>
		<key>JVMPlatformVersion</key><string>11.0.11+10</string>
		<key>JVMVendor</key><string>JDK</string>
		<key>JVMVersion</key><string>11.0.11</string>
	</dict>
</dict>
</plist>
EOL

echo "- Starting the signing process..."
echo "  - Signing individual files to overwrite any lingering signatures..."
find -X "$TARGET_APP" -type f -not -path "*/Contents/MacOS/*" -exec sh -c 'file -I "{}" |grep -m1 "x-mach-binary"|cut -f 1 -d \:' \; |xargs codesign --force -s "$SIGNING_ID" --timestamp --options runtime --entitlement src/entitlements.plist
if [ $? -ne 0 ]; then
    echo "  - Failed."; exit 1
fi

echo "  - Signing the Java.runtime bundle using src/entitlements.plist..."
if ! codesign --force -s "$SIGNING_ID" --timestamp --options runtime --entitlement src/entitlements.plist "$TARGET_RUNTIME" ; then
    echo "  - Failed."; exit 1
fi

echo "  - Signing the main bundle using src/entitlements.plist..."
if ! codesign --force -s "$SIGNING_ID" --timestamp --options runtime --entitlement src/entitlements.plist "$TARGET_APP" ; then
    echo "  - Failed."; exit 1
fi
echo "  - The signed app is available here: $TARGET_APP"

echo "- Packaging the DMG..."
# TODO: ../$TARGET_APP needed because appdmg works relative to json file
cat>"src/dmg.json" <<EOL
{
  "title": "Hello",
  "contents": [
    { "x": 407, "y": 244, "type": "link", "path": "/Applications" },
    { "x": 133, "y": 244, "type": "file", "path": "../$TARGET_APP" }
  ]
}
EOL
rm -f "out/Hello 1.0.0.dmg"
if ! appdmg "src/dmg.json" "out/Hello 1.0.0.dmg" ; then
    echo "   - Failed."; exit 1
fi
echo "  - The DMG is available at out/Hello 1.0.0.dmg"

echo
echo "- Finished"
