package com.airlock.iam.system.version;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@SuppressFBWarnings(value = "REDOS", justification = "Match of VERSION_PATTERN is never against untrusted input and REDOS is only theoretical.")
public final class GatewayVersion implements Comparable<GatewayVersion> {
   private static final Pattern VERSION_PATTERN = Pattern.compile(
      "(?<major>[0-9]++)(?:\\.(?<minor>[0-9]++)((?:\\.(?<patch>[0-9]++))?(?:\\.(?<prerelasetag>[A-Za-z0-9._-]++))?))?"
   );
   private final int major;
   private final int minor;
   private final int patch;

   public static GatewayVersion create(int major, int minor, int patch) {
      return new GatewayVersion(major, minor, patch);
   }

   public static Optional<GatewayVersion> from(String version) {
      if (version != null && !version.isBlank()) {
         Matcher matcher = VERSION_PATTERN.matcher(version);
         if (!matcher.matches()) {
            throw new IllegalArgumentException("Unknown version pattern: " + version);
         }

         int major = parse(matcher.group("major"));
         int minor = parse(matcher.group("minor"));
         int patch = parse(matcher.group("patch"));
         return Optional.of(new GatewayVersion(major, minor, patch));
      } else {
         return Optional.empty();
      }
   }

   private static int parse(String number) {
      return number != null && !number.isBlank() ? Integer.parseInt(number) : 0;
   }

   private GatewayVersion(int major, int minor, int patch) {
      this.major = major;
      this.minor = minor;
      this.patch = patch;
   }

   public int compareTo(GatewayVersion other) {
      return Comparator.<GatewayVersion>comparingInt(value -> value.major)
         .thenComparingInt(value -> value.minor)
         .thenComparingInt(value -> value.patch)
         .compare(this, other);
   }

   @Override
   public boolean equals(Object o) {
      if (this == o) {
         return true;
      } else if (o != null && this.getClass() == o.getClass()) {
         GatewayVersion that = (GatewayVersion)o;
         return this.compareTo(that) == 0;
      } else {
         return false;
      }
   }

   @Override
   public int hashCode() {
      return Objects.hash(this.major, this.minor, this.patch);
   }

   @Override
   public String toString() {
      return IntStream.of(this.major, this.minor, this.patch).mapToObj(Integer::toString).collect(Collectors.joining("."));
   }
}
