/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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.ide.eclipse.adt.internal.actions;

import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidPrintStream;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFolderWrapper;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.export.ApkData;
import com.android.sdklib.internal.export.MultiApkExportHelper;
import com.android.sdklib.internal.export.ProjectConfig;
import com.android.sdklib.internal.export.MultiApkExportHelper.ExportException;
import com.android.sdklib.internal.export.MultiApkExportHelper.Target;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Multiple APK export Action.
 * The action is triggered on a project selection, and performs a full APK export based on the
 * content of the export.properties file.
 */
public class MultiApkExportAction implements IObjectActionDelegate {

    private ISelection mSelection;

    public void setActivePart(IAction action, IWorkbenchPart targetPart) {
        // pass
    }

    public void run(IAction action) {
        if (mSelection instanceof IStructuredSelection) {
            for (Iterator<?> it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) {
                Object element = it.next();
                IProject project = null;
                if (element instanceof IProject) {
                    project = (IProject)element;
                } else if (element instanceof IAdaptable) {
                    project = (IProject)((IAdaptable)element).getAdapter(IProject.class);
                }
                if (project != null) {
                    IWorkbench workbench = PlatformUI.getWorkbench();
                    final IProject fProject = project;
                    try {
                        workbench.getProgressService().busyCursorWhile(new IRunnableWithProgress() {
                            /**
                             * Run the export.
                             * @throws InvocationTargetException
                             * @throws InterruptedException
                             */
                            public void run(IProgressMonitor monitor)
                                    throws InvocationTargetException, InterruptedException {
                                try {
                                    runMultiApkExport(fProject, monitor);
                                } catch (Exception e) {
                                    AdtPlugin.logAndPrintError(e, fProject.getName(),
                                            "Failed to export project: %1$s", e.getMessage());
                                } finally {
                                    monitor.done();
                                }
                            }
                        });
                    } catch (Exception e) {
                        AdtPlugin.logAndPrintError(e, project.getName(),
                                "Failed to export project: %1$s",
                                e.getMessage());
                    }
                }
            }
        }
    }

    public void selectionChanged(IAction action, ISelection selection) {
        mSelection = selection;
    }

    /**
     * Runs the multi-apk export.
     * @param exportProject the main "export" project.
     * @param monitor the progress monitor.
     * @throws ExportException
     * @throws CoreException
     */
    private void runMultiApkExport(IProject exportProject, IProgressMonitor monitor)
            throws ExportException, CoreException {

        ProjectProperties props = ProjectProperties.load(new IFolderWrapper(exportProject),
                PropertyType.EXPORT);

        // get some props and make sure their values are valid.

        String appPackage = props.getProperty(ProjectProperties.PROPERTY_PACKAGE);
        if (appPackage == null || appPackage.length() == 0) {
            throw new IllegalArgumentException("Invalid 'package' property values.");
        }

        String version = props.getProperty(ProjectProperties.PROPERTY_VERSIONCODE);
        int versionCode;
        try {
            versionCode = Integer.parseInt(version);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("version value is not a valid integer.", e);
        }

        String projects = props.getProperty(ProjectProperties.PROPERTY_PROJECTS);
        if (projects == null || projects.length() == 0) {
            throw new IllegalArgumentException("Missing project list.");
        }

        // create the multi apk helper to get the list of apk to export.
        MultiApkExportHelper helper = new MultiApkExportHelper(
                exportProject.getLocation().toOSString(),
                appPackage, versionCode, Target.RELEASE, System.out);

        List<ApkData> apks = helper.getApkData(projects);

        // list of projects that have been resolved (ie the IProject has been found from the
        // ProjectConfig) and compiled.
        HashMap<ProjectConfig, ProjectState> resolvedProjects =
                new HashMap<ProjectConfig, ProjectState>();

        IWorkspace ws = ResourcesPlugin.getWorkspace();
        IWorkspaceRoot wsRoot = ws.getRoot();

        // bin folder for the export project
        IFolder binFolder = exportProject.getFolder(SdkConstants.FD_OUTPUT);
        if (binFolder.exists() == false) {
            binFolder.create(true, true, monitor);
        }

        AndroidPrintStream stdout = new AndroidPrintStream(exportProject, null /*prefix*/,
                System.out);
        AndroidPrintStream stderr = new AndroidPrintStream(exportProject, null /*prefix*/,
                System.err);

        for (ApkData apk : apks) {
            // find the IProject object for this apk.
            ProjectConfig projectConfig = apk.getProjectConfig();
            ProjectState projectState = resolvedProjects.get(projectConfig);
            if (projectState == null) {
                // first time? resolve the project and compile it.
                IPath path = exportProject.getFullPath().append(projectConfig.getRelativePath());

                IResource res = wsRoot.findMember(path);
                if (res.getType() != IResource.PROJECT) {
                    throw new IllegalArgumentException(String.format(
                            "%1$s does not resolve to a project.",
                            projectConfig.getRelativePath()));
                }

                IProject project = (IProject)res;

                projectState = Sdk.getProjectState(project);
                if (projectState == null) {
                    throw new IllegalArgumentException(String.format(
                            "State for project %1$s could not be loaded.",
                            project.getName()));
                }

                if (projectState.isLibrary()) {
                    throw new IllegalArgumentException(String.format(
                            "Project %1$s is a library and cannot be part of a multi-apk export.",
                            project.getName()));
                }

                // build the project, mainly for the java compilation. The rest is handled below.
                project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);

                // store the resolved project in the map.
                resolvedProjects.put(projectConfig, projectState);
            }

            Map<String, String> variantMap = apk.getSoftVariantMap();

            if (variantMap.size() > 0) {
                // if there are soft variants, only export those.
                for (Entry<String, String> entry : variantMap.entrySet()) {
                    buildVariant(wsRoot, projectState, appPackage, versionCode, apk, entry,
                            binFolder, stdout, stderr);
                }
            } else {
                buildVariant(wsRoot, projectState, appPackage, versionCode, apk,
                        null /*soft variant*/, binFolder, stdout, stderr);
            }
        }

