package utils

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
)

type parsed struct {
	major string
	minor string
	patch string
	short string
	micro string
}

func DiffVersion(v, w string) (string, error) {
	pv, ok1 := parse(v)
	pw, ok2 := parse(w)
	if !ok1 && !ok2 {
		return "", fmt.Errorf("invalid versions: %q, %q", v, w)
	}
	if !ok1 {
		return "", fmt.Errorf("invalid version: %q", v)
	}
	if !ok2 {
		return "", fmt.Errorf("invalid version: %q", w)
	}
	pvm, err := strconv.Atoi(pv.major)
	if err != nil {
		return "", err
	}
	pwm, err := strconv.Atoi(pw.major)
	if err != nil {
		return "", err
	}
	pmdelta := pvm - pwm
	if pmdelta < 0 {
		pmdelta = 0
	}
	pvn, err := strconv.Atoi(pv.minor)
	if err != nil {
		return "", err
	}
	pwn, err := strconv.Atoi(pw.minor)
	if err != nil {
		return "", err
	}
	pndelta := pvn - pwn
	if pndelta < 0 {
		pndelta = 0
	}
	pvp, err := strconv.Atoi(pv.patch)
	if err != nil {
		return "", err
	}
	pwp, err := strconv.Atoi(pw.patch)
	if err != nil {
		return "", err
	}
	ppdelta := pvp - pwp
	if ppdelta < 0 {
		ppdelta = 0
	}
	pvs, err := strconv.Atoi(pv.short)
	if err != nil {
		return "", err
	}
	pws, err := strconv.Atoi(pw.short)
	if err != nil {
		return "", err
	}
	psdelta := pvs - pws
	if psdelta < 0 {
		psdelta = 0
	}
	pvc, err := strconv.Atoi(pv.micro)
	if err != nil {
		return "", err
	}
	pwc, err := strconv.Atoi(pw.micro)
	if err != nil {
		return "", err
	}
	pcdelta := pvc - pwc
	if pcdelta < 0 {
		pcdelta = 0
	}
	return fmt.Sprintf("%d.%d.%d.%d.%d", pmdelta, pndelta, ppdelta, psdelta, pcdelta), nil
}

// Compare returns an integer comparing two versions according to
// semantic version precedence.
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
//
// An invalid semantic version string is considered less than a valid one.
// All invalid semantic version strings compare equal to each other.
func Compare(v, w string) int {
	pv, ok1 := parse(v)
	pw, ok2 := parse(w)
	if !ok1 && !ok2 {
		return 0
	}
	if !ok1 {
		return -1
	}
	if !ok2 {
		return +1
	}
	if c := compareInt(pv.major, pw.major); c != 0 {
		return c
	}
	if c := compareInt(pv.minor, pw.minor); c != 0 {
		return c
	}
	if c := compareInt(pv.patch, pw.patch); c != 0 {
		return c
	}
	if c := compareInt(pv.short, pw.short); c != 0 {
		return c
	}
	if c := compareInt(pv.micro, pw.micro); c != 0 {
		return c
	}
	return 0
}

// ByVersion implements sort.Interface for sorting semantic version strings.
type ByVersion []string

func (vs ByVersion) Len() int      { return len(vs) }
func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
func (vs ByVersion) Less(i, j int) bool {
	cmp := Compare(vs[i], vs[j])
	if cmp != 0 {
		return cmp < 0
	}
	return vs[i] < vs[j]
}

// SortVersions sorts a list of semantic version strings using ByVersion.
func SortVersions(list []string) {
	sort.Sort(ByVersion(list))
}

type MachoVersion struct {
	Name    string
	Version string
}

type ByMachoVersion []MachoVersion

func (vs ByMachoVersion) Len() int      { return len(vs) }
func (vs ByMachoVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
func (vs ByMachoVersion) Less(i, j int) bool {
	cmp := Compare(vs[i].Version, vs[j].Version)
	if cmp != 0 {
		return cmp > 0
	}
	return strings.Compare(vs[i].Name, vs[j].Name) < 0
}

func SortMachoVersions(list []MachoVersion) {
	sort.Sort(ByMachoVersion(list))
}

func parse(v string) (p parsed, ok bool) {
	p.major, v, ok = parseInt(v)
	if !ok {
		return
	}
	if v == "" {
		p.minor = "0"
		p.patch = "0"
		p.short = ".0.0"
		return
	}
	if v[0] != '.' {
		ok = false
		return
	}
	p.minor, v, ok = parseInt(v[1:])
	if !ok {
		return
	}
	if v == "" {
		p.patch = "0"
		p.short = ".0"
		return
	}
	if v[0] != '.' {
		ok = false
		return
	}
	p.patch, v, ok = parseInt(v[1:])
	if !ok {
		return
	}
	if v == "" {
		p.patch = "0"
		p.short = ".0"
		return
	}
	if v[0] != '.' {
		ok = false
		return
	}
	p.short, v, ok = parseInt(v[1:])
	if !ok {
		return
	}
	if v == "" {
		p.short = ".0"
		return
	}
	if v[0] != '.' {
		ok = false
		return
	}
	p.micro, v, ok = parseInt(v[1:])
	if !ok {
		return
	}
	if v != "" {
		ok = false
		return
	}
	ok = true
	return
}

func parseInt(v string) (t, rest string, ok bool) {
	if v == "" {
		return
	}
	if v[0] < '0' || '9' < v[0] {
		return
	}
	i := 1
	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
		i++
	}
	if v[0] == '0' && i != 1 {
		return
	}
	return v[:i], v[i:], true
}

func compareInt(x, y string) int {
	if x == y {
		return 0
	}
	if len(x) < len(y) {
		return -1
	}
	if len(x) > len(y) {
		return +1
	}
	if x < y {
		return -1
	} else {
		return +1
	}
}
