/*
 * Copyright (C) 2012 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.motorolamobility.preflighting.core.internal.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.eclipse.core.runtime.Path;

import com.motorolamobility.preflighting.core.exception.PreflightingToolException;
import com.motorolamobility.preflighting.core.i18n.PreflightingCoreNLS;
import com.motorolamobility.preflighting.core.logging.PreflightingLogger;
import com.motorolamobility.preflighting.core.validation.ValidationManagerConfiguration;

/**
 * This class holds methods which deal with APK package.
 */
public final class ApkUtils
{
    public static final String APP_VALIDATOR_TEMP_DIR = "MotodevAppValidator";

    private static final String JAVA_TEMP_DIR_PROPERTY = "java.io.tmpdir";

    private static final String TEMP_DIR_PATH = System.getProperty(JAVA_TEMP_DIR_PROPERTY);

    // Temp folder used for APK extracting
    public static final File tmpAppValidatorFolder =
            new File(TEMP_DIR_PATH, APP_VALIDATOR_TEMP_DIR);

    private static final String RSA = ".rsa";

    private static final String DSA = ".dsa";

    public static final String APK_EXTENSION = ".apk";

    public static final String ZIP_EXTENSION = ".zip";

    private static final int BUFFER_SIZE = 1024;

    /**
     * Give an APK file, a tree directories and files are extracted,
     * representing partially the project which generated the APK.
     * <p>
     * The files are created in a temporary directory.
     * 
     * @param apkFile
     *            APK file which the project tree will be extracted from.
     * @param sdkPath
     *            SDK path where the tools for extracting and interpreting the
     *            APK information will be used.
     * 
     * @return A file object holding a tree of directories and files which
     *         represent partially the project which generated the APK.
     * 
     * @throws PreflightingToolException
     *             Exception thrown when there are problems creating the files
     *             and directories structure.
     */
    public static File extractProjectFromAPK(File apkFile, String sdkPath)
            throws PreflightingToolException
    {
        String apkName = apkFile.getName();

        // Create a temp directory to contain all extracted packages, if needed
        if (!tmpAppValidatorFolder.exists())
        {

            try
            {
                tmpAppValidatorFolder.mkdir();
            }
            catch (SecurityException se)
            {
                PreflightingLogger.error(ApkUtils.class,
                        "It was not possible to extract the android package.", se); //$NON-NLS-1$
                throw new PreflightingToolException(
                        PreflightingCoreNLS.ApkUtils_ImpossibleExtractAndroidPackageMessage, se);
            }
        }

        File tmpProjectFile;
        try
        {
            tmpProjectFile = File.createTempFile(apkName, null, tmpAppValidatorFolder);
            tmpProjectFile.delete();
            tmpProjectFile.mkdir();
        }
        catch (IOException ioException)
        {
            PreflightingLogger.error(ApkUtils.class,
                    "It was not possible to extract the android package.", ioException); //$NON-NLS-1$
            throw new PreflightingToolException(
                    PreflightingCoreNLS.ApkUtils_ImpossibleExtractAndroidPackageMessage,
                    ioException);
        }

        String extractionMode =
                ValidationManagerConfiguration.getInstance().getProperty(
                        ValidationManagerConfiguration.ConfigProperties.APK_EXTRACTION_MODE
                                .getName());
        if (extractionMode.equals(ValidationManagerConfiguration.ExtractionModes.APKTOOL_MODE
                .getMode()))
        {
            ApktoolUtils.extractFilesFromApk(apkFile, tmpProjectFile);
        }
        else
        {
            AaptUtils.extractFilesFromAPK(apkFile, sdkPath, tmpProjectFile);
        }

        return tmpProjectFile;
    }

    /**
     * Returns a handler to the temp directory used for extracting APKs.
     * 
     * @return A handler to the temp directory.
     */
    public static File getAppValidatorTempApkFolder()
    {
        return tmpAppValidatorFolder;
    }

