/*
 * Copyright (C) 2007 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.project;

import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder;
import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder;
import com.android.ide.eclipse.adt.internal.build.builders.ResourceManagerBuilder;

import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IProjectNature;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.JavaCore;

/**
 * Project nature for the Android Projects.
 */
public class AndroidNature implements IProjectNature {

    /** the project this nature object is associated with */
    private IProject mProject;

    /**
     * Configures this nature for its project. This is called by the workspace
     * when natures are added to the project using
     * <code>IProject.setDescription</code> and should not be called directly
     * by clients. The nature extension id is added to the list of natures
     * before this method is called, and need not be added here.
     *
     * Exceptions thrown by this method will be propagated back to the caller of
     * <code>IProject.setDescription</code>, but the nature will remain in
     * the project description.
     *
     * The Android nature adds the pre-builder and the APK builder if necessary.
     *
     * @see org.eclipse.core.resources.IProjectNature#configure()
     * @throws CoreException if configuration fails.
     */
    public void configure() throws CoreException {
        configureResourceManagerBuilder(mProject);
        configurePreBuilder(mProject);
        configureApkBuilder(mProject);
    }

    /**
     * De-configures this nature for its project. This is called by the
     * workspace when natures are removed from the project using
     * <code>IProject.setDescription</code> and should not be called directly
     * by clients. The nature extension id is removed from the list of natures
     * before this method is called, and need not be removed here.
     *
     * Exceptions thrown by this method will be propagated back to the caller of
     * <code>IProject.setDescription</code>, but the nature will still be
     * removed from the project description.
     *
     * The Android nature removes the custom pre builder and APK builder.
     *
     * @see org.eclipse.core.resources.IProjectNature#deconfigure()
     * @throws CoreException if configuration fails.
     */
    public void deconfigure() throws CoreException {
        // remove the android builders
        removeBuilder(mProject, ResourceManagerBuilder.ID);
        removeBuilder(mProject, PreCompilerBuilder.ID);
        removeBuilder(mProject, PostCompilerBuilder.ID);
    }

    /**
     * Returns the project to which this project nature applies.
     *
     * @return the project handle
     * @see org.eclipse.core.resources.IProjectNature#getProject()
     */
    public IProject getProject() {
        return mProject;
    }

    /**
     * Sets the project to which this nature applies. Used when instantiating
     * this project nature runtime. This is called by
     * <code>IProject.create()</code> or
     * <code>IProject.setDescription()</code> and should not be called
     * directly by clients.
     *
     * @param project the project to which this nature applies
     * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject)
     */
    public void setProject(IProject project) {
        mProject = project;
    }

    /**
     * Adds the Android Nature and the Java Nature to the project if it doesn't
     * already have them.
     *
     * @param project An existing or new project to update
     * @param monitor An optional progress monitor. Can be null.
     * @throws CoreException if fails to change the nature.
     */
    public static synchronized void setupProjectNatures(IProject project,
            IProgressMonitor monitor) throws CoreException {
        if (project == null || !project.isOpen()) return;
        if (monitor == null) monitor = new NullProgressMonitor();

        // Add the natures. We need to add the Java nature first, so it adds its builder to the
        // project first. This way, when the android nature is added, we can control where to put
        // the android builders in relation to the java builder.
        // Adding the java nature after the android one, would place the java builder before the
        // android builders.
        addNatureToProjectDescription(project, JavaCore.NATURE_ID, monitor);
        addNatureToProjectDescription(project, AdtConstants.NATURE_DEFAULT, monitor);
    }

    /**
     * Add the specified nature to the specified project. The nature is only
     * added if not already present.
     * <p/>
     * Android Natures are always inserted at the beginning of the list of natures in order to
     * have the jdt views/dialogs display the proper icon.
     *
     * @param project The project to modify.
     * @param natureId The Id of the nature to add.
     * @param monitor An existing progress monitor.
     * @throws CoreException if fails to change the nature.
     */
    private static void addNatureToProjectDescription(IProject project,
            String natureId, IProgressMonitor monitor) throws CoreException {
        if (!project.hasNature(natureId)) {

            IProjectDescription description = project.getDescription();
            String[] natures = description.getNatureIds();
            String[] newNatures = new String[natures.length + 1];

            // Android natures always come first.
            if (natureId.equals(AdtConstants.NATURE_DEFAULT)) {
                System.arraycopy(natures, 0, newNatures, 1, natures.length);
                newNatures[0] = natureId;
            } else {
                System.arraycopy(natures, 0, newNatures, 0, natures.length);
                newNatures[natures.length] = natureId;
            }

            description.setNatureIds(newNatures);
            project.setDescription(description, new SubProgressMonitor(monitor, 10));
        }
    }

