/*
 * Copyright (C) 2011 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.wizards.newproject;

import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import com.android.sdklib.xml.AndroidManifest;
import com.android.sdklib.xml.ManifestData;
import com.android.sdklib.xml.ManifestData.Activity;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.ui.IWorkingSet;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * The {@link NewProjectWizardState} holds the state used by the various pages
 * in the {@link NewProjectWizard} and its variations, and it can also be used
 * to pass project information to the {@link NewProjectCreator}.
 */
public class NewProjectWizardState {
    /** The mode to run the wizard in: creating test, or sample, or plain project */
    public Mode mode;

    /**
     * If true, the project should be created from an existing codebase (pointed
     * to by the {@link #projectLocation} or in the case of sample projects, the
     * {@link #chosenSample}. Otherwise, create a brand new project from scratch.
     */
    public boolean useExisting;

    /**
     * Whether new projects should be created into the default project location
     * (e.g. in the Eclipse workspace) or not
     */
    public boolean useDefaultLocation = true;

    /** The build target SDK */
    public IAndroidTarget target;
    /** True if the user has manually modified the target */
    public boolean targetModifiedByUser;

    /** The location to store projects into */
    public File projectLocation = new File(Platform.getLocation().toOSString());
    /** True if the project location name has been manually edited by the user */
    public boolean projectLocationModifiedByUser;

    /** The name of the project */
    public String projectName = ""; //$NON-NLS-1$
    /** True if the project name has been manually edited by the user */
    public boolean projectNameModifiedByUser;

    /** The application name */
    public String applicationName;
    /** True if the application name has been manually edited by the user */
    public boolean applicationNameModifiedByUser;

    /** The package path */
    public String packageName;
    /** True if the package name has been manually edited by the user */
    public boolean packageNameModifiedByUser;

    /** True if a new activity should be created */
    public boolean createActivity = true;
    /** The name of the new activity to be created */
    public String activityName;
    /** True if the activity name has been manually edited by the user */
    public boolean activityNameModifiedByUser;

    /** The minimum SDK version to use with the project (may be null or blank) */
    public String minSdk;
    /** True if the minimum SDK version has been manually edited by the user */
    public boolean minSdkModifiedByUser;

    /**
     * The directory where the samples are found. This field is only applicable
     * when the wizard is running in create-sample-mode.
     */
    public File samplesDir;
    /** A list of paths to each of the available samples for the current SDK */
    public List<File> samples = new ArrayList<File>();
    /** Path to the currently chosen sample */
    public File chosenSample;

    /** The name of the source folder, relative to the project root */
    public String sourceFolder = SdkConstants.FD_SOURCES;
    /** The set of chosen working sets to use when creating the project */
    public IWorkingSet[] workingSets = new IWorkingSet[0];

    /**
     * A reference to a different project that the current test project will be
     * testing.
     */
    public IProject testedProject;
    /**
     * If true, this test project should be testing itself, otherwise it will be
     * testing the project pointed to by {@link #testedProject}.
     */
    public boolean testingSelf;

    // NOTE: These apply only to creating paired projects; when isTest is true
    // we're using
    // the normal fields above
    /**
     * If true, create a test project along with this plain project which will
     * be testing the plain project. (This flag only applies when creating
     * normal projects.)
     */
    public boolean createPairProject;
    /**
     * The application name of the test application (only applies when
     * {@link #createPairProject} is true)
     */
    public String testApplicationName;
    /**
     * True if the testing application name has been modified by the user (only
     * applies when {@link #createPairProject} is true)
     */
    public boolean testApplicationNameModified;
    /**
     * The package name of the test application (only applies when
     * {@link #createPairProject} is true)
     */
    public String testPackageName;
    /**
     * True if the testing package name has been modified by the user (only
     * applies when {@link #createPairProject} is true)
     */
    public boolean testPackageModified;
    /**
     * The project name of the test project (only applies when
     * {@link #createPairProject} is true)
     */
    public String testProjectName;
    /**
     * True if the testing project name has been modified by the user (only
     * applies when {@link #createPairProject} is true)
     */
    public boolean testProjectModified;
    /** Package name of the tested app */
    public String testTargetPackageName;

    /**
     * Creates a new {@link NewProjectWizardState}
     *
     * @param mode the mode to run the wizard in
     */
    public NewProjectWizardState(Mode mode) {
        this.mode = mode;
        if (mode == Mode.SAMPLE) {
            useExisting = true;
        } else if (mode == Mode.TEST) {
            createActivity = false;
        }
    }

