/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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 vogar.android;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import vogar.Action;
import vogar.Classpath;
import vogar.Result;
import vogar.Run;
import vogar.TestProperties;
import vogar.Toolchain;
import vogar.commands.Command;
import vogar.tasks.Task;

public final class InstallApkTask extends Task {
    public static final String ACTIVITY_CLASS = "vogar.target.TestActivity";

    private final Action action;
    private final File jar;
    private final Run run;

    public InstallApkTask(Run run, Action action, File jar) {
        super("aapt and push " + action.getName());
        this.action = action;
        this.jar = jar;
        this.run = run;
    }

    @Override protected Result execute() throws Exception {
        // We can't put multiple dex files in one apk.
        // We can't just give dex multiple jars with conflicting class names

        // With that in mind, the APK packaging strategy is as follows:
        // 1. d8 to create a dex
        // 2. aapt the dex to create apk
        // 3. sign the apk
        // 4. install the apk
        File dex = createDex(action, jar);
        File apk = createApk(action, dex);
        signApk(apk);
        installApk(action, apk);
        return Result.SUCCESS;
    }

    /**
     * Returns a single dexfile containing {@code action}'s classes and all
     * dependencies.
     */
    private File createDex(Action action, File actionJar) {
        File dex = run.localFile(action, "classes.dex");
        Classpath classesToDex = Classpath.of(actionJar);
        classesToDex.addAll(run.classpath);

        File localTempDir = run.localDir(action.getName());

        // Do not specify additional compile-time-only dependencies,
        // because everything gets bundled into classes.dex.
        Classpath dependentCp = new Classpath();

        run.androidSdk.dex(run.multidex, dex, localTempDir, classesToDex, dependentCp, run.toolchain.getDexer());
        return dex;
    }

    private File createApk (Action action, File dex) {
        String androidManifest =
            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
            "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
            "      package=\"" + packageName(action) + "\">\n" +
            "    <uses-permission android:name=\"android.permission.INTERNET\" />\n" +
            "    <uses-sdk android:minSdkVersion=\"" + run.sdkVersion + "\"\n" +
            "          android:targetSdkVersion=\"" + run.sdkVersion + "\" />\n" +
            "    <application" +
                    ((run.debugging) ? " android:debuggable=\"true\"" : "") + ">\n" +
            "        <activity android:name=\"" + ACTIVITY_CLASS + "\">\n" +
            "            <intent-filter>\n" +
            "                <action android:name=\"android.intent.action.MAIN\" />\n" +
            "                <category android:name=\"android.intent.category.LAUNCHER\" />\n" +
            "            </intent-filter>\n" +
            "        </activity>\n" +
            "    </application>\n" +
            "</manifest>\n";
        File androidManifestFile = run.localFile(action, "classes", "AndroidManifest.xml");
        try {
            FileOutputStream androidManifestOut =
                    new FileOutputStream(androidManifestFile);
            androidManifestOut.write(androidManifest.getBytes("UTF-8"));
            androidManifestOut.close();
        } catch (IOException e) {
            throw new RuntimeException("Problem writing " + androidManifestFile, e);
        }

        File apk = run.localFile(action, action + ".apk");
        run.androidSdk.packageApk(apk, androidManifestFile);
        run.androidSdk.addToApk(apk, dex);
        run.androidSdk.addToApk(apk, run.localFile(action, "classes", TestProperties.FILE));
        return apk;
    }

    /**
     * According to android.content.pm.PackageParser, package name
     * "must have at least one '.' separator" Since the qualified name
     * may not contain a dot, we prefix containing one to ensure we
     * are compliant.  Also transform any hyphens to underscores as they
     * are illegal in package names.
     */
    public static String packageName(Action action) {
        return "vogar.test." + action.getName().replaceAll("-", "_");
    }

    private void signApk(File apkUnsigned) {
        /*
         * key generated with this command, using "password" for the key and keystore passwords:
         *     keytool -genkey -v -keystore src/vogar/vogar.keystore \
         *         -keyalg RSA -validity 10000 -alias vogar
         */
        new Command(run.log, "jarsigner",
                "--storepass", "password",
                "-keystore", run.keystore.getPath(),
                apkUnsigned.getPath(),
                "vogar")
                .execute();
    }

    private void installApk(Action action, File apkSigned) {
        // install the local apk ona the device
        run.androidSdk.uninstall(packageName(action));
        run.androidSdk.install(apkSigned);
    }
}
