// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
//
// 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.google.devtools.build.lib.bazel;

import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.Comparators.lexicographical;
import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import java.util.Comparator;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;

/**
 * Represents a bazel version. The version format supported is {RELEASE[SUFFIX]}, where:
 *
 * <ul>
 *   <li>{RELEASE} is a sequence of decimal numbers separated by dots;
 *   <li>{SUFFIX} could be: {@code -pre.*}, {@code rc\d+}, or any other string (which compares equal
 *       to SUFFIX is absent)
 * </ul>
 */
@AutoValue
public abstract class BazelVersion {

  private static final Pattern PATTERN =
      Pattern.compile("(?<release>(?:\\d+\\.)*\\d+)(?<suffix>(.*))?");

  /** Returns the "release" part of the version string as a list of integers. */
  abstract ImmutableList<Integer> getRelease();

  /** Returns the "suffix" part of the version that starts after the integers */
  abstract String getSuffix();

  /** Returns the original version string. */
  public abstract String getOriginal();

  /** Whether this is a prerelease or a release candidate */
  boolean isPrereleaseOrCandidate() {
    return getSuffix().startsWith("-pre") || getSuffix().startsWith("rc");
  }

  /** Parses a version string into a {@link BazelVersion} object. */
  public static BazelVersion parse(String version) {
    Matcher matcher = PATTERN.matcher(version);
    Preconditions.checkArgument(
        matcher.matches(), "bad version (does not match regex): %s", version);

    String release = matcher.group("release");
    @Nullable String suffix = matcher.group("suffix");

    ImmutableList<Integer> releaseSplit =
        Splitter.on('.').splitToStream(release).map(Integer::valueOf).collect(toImmutableList());
    return new AutoValue_BazelVersion(releaseSplit, nullToEmpty(suffix), version);
  }

  /** Check if class version satisfies compatibility version */
  public boolean satisfiesCompatibility(String compatVersion) {
    int cutIndex = compatVersion.contains("=") ? 2 : 1;
    String sign = compatVersion.substring(0, cutIndex);
    compatVersion = compatVersion.substring(cutIndex);

    ImmutableList<Integer> compatSplit =
        Splitter.on('.')
            .splitToStream(compatVersion)
            .map(Integer::valueOf)
            .collect(toImmutableList());

    int result =
        Objects.compare(
            getRelease(), compatSplit, lexicographical(Comparator.<Integer>naturalOrder()));
    if (result == 0 && isPrereleaseOrCandidate()) {
      result = -1;
    }

    return (result == 0 && sign.contains("="))
        || (result > 0 && (sign.contains(">") || sign.contains("-")))
        || (result < 0 && (sign.contains("<") || sign.contains("-")));
  }
}