        helper.writeLogs();
    }

    /**
     * Builds a particular variant of an APK
     * @param wsRoot the workspace root
     * @param projectState the project to export
     * @param appPackage the application package
     * @param versionCode the major version code.
     * @param apk the {@link ApkData} describing how the export should happen.
     * @param softVariant an optional soft variant info. The entry contains (name, resource filter).
     * @param binFolder the binFolder where the file must be created.
     * @throws CoreException
     */
    private void buildVariant(IWorkspaceRoot wsRoot, ProjectState projectState, String appPackage,
            int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder,
            AndroidPrintStream stdout, AndroidPrintStream stderr)
            throws CoreException {
        try {
            // get the libraries for this project
            List<IProject> libProjects = projectState.getFullLibraryProjects();

            IProject project = projectState.getProject();
            IJavaProject javaProject = JavaCore.create(project);

            int compositeVersionCode = apk.getCompositeVersionCode(versionCode);

            // figure out the file names
            String pkgName = project.getName() + "-" + apk.getBuildInfo();
            String finalNameRoot = appPackage + "-" + compositeVersionCode;
            if (softVariant != null) {
                String tmp = "-" + softVariant.getKey();
                pkgName += tmp;
                finalNameRoot += tmp;
            }

            pkgName += ".ap_";
            String outputName = finalNameRoot + "-unsigned.apk";

            BuildHelper helper = new BuildHelper(project, stdout, stderr,
                    false /*debugMode*/, false/*verbose*/);

            // get the manifest file
            IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
            // get the project bin folder
            IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation());
            String projectBinFolderPath = projectBinFolder.getLocation().toOSString();

            // package the resources
            helper.packageResources(manifestFile, libProjects,
                    softVariant != null ? softVariant.getValue() : null,
                    compositeVersionCode, projectBinFolderPath, pkgName);

            apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName);

            // do the final export.
            IFile dexFile = projectBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX);
            String outputFile = binFolder.getFile(outputName).getLocation().toOSString();

            // get the list of referenced projects.
            List<IProject> javaRefs = ProjectHelper.getReferencedProjects(project);
            List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(javaRefs);

            helper.finalPackage(
                    new File(projectBinFolderPath, pkgName).getAbsolutePath(),
                    dexFile.getLocation().toOSString(),
                    outputFile,
                    javaProject,
                    libProjects,
                    referencedJavaProjects,
                    apk.getAbi(),
                    null, //key
                    null, //certificate
                    null); //ResourceMarker
        } catch (CoreException e) {
            throw e;
        } catch (Exception e) {
            throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
                    e.getMessage(), e));
        }

    }
}
