/*
 * Copyright (C) 2008 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 com.android.sdklib.build;

import com.google.devtools.build.singlejar.ZipCombiner;
import com.google.devtools.build.singlejar.ZipEntryFilter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.regex.Pattern;

/**
 * Command line APK builder with signing support.
 */
public final class ApkBuilderMain {

    private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
            Pattern.CASE_INSENSITIVE);

    /**
     * Main method. This is meant to be called from the command line through an exec.
     * <p/>WARNING: this will call {@link System#exit(int)} if anything goes wrong.
     * @param args command line arguments.
     */
    public static void main(String[] args) {
        if (args.length < 1) {
            printUsageAndQuit();
        }

        try {
            File outApk = new File(args[0]);

            File dexFile = null;
            ArrayList<File> zipArchives = new ArrayList<File>();
            ArrayList<File> sourceFolders = new ArrayList<File>();
            ArrayList<File> jarFiles = new ArrayList<File>();
            ArrayList<File> nativeFolders = new ArrayList<File>();

            boolean verbose = false;
            boolean signed = true;
            boolean debug = false;
            String keystorePath = null;

            int index = 1;
            do {
                String argument = args[index++];

                if ("-v".equals(argument)) {
                    verbose = true;

                } else if ("-d".equals(argument)) {
                    debug = true;

                } else if ("-u".equals(argument)) {
                    signed = false;

                } else if ("-ks".equals(argument)) {
                    // bazel-specific option
                    if (index == args.length) {
                        printAndExit("Missing value for -ks");
                    }

                    keystorePath = args[index++];
                } else if ("-z".equals(argument)) {
                    // quick check on the next argument.
                    if (index == args.length)  {
                        printAndExit("Missing value for -z");
                    }

                    zipArchives.add(new File(args[index++]));
                } else if ("-f". equals(argument)) {
                    if (dexFile != null) {
                        // can't have more than one dex file.
                        printAndExit("Can't have more than one dex file (-f)");
                    }
                    // quick check on the next argument.
                    if (index == args.length) {
                        printAndExit("Missing value for -f");
                    }

                    dexFile = new File(args[index++]);
                } else if ("-rf". equals(argument)) {
                    // quick check on the next argument.
                    if (index == args.length) {
                        printAndExit("Missing value for -rf");
                    }

                    sourceFolders.add(new File(args[index++]));
                } else if ("-rj". equals(argument)) {
                    // quick check on the next argument.
                    if (index == args.length) {
                        printAndExit("Missing value for -rj");
                    }

                    jarFiles.add(new File(args[index++]));
                } else if ("-nf".equals(argument)) {
                    // quick check on the next argument.
                    if (index == args.length) {
                        printAndExit("Missing value for -nf");
                    }

                    nativeFolders.add(new File(args[index++]));
                } else if ("-storetype".equals(argument)) {
                    // quick check on the next argument.
                    if (index == args.length) {
                        printAndExit("Missing value for -storetype");
                    }

                    // FIXME
                } else {
                    printAndExit("Unknown argument: " + argument);
                }
            } while (index < args.length);

            if (zipArchives.size() == 0) {
                printAndExit("No zip archive, there must be one for the resources");
            }

            if (signed && keystorePath == null) {
              keystorePath = ApkBuilder.getDebugKeystore();
            }

            // create the builder with the basic files.
            ApkBuilder builder = new ApkBuilder(outApk, zipArchives.get(0), dexFile,
                signed ? keystorePath : null,
                verbose ? System.out : null);
            builder.setDebugMode(debug);

            // add the rest of the files.
            // first zip Archive was used in the constructor.
            for (int i = 1 ; i < zipArchives.size() ; i++) {
                builder.addZipFile(zipArchives.get(i));
            }

            for (File sourceFolder : sourceFolders) {
                builder.addSourceFolder(sourceFolder);
            }

            for (File jarFile : jarFiles) {
                if (jarFile.isDirectory()) {
                    String[] filenames = jarFile.list(new FilenameFilter() {
                        public boolean accept(File dir, String name) {
                            return PATTERN_JAR_EXT.matcher(name).matches();
                        }
                    });

                    for (String filename : filenames) {
                        builder.addResourcesFromJar(new File(jarFile, filename));
                    }
                } else {
                    builder.addResourcesFromJar(jarFile);
                }
            }

            for (File nativeFolder : nativeFolders) {
                builder.addNativeLibraries(nativeFolder);
            }

            // seal the apk
            builder.sealApk();

            // ensure hermeticity, bazel specific
            clearTimeStamps(outApk);
        } catch (ApkCreationException e) {
            printAndExit(e.getMessage());
        } catch (DuplicateFileException e) {
            printAndExit(String.format(
                    "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
                    e.getArchivePath(), e.getFile1(), e.getFile2()));
        } catch (SealedApkException e) {
            printAndExit(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    private static void clearTimeStamps(File outApk) throws IOException {
        File renamed = new File(outApk.getPath() + ".nonhermetic");
        if (!outApk.renameTo(renamed)) {
            throw new IOException("could not rename: " + outApk);
        }

        OutputStream out = new FileOutputStream(outApk);
        ZipCombiner combiner = new ZipCombiner(
            new ZipEntryFilter() {
                @Override
                public void accept(String filename, StrategyCallback callback)
                        throws IOException {
                    callback.copy(ZipCombiner.DOS_EPOCH);
                }
            }, out);
        combiner.addZip(renamed);
        combiner.close();  // closes its outstream.
        renamed.deleteOnExit();
    }

    private static void printUsageAndQuit() {
        // 80 cols marker:  01234567890123456789012345678901234567890123456789012345678901234567890123456789
        System.err.println("A command line tool to package an Android application from various sources.");
        System.err.println("Usage: apkbuilder <out archive> [-v][-u][-storetype STORE_TYPE] [-z inputzip]");
        System.err.println("            [-f inputfile] [-rf input-folder] [-rj -input-path]");
        System.err.println("            [-nf native-folder] [-rj -input-path]");
        System.err.println("");
        System.err.println("NOTE: This is a version of the ApkBuilder tool that comes "
            + "with the Android sdk modified for Bazel.");
        System.err.println("");
        System.err.println("    -v      Verbose.");
        System.err.println("    -d      Debug Mode: Includes debug files in the APK file.");
        System.err.println("    -u      Creates an unsigned package.");
        System.err.println("    -storetype Forces the KeyStore type. If ommited the default is used.");
        System.err.println("");
        System.err.println("    -z      Followed by the path to a zip archive.");
        System.err.println("            Adds the content of the application package.");
        System.err.println("");
        System.err.println("    -f      Followed by the path to a file.");
        System.err.println("            Adds the file to the application package.");
        System.err.println("");
        System.err.println("    -rf     Followed by the path to a source folder.");
        System.err.println("            Adds the java resources found in that folder to the application");
        System.err.println("            package, while keeping their path relative to the source folder.");
        System.err.println("");
        System.err.println("    -rj     Followed by the path to a jar file or a folder containing");
        System.err.println("            jar files.");
        System.err.println("            Adds the java resources found in the jar file(s) to the application");
        System.err.println("            package.");
        System.err.println("");
        System.err.println("    -nf     Followed by the root folder containing native libraries to");
        System.err.println("            include in the application package.");

        System.exit(1);
    }

    private static void printAndExit(String... messages) {
        for (String message : messages) {
            System.err.println(message);
        }
        System.exit(1);
    }

    private ApkBuilderMain() {
    }
}
