package kiosk import ( "regexp" "strings" ) var ( validVersionRe = regexp.MustCompile(`^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`) describeRe = regexp.MustCompile(`^(v(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))(?:-[0-9]+-g[0-9a-f]+)?$`) pseudoBaseRe = regexp.MustCompile(`^(v[0-9]+\.[0-9]+\.[0-9]+)-`) // pseudoNoBaseRe is a pseudo-version with no tag before its commit: // vX.0.0--, without the "0." or "
.0." of one
	// that builds on a release or a pre-release.
	pseudoNoBaseRe = regexp.MustCompile(`^v[0-9]+\.[0-9]+\.[0-9]+-[0-9]{14}-[0-9a-f]{12}$`)
)

// ValidVersion reports whether v is a release version in the strict form
// vX.Y.Z: no pre-release, no build metadata, no leading zeros.
func ValidVersion(v string) bool {
	return validVersionRe.MatchString(v)
}

// CompareVersions compares two valid versions numerically per component
// and returns -1, 0 or +1. It is only defined for versions ValidVersion
// accepts; never pass a git describe string (use BaseVersion first).
func CompareVersions(a, b string) int {
	pa := strings.Split(strings.TrimPrefix(a, "v"), ".")
	pb := strings.Split(strings.TrimPrefix(b, "v"), ".")
	for i := 0; i < len(pa) && i < len(pb); i++ {
		if c := compareNumeric(pa[i], pb[i]); c != 0 {
			return c
		}
	}
	switch {
	case len(pa) < len(pb):
		return -1
	case len(pa) > len(pb):
		return 1
	}
	return 0
}

// compareNumeric compares two decimal strings without leading zeros, of
// any length.
func compareNumeric(a, b string) int {
	switch {
	case len(a) != len(b):
		if len(a) < len(b) {
			return -1
		}
		return 1
	case a < b:
		return -1
	case a > b:
		return 1
	}
	return 0
}

// BaseVersion returns the vX.Y.Z tag a `git describe --tags` string starts
// from: "v0.3.1" for "v0.3.1" and for "v0.3.1-4-gabc1234". Anything else
// (a bare commit hash, an empty string, a pre-release tag) gives "".
func BaseVersion(describe string) string {
	m := describeRe.FindStringSubmatch(describe)
	if m == nil {
		return ""
	}
	return m[1]
}

// Newer reports whether release is an upgrade for a plugin installed at
// installed, so moving to it never goes back. installed may be a release
// vX.Y.Z (newer: above it), a git describe string vX.Y.Z-N-g, a
// commit after vX.Y.Z (newer: above vX.Y.Z), or a Go pseudo-version, a
// commit after the tag it builds on: vX.Y.(Z+1)-0.-
// after the release vX.Y.Z, vX.Y.Z-
.0.- after the
// pre-release vX.Y.Z-
; newer is at or above vX.Y.(Z+1) and vX.Y.Z,
// Go's own version order. Nothing is newer than an installed version
// Comparable refuses (a pseudo-version with no tag before its commit, a
// bare commit hash, a pre-release, ""). release must be a valid release.
func Newer(release, installed string) bool {
	if !ValidVersion(release) {
		return false
	}
	if base := BaseVersion(installed); base != "" {
		return CompareVersions(release, base) > 0
	}
	if !isPseudoVersion(installed) || pseudoNoBaseRe.MatchString(installed) {
		return false
	}
	core := pseudoBaseRe.FindStringSubmatch(installed)
	if core == nil || !ValidVersion(core[1]) {
		return false
	}
	return CompareVersions(release, core[1]) >= 0
}

// Comparable reports whether Newer can place installed among releases: a
// release, a git describe string, or a pseudo-version that builds on a
// release or a pre-release.
func Comparable(installed string) bool {
	if BaseVersion(installed) != "" {
		return true
	}
	if !isPseudoVersion(installed) || pseudoNoBaseRe.MatchString(installed) {
		return false
	}
	core := pseudoBaseRe.FindStringSubmatch(installed)
	return core != nil && ValidVersion(core[1])
}

// Satisfies reports whether the release have meets the minimum min. An
// empty minimum is always met. A pseudo-version minimum such as
// vX.Y.Z-0.- sorts just below vX.Y.Z, so it is met by
// vX.Y.Z and later. have must be a valid release; anything else meets
// only the empty minimum.
func Satisfies(have, min string) bool {
	if min == "" {
		return true
	}
	if !ValidVersion(have) {
		return false
	}
	if ValidVersion(min) {
		return CompareVersions(have, min) >= 0
	}
	if isPseudoVersion(min) {
		base := pseudoBaseRe.FindStringSubmatch(min)
		return base != nil && ValidVersion(base[1]) && CompareVersions(have, base[1]) >= 0
	}
	return false
}

// NewestCompatible returns the newest release in versions whose framework
// minimum the given framework version satisfies, and false when there is
// none. Entries whose Version is not a valid release are skipped.
func NewestCompatible(versions []RegistryVersion, framework string) (RegistryVersion, bool) {
	var best RegistryVersion
	found := false
	for _, v := range versions {
		if !ValidVersion(v.Version) || !Satisfies(framework, v.Framework) {
			continue
		}
		if !found || CompareVersions(v.Version, best.Version) > 0 {
			best, found = v, true
		}
	}
	return best, found
}