    /**
     * Adds the ResourceManagerBuilder, if its not already there. It'll insert
     * itself as the first builder.
     * @throws CoreException
     *
     */
    public static void configureResourceManagerBuilder(IProject project)
            throws CoreException {
        // get the builder list
        IProjectDescription desc = project.getDescription();
        ICommand[] commands = desc.getBuildSpec();

        // look for the builder in case it's already there.
        for (int i = 0; i < commands.length; ++i) {
            if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) {
                return;
            }
        }

        // it's not there, lets add it at the beginning of the builders
        ICommand[] newCommands = new ICommand[commands.length + 1];
        System.arraycopy(commands, 0, newCommands, 1, commands.length);
        ICommand command = desc.newCommand();
        command.setBuilderName(ResourceManagerBuilder.ID);
        newCommands[0] = command;
        desc.setBuildSpec(newCommands);
        project.setDescription(desc, null);
    }

    /**
     * Adds the PreCompilerBuilder if its not already there. It'll check for
     * presence of the ResourceManager and insert itself right after.
     * @param project
     * @throws CoreException
     */
    public static void configurePreBuilder(IProject project)
            throws CoreException {
        // get the builder list
        IProjectDescription desc = project.getDescription();
        ICommand[] commands = desc.getBuildSpec();

        // look for the builder in case it's already there.
        for (int i = 0; i < commands.length; ++i) {
            if (PreCompilerBuilder.ID.equals(commands[i].getBuilderName())) {
                return;
            }
        }

        // we need to add it after the resource manager builder.
        // Let's look for it
        int index = -1;
        for (int i = 0; i < commands.length; ++i) {
            if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) {
                index = i;
                break;
            }
        }

        // we're inserting after
        index++;

        // do the insertion

        // copy the builders before.
        ICommand[] newCommands = new ICommand[commands.length + 1];
        System.arraycopy(commands, 0, newCommands, 0, index);

        // insert the new builder
        ICommand command = desc.newCommand();
        command.setBuilderName(PreCompilerBuilder.ID);
        newCommands[index] = command;

        // copy the builder after
        System.arraycopy(commands, index, newCommands, index + 1, commands.length-index);

        // set the new builders in the project
        desc.setBuildSpec(newCommands);
        project.setDescription(desc, null);
    }

    public static void configureApkBuilder(IProject project)
            throws CoreException {
        // Add the .apk builder at the end if it's not already there
        IProjectDescription desc = project.getDescription();
        ICommand[] commands = desc.getBuildSpec();

        for (int i = 0; i < commands.length; ++i) {
            if (PostCompilerBuilder.ID.equals(commands[i].getBuilderName())) {
                return;
            }
        }

        ICommand[] newCommands = new ICommand[commands.length + 1];
        System.arraycopy(commands, 0, newCommands, 0, commands.length);
        ICommand command = desc.newCommand();
        command.setBuilderName(PostCompilerBuilder.ID);
        newCommands[commands.length] = command;
        desc.setBuildSpec(newCommands);
        project.setDescription(desc, null);
    }

    /**
     * Removes a builder from the project.
     * @param project The project to remove the builder from.
     * @param id The String ID of the builder to remove.
     * @return true if the builder was found and removed.
     * @throws CoreException
     */
    public static boolean removeBuilder(IProject project, String id) throws CoreException {
        IProjectDescription description = project.getDescription();
        ICommand[] commands = description.getBuildSpec();
        for (int i = 0; i < commands.length; ++i) {
            if (id.equals(commands[i].getBuilderName())) {
                ICommand[] newCommands = new ICommand[commands.length - 1];
                System.arraycopy(commands, 0, newCommands, 0, i);
                System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1);
                description.setBuildSpec(newCommands);
                project.setDescription(description, null);
                return true;
            }
        }

        return false;
    }
}
