package com.atlassian.bitbucket.web.conditions;

import com.atlassian.plugin.PluginParseException;
import com.atlassian.plugin.web.Condition;
import com.atlassian.bitbucket.util.Version;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;

import java.util.Map;

/**
 * A Condition which displays based on the Stash version.
 * <p>
 * Two parameters are required:
 * <ul>
 *     <li>The {@code operator} param defines the operation to use.</li>
 *     <li>The {@code version} param defines the version to check against.</li>
 * </ul>
 * <p>
 * Valid {@code operator}s are:
 * <ul>
 *     <li>{@code eq} - Displays if the Stash version is equal to the {@code version} param</li>
 *     <li>{@code neq} - Displays if the Stash version is not equal to the {@code version} param</li>
 *     <li>{@code gt} - Displays if the Stash version is greater than the {@code version} param</li>
 *     <li>{@code gte} - Displays if the Stash version is greater than or equal to the {@code version} param</li>
 *     <li>{@code lt} - Displays if the Stash version is less than the {@code version} param</li>
 *     <li>{@code lte} - Displays if the Stash version is less than or equal to the {@code version} param</li>
 * </ul>
 * <p>
 * The following example will show the web fragment if Stash version 2.4 or beyond (2.4.1, 2.5, etc) is running:
 * <code>
 * <pre>
 * &lt;condition class="com.atlassian.bitbucket.web.conditions.IsApplicationVersionCondition">
 *     &lt;param name="operator">gte&lt;/param>
 *     &lt;param name="version">2.4&lt;/param>
 * &lt;/condition>
 * </pre>
 * </code>
 */
public class IsApplicationVersionCondition implements Condition {

    private static final String VERSION_PARAM = "version";
    private static final String OPERATOR_PARAM = "operator";

    private final ApplicationPropertiesService applicationPropertiesService;

    private Operator operator;
    private Version testVersion;

    public IsApplicationVersionCondition(ApplicationPropertiesService applicationPropertiesService) {
        this.applicationPropertiesService = applicationPropertiesService;
    }

    @Override
    public void init(Map<String, String> params) throws PluginParseException {
        operator = getOperator(params);
        testVersion = getVersion(params);
    }

    @Override
    public boolean shouldDisplay(Map<String, Object> context) {
        String stashVersionString = applicationPropertiesService.getBuildVersion();
        Version stashVersion = new Version(stashVersionString);
        return operator.test(stashVersion.compareTo(testVersion));
    }

    private Version getVersion(Map<String, String> params) {
        String stringVersion = params.get(VERSION_PARAM);
        if (stringVersion == null) {
            throw new PluginParseException("Required parameter '" + VERSION_PARAM + "' missing");
        }

        if (!Version.STRICT_NUMERIC_VALIDATOR.matcher(stringVersion).matches()) {
            throw new PluginParseException("Invalid " + VERSION_PARAM + ": " + stringVersion);
        }

        return new Version(stringVersion);
    }

    private Operator getOperator(Map<String, String> params) {
        String stringOperator = params.get(OPERATOR_PARAM);
        if (stringOperator == null) {
            throw new PluginParseException("Required parameter '" + OPERATOR_PARAM + "' missing");
        }
        try {
            return Operator.valueOf(stringOperator.toUpperCase());
        } catch (IllegalArgumentException e) {
            throw new PluginParseException("Invalid " + OPERATOR_PARAM + ": " + stringOperator);
        }
    }

    private enum Operator {
        EQ {
            @Override
            public boolean test(int compareToResult) {
                return compareToResult == 0;
            }
        },
        NEQ {
            @Override
            public boolean test(int compareToResult) {
                return compareToResult != 0;
            }
        },
        GT {
            @Override
            public boolean test(int compareToResult) {
                return compareToResult > 0;
            }
        },
        GTE {
            @Override
            public boolean test(int compareToResult) {
                return compareToResult >= 0;
            }
        },
        LT {
            @Override
            public boolean test(int compareToResult) {
                return compareToResult < 0;
            }
        },
        LTE {
            @Override
            public boolean test(int compareToResult) {
                return compareToResult <= 0;
            }
        };

        public abstract boolean test(int compareToResult);
    }
}