    /**
     * Extract information (package name, application name, minimum SDK etc) from
     * the given Android project.
     *
     * @param path the path to the project to extract information from
     */
    public void extractFromAndroidManifest(Path path) {
        String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
        if (!(new File(osPath).exists())) {
            return;
        }

        ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
        if (manifestData == null) {
            return;
        }

        String newPackageName = null;
        Activity activity = null;
        String newActivityName = null;
        String minSdkVersion = null;
        try {
            newPackageName = manifestData.getPackage();
            minSdkVersion = manifestData.getMinSdkVersionString();

            // try to get the first launcher activity. If none, just take the first activity.
            activity = manifestData.getLauncherActivity();
            if (activity == null) {
                Activity[] activities = manifestData.getActivities();
                if (activities != null && activities.length > 0) {
                    activity = activities[0];
                }
            }
        } catch (Exception e) {
            // ignore exceptions
        }

        if (newPackageName != null && newPackageName.length() > 0) {
            packageName = newPackageName;;
        }

        if (activity != null) {
            newActivityName = AndroidManifest.extractActivityName(activity.getName(),
                    newPackageName);
        }

        if (newActivityName != null && newActivityName.length() > 0) {
            activityName = newActivityName;
            // we are "importing" an existing activity, not creating a new one
            createActivity = false;

            // If project name and application names are empty, use the activity
            // name as a default. If the activity name has dots, it's a part of a
            // package specification and only the last identifier must be used.
            if (newActivityName.indexOf('.') != -1) {
                String[] ids = newActivityName.split(AdtConstants.RE_DOT);
                newActivityName = ids[ids.length - 1];
            }
            if (projectName == null || projectName.length() == 0 ||
                    !projectNameModifiedByUser) {
                projectName = newActivityName;
                projectNameModifiedByUser = false;
            }
            if (applicationName == null || applicationName.length() == 0 ||
                    !applicationNameModifiedByUser) {
                applicationNameModifiedByUser = false;
                applicationName = newActivityName;
            }
        } else {
            activityName = ""; //$NON-NLS-1$

            // There is no activity name to use to fill in the project and application
            // name. However if there's a package name, we can use this as a base.
            if (newPackageName != null && newPackageName.length() > 0) {
                // Package name is a java identifier, so it's most suitable for
                // an application name.

                if (applicationName == null || applicationName.length() == 0 ||
                        !applicationNameModifiedByUser) {
                    applicationName = newPackageName;
                }

                // For the project name, remove any dots
                newPackageName = newPackageName.replace('.', '_');
                if (projectName == null || projectName.length() == 0 ||
                        !projectNameModifiedByUser) {
                    projectName = newPackageName;
                }

            }
        }

        if (mode == Mode.ANY && useExisting) {
            updateSdkTargetToMatchProject(path.toFile());
        }

        minSdk = minSdkVersion;
        minSdkModifiedByUser = false;
    }

    /**
     * Try to find an SDK Target that matches the current MinSdkVersion.
     *
     * There can be multiple targets with the same sdk api version, so don't change
     * it if it's already at the right version. Otherwise pick the first target
     * that matches.
     */
    public void updateSdkTargetToMatchMinSdkVersion() {
        IAndroidTarget currentTarget = target;
        if (currentTarget != null && currentTarget.getVersion().equals(minSdk)) {
            return;
        }

        Sdk sdk = Sdk.getCurrent();
        if (sdk != null) {
            IAndroidTarget[] targets = sdk.getTargets();
            for (IAndroidTarget t : targets) {
                if (t.getVersion().equals(minSdk)) {
                    target = t;
                    return;
                }
            }
        }
    }

    /**
     * Updates the SDK to reflect the SDK required by the project at the given
     * location
     *
     * @param location the location of the project
     */
    public void updateSdkTargetToMatchProject(File location) {
        // Select the target matching the manifest's sdk or build properties, if any
        IAndroidTarget foundTarget = null;
        // This is the target currently in the UI
        IAndroidTarget currentTarget = target;
        String projectPath = location.getPath();

        // If there's a current target defined, we do not allow to change it when
        // operating in the create-from-sample mode -- since the available sample list
        // is tied to the current target, so changing it would invalidate the project we're
        // trying to load in the first place.
        if (!targetModifiedByUser) {
            ProjectProperties p = ProjectProperties.load(projectPath,
                    PropertyType.PROJECT);
            if (p != null) {
                String v = p.getProperty(ProjectProperties.PROPERTY_TARGET);
                IAndroidTarget desiredTarget = Sdk.getCurrent().getTargetFromHashString(v);
                // We can change the current target if:
                // - we found a new desired target
                // - there is no current target
                // - or the current target can't run the desired target
                if (desiredTarget != null &&
                        (currentTarget == null || !desiredTarget.canRunOn(currentTarget))) {
                    foundTarget = desiredTarget;
                }
            }

            Sdk sdk = Sdk.getCurrent();
            IAndroidTarget[] targets = null;
            if (sdk != null) {
                targets = sdk.getTargets();
            }
            if (targets == null) {
                targets = new IAndroidTarget[0];
            }

            if (foundTarget == null && minSdk != null) {
                // Otherwise try to match the requested min-sdk-version if we find an
                // exact match, regardless of the currently selected target.
                for (IAndroidTarget existingTarget : targets) {
                    if (existingTarget != null &&
                            existingTarget.getVersion().equals(minSdk)) {
                        foundTarget = existingTarget;
                        break;
                    }
                }
            }

            if (foundTarget == null) {
                // Or last attempt, try to match a sample project location and use it
                // if we find an exact match, regardless of the currently selected target.
                for (IAndroidTarget existingTarget : targets) {
                    if (existingTarget != null &&
                            projectPath.startsWith(existingTarget.getLocation())) {
                        foundTarget = existingTarget;
                        break;
                    }
                }
            }
        }

        if (foundTarget != null) {
            target = foundTarget;
        }
    }

    /**
     * Type of project being offered/created by the wizard
     */
    public enum Mode {
        /** Create a sample project. Testing options are not presented. */
        SAMPLE,

        /**
         * Create a test project, either testing itself or some other project.
         * Note that even if in the {@link #ANY} mode, a test project can be
         * created as a *paired* project with the main project, so this flag
         * only means that we are creating *just* a test project
         */
        TEST,

        /**
         * Create an Android project, which can be a plain project, optionally
         * with a paired test project, or a sample project (the first page
         * contains toggles for choosing which
         */
        ANY;
    }
}