package version

import (
	"errors"
	"strconv"
	"strings"
)

type Version struct {
	Major, Minor uint64
}

var (
	// ErrInvalidSemVer is returned if a version can not be parsed
	ErrInvalidSemVer = errors.New("invalid semantic version")

	// ErrUnsupportedVer is returned if a version out of supported range
	ErrUnsupportedVer = errors.New("the version is out of supported range")

	php5RangeStart = &Version{Major: 5}
	php5RangeEnd   = &Version{Major: 5, Minor: 6}

	php7RangeStart = &Version{Major: 7}
	php7RangeEnd   = &Version{Major: 7, Minor: 4}

	php8RangeStart = &Version{Major: 8}
	php8RangeEnd   = &Version{Major: 8, Minor: 1}
)

func New(v string) (*Version, error) {
	// Split the parts into [0]Major, [1]Minor
	parts := strings.SplitN(v, ".", 2)
	if len(parts) != 2 {
		return nil, ErrInvalidSemVer
	}

	var ver = new(Version)
	var err error

	ver.Major, err = strconv.ParseUint(parts[0], 10, 64)
	if err != nil {
		return nil, err
	}

	ver.Minor, err = strconv.ParseUint(parts[1], 10, 64)
	if err != nil {
		return nil, err
	}

	return ver, nil
}

func (v *Version) Validate() error {
	if !v.InRange(php5RangeStart, php5RangeEnd) &&
		!v.InRange(php7RangeStart, php7RangeEnd) &&
		!v.InRange(php8RangeStart, php8RangeEnd) {
		return ErrUnsupportedVer
	}

	return nil
}

// Less tests if one version is less than another one
func (v *Version) Less(o *Version) bool {
	return v.Compare(o) < 0
}

// LessOrEqual tests if one version is less than another one or equal
func (v *Version) LessOrEqual(o *Version) bool {
	return v.Compare(o) <= 0
}

// Greater tests if one version is greater than another one
func (v *Version) Greater(o *Version) bool {
	return v.Compare(o) > 0
}

// GreaterOrEqual tests if one version is greater than another one or equal
func (v *Version) GreaterOrEqual(o *Version) bool {
	return v.Compare(o) >= 0
}

// InRange tests if version is in range in another one
func (v *Version) InRange(s, e *Version) bool {
	return v.Compare(s) >= 0 && v.Compare(e) <= 0
}

// Compare compares this version to another one. It returns -1, 0, or 1 if
// the version smaller, equal, or larger than the other version.
func (v *Version) Compare(o *Version) int {
	if d := compareSegment(v.Major, o.Major); d != 0 {
		return d
	}

	return compareSegment(v.Minor, o.Minor)
}

func compareSegment(v, o uint64) int {
	if v < o {
		return -1
	}
	if v > o {
		return 1
	}

	return 0
}
