/*
 * The MIT License
 *
 * Copyright (c) 2011, Christoph Kutzinski
 *
 * 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 org.jvnet.localizer.Localizable;

import java.util.Locale;

/**
 * Describes an {@link Result} trend by taking the comparing the result of the current and the previous build.
 * 
 * @author kutzi
 * @since 1.416
 */
public enum ResultTrend {
    /**
     * Previous build was {@link Result#FAILURE} or {@link Result#UNSTABLE}
     * and is now {@link Result#SUCCESS}.
     */
    FIXED(Messages._ResultTrend_Fixed()),
    /**
     * Build as well as previous build (if it has a previous build) are {@link Result#SUCCESS}
     */
    SUCCESS(Messages._ResultTrend_Success()),
    /**
     * Previous build was {@link Result#FAILURE} and is now 'only' {@link Result#UNSTABLE}.
     */
    NOW_UNSTABLE(Messages._ResultTrend_NowUnstable()),
    /**
     * Build as well as previous build are {@link Result#UNSTABLE}.
     */
    STILL_UNSTABLE(Messages._ResultTrend_StillUnstable()),
    /**
     * Previous build (if there is one) was {@link Result#SUCCESS} and current build is {@link Result#UNSTABLE}.
     */
    UNSTABLE(Messages._ResultTrend_Unstable()),
    /**
     * Build as well as previous build are {@link Result#FAILURE}.
     */
    STILL_FAILING(Messages._ResultTrend_StillFailing()),
    /**
     * Previous build (if there is one) was {@link Result#SUCCESS} or {@link Result#UNSTABLE}
     * and current build is {@link Result#FAILURE}.
     */
    FAILURE(Messages._ResultTrend_Failure()),
    /**
     * Build was aborted.
     */
    ABORTED(Messages._ResultTrend_Aborted()),
    /**
     * Build didn't run (yet).
     */
    NOT_BUILT(Messages._ResultTrend_NotBuilt());
    
    private final Localizable description;

    ResultTrend(Localizable description) {
        this.description = description;
    }
    
    /**
     * Returns a short human-readable description of the result.
     */
    public String getDescription() {
        return this.description.toString();
    }

    /**
     * Gets all upper case ID like token of the build status.
     */
    public String getID() {
        return this.description.toString(Locale.ENGLISH).toUpperCase(Locale.ENGLISH);
    }
    
    /**
     * Returns the result trend of a build.
     * 
     * @param build the build
     * @return the result trend
     */
    public static ResultTrend getResultTrend(AbstractBuild<?, ?> build) {
        return getResultTrend((Run<?,?>)build);
    }

    /**
     * Returns the result trend of a run.
     * 
     * @param run the run
     * @return the result trend
     * 
     * @since 1.441
     */
    public static ResultTrend getResultTrend(Run<?, ?> run) {
        
        Result result = run.getResult();
        
        if (result == Result.ABORTED) {
            return ABORTED;
        } else if (result == Result.NOT_BUILT) {
            return NOT_BUILT;
        }
        
        if (result == Result.SUCCESS) {
            if (isFix(run)) {
                return FIXED;
            } else {
                return SUCCESS;
            }
        }
        
        Run<?, ?> previousBuild = getPreviousNonAbortedBuild(run);
        if (result == Result.UNSTABLE) {
            if (previousBuild == null) {
                return UNSTABLE;
            }
            
            
            if (previousBuild.getResult() == Result.UNSTABLE) {
                return STILL_UNSTABLE;
            } else if (previousBuild.getResult() == Result.FAILURE) {
                return NOW_UNSTABLE;
            } else {
                return UNSTABLE;
            }
        } else if (result == Result.FAILURE) {
            if (previousBuild != null && previousBuild.getResult() == Result.FAILURE) {
                return STILL_FAILING;
            } else {
                return FAILURE;
            }
        }
        
        throw new IllegalArgumentException("Unknown result: '" + result + "' for build: " + run);
    }
    
    /**
     * Returns the previous 'not aborted' build (i.e. ignores ABORTED and NOT_BUILT builds)
     * or null.
    */
    private static Run<?, ?> getPreviousNonAbortedBuild(Run<?, ?> build) {
        Run<?, ?> previousBuild = build.getPreviousBuild();
        while (previousBuild != null) {
            if (previousBuild.getResult() == null 
                || previousBuild.getResult() == Result.ABORTED
                || previousBuild.getResult() == Result.NOT_BUILT) {
                
                previousBuild = previousBuild.getPreviousBuild();
            } else {
                return previousBuild;
            }
        }
        return previousBuild;
    }
    
    /**
     * Returns true if this build represents a 'fix'.
     * I.e. it is the first successful build after previous
     * 'failed' and/or 'unstable' builds.
     * Ignores 'aborted' and 'not built' builds.
     */
    private static boolean isFix(Run<?, ?> build) {
        if (build.getResult() != Result.SUCCESS) {
            return false;
        }
        
        Run<?, ?> previousBuild = getPreviousNonAbortedBuild(build);
        if (previousBuild != null) {
            return previousBuild.getResult().isWorseThan(Result.SUCCESS);
        }
        return false;
    }
}
