/*
 * The MIT License
 *
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Martin Eigenbrodt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package hudson.model;

import static hudson.model.Result.FAILURE;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Functions;
import hudson.Launcher;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Notifier;
import hudson.tasks.Recorder;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
 * A build of a {@link Project}.
 *
 * <h2>Steps of a build</h2>
 * <p>
 * Roughly speaking, a {@link Build} goes through the following stages:
 *
 * <dl>
 * <dt>SCM checkout
 * <dd>Hudson decides which directory to use for a build, then the source code is checked out
 *
 * <dt>Pre-build steps
 * <dd>Everyone gets their {@link BuildStep#prebuild(AbstractBuild, BuildListener)} invoked
 * to indicate that the build is starting
 *
 * <dt>Build wrapper set up
 * <dd>{@link BuildWrapper#setUp(AbstractBuild, Launcher, BuildListener)} is invoked. This is normally
 * to prepare an environment for the build.
 *
 * <dt>Builder runs
 * <dd>{@link Builder#perform(AbstractBuild, Launcher, BuildListener)} is invoked. This is where
 * things that are useful to users happen, like calling Ant, Make, etc.
 *
 * <dt>Recorder runs
 * <dd>{@link Recorder#perform(AbstractBuild, Launcher, BuildListener)} is invoked. This is normally
 * to record the output from the build, such as test results.
 *
 * <dt>Notifier runs
 * <dd>{@link Notifier#perform(AbstractBuild, Launcher, BuildListener)} is invoked. This is normally
 * to send out notifications, based on the results determined so far.
 * </dl>
 *
 * <p>
 * And beyond that, the build is considered complete, and from then on {@link Build} object is there to
 * keep the record of what happened in this build.
 *
 * @author Kohsuke Kawaguchi
 */
public abstract class Build<P extends Project<P, B>, B extends Build<P, B>>
    extends AbstractBuild<P, B> {

    /**
     * Creates a new build.
     */
    protected Build(P project) throws IOException {
        super(project);
    }

    protected Build(P job, Calendar timestamp) {
        super(job, timestamp);
    }

    /**
     * Loads a build from a log file.
     */
    protected Build(P project, File buildDir) throws IOException {
        super(project, buildDir);
    }

//
//
// actions
//
//
    @Override
    public void run() {
        execute(createRunner());
    }

    /**
     * @deprecated as of 1.467
     *      Override the {@link #run()} method by calling {@link #execute(hudson.model.Run.RunExecution)} with
     *      proper execution object.
     */
    @Restricted(NoExternalUse.class)
    @Deprecated
    protected Runner createRunner() {
        return new BuildExecution();
    }

    /**
     * @deprecated as of 1.467
     *      Please use {@link BuildExecution}
     */
    @Deprecated
    protected class RunnerImpl extends BuildExecution {
    }

    protected class BuildExecution extends AbstractRunner {
        /*
            Some plugins might depend on this instance castable to Runner, so we need to use
            deprecated class here.
         */

        @Override
        protected Result doRun(@NonNull BuildListener listener) throws Exception {
            if (!preBuild(listener, project.getBuilders()))
                return FAILURE;
            if (!preBuild(listener, project.getPublishersList()))
                return FAILURE;

            Result r = null;
            try {
                List<BuildWrapper> wrappers = new ArrayList<>(project.getBuildWrappers().values());

                ParametersAction parameters = getAction(ParametersAction.class);
                if (parameters != null)
                    parameters.createBuildWrappers(Build.this, wrappers);

                for (BuildWrapper w : wrappers) {
                    Environment e = w.setUp((AbstractBuild<?, ?>) Build.this, launcher, listener);
                    if (e == null)
                        return r = FAILURE;
                    buildEnvironments.add(e);
                }

                if (!build(listener, project.getBuilders()))
                    r = FAILURE;
            } catch (InterruptedException e) {
                r = Executor.currentExecutor().abortResult();
                // not calling Executor.recordCauseOfInterruption here. We do that where this exception is consumed.
                throw e;
            } finally {
                if (r != null) setResult(r);
            }

            return r;
        }

        @Override
        public void post2(@NonNull BuildListener listener) throws IOException, InterruptedException {
            if (!performAllBuildSteps(listener, project.getPublishersList(), true))
                setResult(FAILURE);
            if (!performAllBuildSteps(listener, project.getProperties(), true))
                setResult(FAILURE);
        }

        @Override
        public void cleanUp(@NonNull BuildListener listener) throws Exception {
            // at this point it's too late to mark the build as a failure, so ignore return value.
            try {
                performAllBuildSteps(listener, project.getPublishersList(), false);
                performAllBuildSteps(listener, project.getProperties(), false);
            } catch (Exception x) {
                Functions.printStackTrace(x, listener.error(Messages.Build_post_build_steps_failed()));
            }
            super.cleanUp(listener);
        }

        private boolean build(@NonNull BuildListener listener, @NonNull Collection<Builder> steps) throws IOException, InterruptedException {
            for (BuildStep bs : steps) {
                if (!perform(bs, listener)) {
                    LOGGER.log(Level.FINE, "{0} : {1} failed", new Object[] {Build.this, bs});
                    return false;
                }

                Executor executor = getExecutor();
                if (executor != null && executor.isInterrupted()) {
                    // someone asked build interruption, let stop the build before trying to run another build step
                    throw new InterruptedException();
                }
            }
            return true;
        }
    }

    private static final Logger LOGGER = Logger.getLogger(Build.class.getName());
}
