package apiversion

import (
	"fmt"
	"regexp"
	"strconv"

	"github.com/pkg/errors"
)

// API version names follow k8s style version names:
// valid API version names examples: "v1", "v1alpha2", "v2beta3", etc...
var nameRegex = regexp.MustCompile("^v([1-9][0-9]*)(?:(alpha|beta)([1-9][0-9]*))?$")

type qualifier uint

const (
	alpha qualifier = iota
	beta
	stable
)

type Version struct {
	// major version number, eg 1 for "v1", 2 for "v2beta3"
	major uint

	// qualifier is "alpha" or "beta"
	qualifier qualifier

	// the qualifier's version, eg 2 for "v1alpha2" or 3 for "v2beta3"
	qualifierVersion uint

	rawName string
}

// NewVersion builds a Version from a version string, eg "v1alpha2"
func NewVersion(name string) (version Version, err error) {
	matches := nameRegex.FindStringSubmatch(name)
	if len(matches) < 2 || matches[1] == "" {
		err = fmt.Errorf("invalid version name: %q", name)
		return
	}

	major, err := toUint(matches[1], name)
	if err != nil {
		return
	}

	qualifier := stable
	var qualifierVersion uint
	if len(matches) >= 4 && matches[2] != "" {
		switch matches[2] {
		case "alpha":
			qualifier = alpha
		case "beta":
			qualifier = beta
		}

		qualifierVersion, err = toUint(matches[3], name)
		if err != nil {
			return
		}
	}

	version.major = major
	version.qualifier = qualifier
	version.qualifierVersion = qualifierVersion
	version.rawName = name

	return
}

// NewVersionOrPanic is the same as NewVersion, but panics if
// passed an invalid version name.
func NewVersionOrPanic(name string) Version {
	version, err := NewVersion(name)
	if err != nil {
		panic(err)
	}
	return version
}

// IsValidVersion returns true iff the input is a valid version name.
func IsValidVersion(name string) bool {
	return nameRegex.MatchString(name)
}

func toUint(s, name string) (uint, error) {
	i, err := strconv.ParseUint(s, 10, 0)
	if err != nil {
		return 0, errors.Wrapf(err, "unable to convert %q from version name %q to int", s, name)
	}
	return uint(i), nil
}

type Comparison int

const (
	Lesser  Comparison = -1
	Equal   Comparison = 0
	Greater Comparison = 1
)

// Compare return Lesser if v < other (ie other is more recent), Equal if v == other,
// and Greater if v > other (ie v is more recent)
func (v Version) Compare(other Version) Comparison {
	if cmp := compareUints(v.major, other.major); cmp != Equal {
		return cmp
	}
	if cmp := compareUints(uint(v.qualifier), uint(other.qualifier)); cmp != Equal {
		return cmp
	}
	return compareUints(v.qualifierVersion, other.qualifierVersion)
}

func compareUints(a, b uint) Comparison {
	if a < b {
		return Lesser
	}
	if a > b {
		return Greater
	}
	return Equal
}

func (v Version) String() string {
	return v.rawName
}