    /**
     * Iterates over APK (jar entries) to populate
     * 
     * @param projectFile
     * @return
     * @throws IOException
     * @throws CertificateException
     */
    public static List<Certificate> populateCertificate(File projectFile) throws IOException,
            CertificateException
    {
        List<Certificate> certList = new ArrayList<Certificate>();
        JarFile jar = new JarFile(projectFile);
        Enumeration<JarEntry> jarEntries = jar.entries();
        while (jarEntries.hasMoreElements())
        {
            JarEntry entry = jarEntries.nextElement();
            if (entry.getName().toLowerCase().contains(DSA)
                    || entry.getName().toLowerCase().contains(RSA))
            {
                certList.addAll(extractCertificate(jar, entry));
            }
        }
        return certList;
    }

    /**
     * Extracts certificate from APK
     * 
     * @param jar
     * @param entry
     *            rsa or dsa jar item
     * @return
     * @throws IOException
     *             I/O problem to read jar
     * @throws CertificateException
     *             certificate has problems
     */
    private static List<Certificate> extractCertificate(JarFile jar, JarEntry entry)
            throws IOException, CertificateException
    {
        List<Certificate> certList = new ArrayList<Certificate>();
        InputStream inStream = null;
        try
        {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            inStream = jar.getInputStream(entry);
            Collection<? extends Certificate> c = cf.generateCertificates(inStream);
            Iterator<? extends Certificate> i = c.iterator();
            while (i.hasNext())
            {
                Certificate cert = i.next();
                certList.add(cert);
            }
        }
        finally
        {
            if (inStream != null)
            {
                inStream.close();
            }
        }
        return certList;
    }

    /**
     * Unzip zipFile and returns the directory created with its contents
     * @param zipFile
     * @return 
     * @throws PreflightingToolException
     */
    public static File unzip(File zipFile) throws PreflightingToolException
    {
        File tempExtractionDir = null;
        ZipInputStream apkInputStream = null;
        FileOutputStream apkOutputStream = null;

        try
        {
            //crate MOTODEV temp folder
            if (!tmpAppValidatorFolder.exists())
            {
                tmpAppValidatorFolder.mkdir();
            }
            //create extraction folder
            tempExtractionDir = File.createTempFile(zipFile.getName(), null, tmpAppValidatorFolder);
            tempExtractionDir.delete();
            tempExtractionDir.mkdir();

            //open zipFile stream
            apkInputStream = new ZipInputStream(new FileInputStream(zipFile.getAbsolutePath()));
            ZipEntry apkZipEntry = apkInputStream.getNextEntry();

            byte[] buf = new byte[BUFFER_SIZE];
            CRC32 crc = new CRC32();

            //while there are apks inside zip file
            while (apkZipEntry != null)
            {
                crc.reset();
                if (apkZipEntry.getName().endsWith(APK_EXTENSION))
                {
                    //file to be created (apk)
                    try
                    {
                        apkOutputStream =
                                new FileOutputStream(tempExtractionDir.getAbsolutePath()
                                        + Path.SEPARATOR + apkZipEntry.getName());

                        int length = 0;
                        //creates apk and updates CRC during the process
                        while ((length = apkInputStream.read(buf, 0, BUFFER_SIZE)) > -1)
                        {
                            apkOutputStream.write(buf, 0, length);
                            crc.update(buf, 0, length);
                        }

                        //test if extraction went fine
                        if (crc.getValue() != apkZipEntry.getCrc())
                        {
                            throw new PreflightingToolException(PreflightingCoreNLS.bind(
                                    PreflightingCoreNLS.ApkUtils_ZipExtractionFile,
                                    apkZipEntry.getName()));
                        }
                    }
                    finally
                    {
                        if (apkOutputStream != null)
                        {
                            try
                            {
                                apkOutputStream.close();
                            }
                            catch (IOException e)
                            {
                                // do nothing
                            }
                        }
                    }
                }
                apkZipEntry = apkInputStream.getNextEntry();
            }
        }
        catch (IOException ioe)
        {
            //error during extraction, abort validation
            throw new PreflightingToolException(PreflightingCoreNLS.ApkUtils_ZipExtraction, ioe);
        }
        finally
        {
            try
            {
                if (apkInputStream != null)
                {
                    apkInputStream.close();
                }
            }
            catch (IOException e)
            {
                // do nothing
            }
        }

        return tempExtractionDir;
    }
}